From c8191a19e15c43c1871880af478f0fc0bbf9c0b9 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 5 Jan 2023 11:52:07 +0100 Subject: [PATCH] refactor: removed custom error type; greatly increased internal api --- Makefile | 6 +++- containers.v | 48 ++++---------------------------- docker.v | 77 ++++++++++++++++++++++++++-------------------------- errors.v | 22 --------------- images.v | 20 +++----------- 5 files changed, 54 insertions(+), 119 deletions(-) delete mode 100644 errors.v diff --git a/Makefile b/Makefile index b25f105..7a86c0e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # =====CONFIG===== V_PATH ?= v -V := $(V_PATH) -showcc +V := $(V_PATH) -showcc -d use_openssl all: vdocker @@ -10,6 +10,10 @@ all: vdocker vdocker: $(V) -g -shared . +.PHONY: c +c: + $(V) -o docker.c . + # =====DOCS===== .PHONY: api-docs diff --git a/containers.v b/containers.v index 809ef38..7b0af3a 100644 --- a/containers.v +++ b/containers.v @@ -1,6 +1,5 @@ module docker -import json import time import net.http { Method } import types { ContainerListItem } @@ -38,29 +37,14 @@ pub: pub fn (mut d DockerConn) container_create(c NewContainer) !CreatedContainer { d.send_request_with_json(Method.post, '/containers/create', c)! - head, res := d.read_response()! - if head.status_code != 201 { - data := json.decode(DockerError, res)! - - return error(data.message) - } - - data := json.decode(CreatedContainer, res)! - - return data + return d.read_json_response() } // start_container starts the container with the given id. pub fn (mut d DockerConn) container_start(id string) ! { d.send_request(Method.post, '/containers/$id/start')! - head, body := d.read_response()! - - if head.status_code != 204 { - data := json.decode(DockerError, body)! - - return error(data.message) - } + d.read_response()! } struct ContainerInspect { @@ -83,15 +67,8 @@ pub mut: pub fn (mut d DockerConn) container_inspect(id string) !ContainerInspect { d.send_request(Method.get, '/containers/$id/json')! - head, body := d.read_response()! - if head.status_code != 200 { - data := json.decode(DockerError, body)! - - return error(data.message) - } - - mut data := json.decode(ContainerInspect, body)! + mut data := d.read_json_response()! // The Docker engine API *should* always return UTC time. data.state.start_time = time.parse_rfc3339(data.state.start_time_str)! @@ -105,26 +82,13 @@ pub fn (mut d DockerConn) container_inspect(id string) !ContainerInspect { pub fn (mut d DockerConn) container_remove(id string) ! { d.send_request(Method.delete, '/containers/$id')! - head, body := d.read_response()! - - if head.status_code != 204 { - data := json.decode(DockerError, body)! - - return error(data.message) - } + d.read_response()! } pub fn (mut d DockerConn) container_get_logs(id string) !&StreamFormatReader { d.send_request(Method.get, '/containers/$id/logs?stdout=true&stderr=true')! - head := d.read_response_head()! - - if head.status_code != 200 { - content_length := head.header.get(http.CommonHeader.content_length)!.int() - body := d.read_response_body(content_length)! - data := json.decode(DockerError, body)! - - return error(data.message) - } + d.read_response_head()! + d.check_error()! return d.get_stream_format_reader() } diff --git a/docker.v b/docker.v index 7eb5ea2..d1180f8 100644 --- a/docker.v +++ b/docker.v @@ -27,6 +27,7 @@ mut: url string params map[string]string content_type string + head http.Response body string } @@ -81,79 +82,61 @@ fn (mut d DockerConn) send_request_with_json(method http.Method, url_str stri // '\r\n\r\n', after which it parses the response as an HTTP response. // Importantly, this function never consumes the reader past the HTTP // separator, so the body can be read fully later on. -fn (mut d DockerConn) read_response_head() !http.Response { +fn (mut d DockerConn) read_response_head() ! { mut res := []u8{} util.read_until_separator(mut d.reader, mut res, docker.http_separator)! - return http.parse_response(res.bytestr()) + d.head = http.parse_response(res.bytestr())! } -// read_response_body reads `length` bytes from the stream. It can be used when -// the response encoding isn't chunked to fully read it. -fn (mut d DockerConn) read_response_body(length int) !string { - if length == 0 { - return '' +fn (mut d DockerConn) read_response_body() ! { + content_length := d.head.header.get(.content_length)!.int() + + if content_length == 0 { + return } mut buf := []u8{len: docker.buf_len} mut c := 0 mut builder := strings.new_builder(docker.buf_len) - for builder.len < length { + for builder.len < content_length { c = d.reader.read(mut buf) or { break } builder.write(buf[..c])! } - return builder.str() + d.body = builder.str() } // read_response is a convenience function which always consumes the entire // response & returns it. It should only be used when we're certain that the // result isn't too large. -fn (mut d DockerConn) read_response() !(http.Response, string) { - head := d.read_response_head()! - - if head.status().is_error() { - content_length := head.header.get(.content_length)!.int() - body := d.read_response_body(content_length)! - mut err := json.decode(DockerError, body)! - err.status = head.status_code - - return err - } +fn (mut d DockerConn) read_response() ! { + d.read_response_head()! + d.check_error()! // 204 means "No Content", so we can assume nothing follows after this - if head.status() == .no_content { - return head, '' + if d.head.status() == .no_content { + return } - if head.header.get(http.CommonHeader.transfer_encoding) or { '' } == 'chunked' { + if d.head.header.get(http.CommonHeader.transfer_encoding) or { '' } == 'chunked' { mut builder := strings.new_builder(1024) mut body := d.get_chunked_response_reader() util.reader_to_writer(mut body, mut builder)! - - return head, builder.str() + d.body = builder.str() + } else { + d.read_response_body()! } - - content_length := head.header.get(http.CommonHeader.content_length)!.int() - body := d.read_response_body(content_length)! - - return head, body } fn (mut d DockerConn) read_json_response() !T { - head, body := d.read_response()! + d.read_response()! - if head.status_code < 200 || head.status_code > 300 { - data := json.decode(DockerError, body)! - - return docker_error(head.status_code, data.message) - } - - mut data := json.decode(T, body)! + mut data := json.decode(T, d.body)! //$for field in T.fields { //$if field.typ is time.Time { @@ -180,3 +163,21 @@ fn (mut d DockerConn) get_stream_format_reader() &StreamFormatReader { return r2 } + +struct DockerError { +pub: + message string +} + +// check_error should be called after read_response_head. If the status code of +// the response is an error, the body is consumed and the Docker HTTP error is +// returned as a V error. If the status isn't the error, this function is a +// no-op. +fn (mut d DockerConn) check_error() ! { + if d.head.status().is_error() { + d.read_response_body()! + d_err := json.decode(DockerError, d.body)! + + return error_with_code('$d.head.status(): $d_err.message', d.head.status_code) + } +} diff --git a/errors.v b/errors.v deleted file mode 100644 index 31ed81a..0000000 --- a/errors.v +++ /dev/null @@ -1,22 +0,0 @@ -module docker - -struct DockerError { -pub mut: - status int [skip] - message string -} - -fn (err DockerError) code() int { - return err.status -} - -fn (err DockerError) msg() string { - return err.message -} - -fn docker_error(status int, message string) DockerError { - return DockerError{ - status: status - message: message - } -} diff --git a/images.v b/images.v index a6f2a23..03f9387 100644 --- a/images.v +++ b/images.v @@ -2,13 +2,11 @@ module docker import net.http { Method } import types { Image } -import json pub fn (mut d DockerConn) image_inspect(image string) !Image { d.send_request(.get, '/images/$image/json')! - _, body := d.read_response()! - data := json.decode(Image, body)! + data := d.read_json_response()! return data } @@ -16,16 +14,8 @@ pub fn (mut d DockerConn) image_inspect(image string) !Image { // pull_image pulls the given image:tag. pub fn (mut d DockerConn) pull_image(image string, tag string) ! { d.send_request(Method.post, '/images/create?fromImage=$image&tag=$tag')! - head := d.read_response_head()! - - if head.status().is_error() { - content_length := head.header.get(.content_length)!.int() - body := d.read_response_body(content_length)! - mut err := json.decode(DockerError, body)! - err.status = head.status_code - - return err - } + d.read_response_head()! + d.check_error()! // Keep reading the body until the pull has completed mut body := d.get_chunked_response_reader() @@ -40,9 +30,7 @@ pub fn (mut d DockerConn) pull_image(image string, tag string) ! { // create_image_from_container creates a new image from a container. pub fn (mut d DockerConn) create_image_from_container(id string, repo string, tag string) !Image { d.send_request(.post, '/commit?container=$id&repo=$repo&tag=$tag')! - _, body := d.read_response()! - - data := json.decode(Image, body)! + data := d.read_json_response()! return data }