From cc227580701277ba8e806613747ee8179ca7942d Mon Sep 17 00:00:00 2001 From: Killian Delarue Date: Tue, 16 Nov 2021 16:56:17 +0100 Subject: [PATCH 1/3] lib_rpc_http: add generic_media_type_call --- src/lib_client_base/client_context.ml | 2 + src/lib_mockup/RPC_client.ml | 3 + src/lib_mockup_proxy/RPC_client.ml | 3 + src/lib_proxy/RPC_client.ml | 38 ++- src/lib_proxy/proxy_events.ml | 18 ++ src/lib_rpc/RPC_context.ml | 16 +- src/lib_rpc/RPC_context.mli | 20 +- src/lib_rpc_http/RPC_client.ml | 240 ++++++++++++------ src/lib_rpc_http/RPC_client.mli | 8 + src/lib_rpc_http/media_type.ml | 18 ++ src/lib_rpc_http/media_type.mli | 14 + .../lib_client/alpha_client_context.ml | 2 + .../lib_client/alpha_client_context.ml | 2 + .../lib_client/alpha_client_context.ml | 2 + .../lib_client/alpha_client_context.ml | 2 + .../lib_client/protocol_client_context.ml | 2 + .../lib_client/protocol_client_context.ml | 2 + .../lib_client/protocol_client_context.ml | 2 + .../lib_client/protocol_client_context.ml | 2 + .../lib_client/protocol_client_context.ml | 2 + .../lib_client/protocol_client_context.ml | 2 + .../lib_client/protocol_client_context.ml | 2 + .../lib_client/protocol_client_context.ml | 2 + .../mockup_simulator/faked_client_context.ml | 3 + 24 files changed, 322 insertions(+), 85 deletions(-) diff --git a/src/lib_client_base/client_context.ml b/src/lib_client_base/client_context.ml index 332253e7a710..e6272beb3c76 100644 --- a/src/lib_client_base/client_context.ml +++ b/src/lib_client_base/client_context.ml @@ -185,6 +185,8 @@ class proxy_context (obj : full) = method generic_json_call = obj#generic_json_call + method generic_media_type_call = obj#generic_media_type_call + method with_lock : type a. (unit -> a Lwt.t) -> a Lwt.t = obj#with_lock method load : type a. diff --git a/src/lib_mockup/RPC_client.ml b/src/lib_mockup/RPC_client.ml index b03ae2048bbe..4978591f7b32 100644 --- a/src/lib_mockup/RPC_client.ml +++ b/src/lib_mockup/RPC_client.ml @@ -43,6 +43,9 @@ class mockup_ctxt (base_dir : string) (mem_only : bool) method generic_json_call meth ?body uri = local_ctxt#generic_json_call meth ?body uri + method generic_media_type_call meth ?body uri = + local_ctxt#generic_media_type_call meth ?body uri + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/lib_mockup_proxy/RPC_client.ml b/src/lib_mockup_proxy/RPC_client.ml index 3461b54908cd..d55c4833b5b0 100644 --- a/src/lib_mockup_proxy/RPC_client.ml +++ b/src/lib_mockup_proxy/RPC_client.ml @@ -68,6 +68,9 @@ let local_ctxt (directory : unit RPC_directory.t) : RPC_context.json = method generic_json_call meth ?body uri = C.generic_json_call meth ?body uri + method generic_media_type_call meth ?body uri = + C.generic_media_type_call ~accept:media_types meth ?body uri + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/lib_proxy/RPC_client.ml b/src/lib_proxy/RPC_client.ml index b22e2ccae897..77953c846472 100644 --- a/src/lib_proxy/RPC_client.ml +++ b/src/lib_proxy/RPC_client.ml @@ -156,8 +156,8 @@ class http_local_ctxt (printer : Tezos_client_base.Client_context.printer) in if method_is_writer meth then delegate () else - let* y = local_ctxt#generic_json_call meth ?body uri in - match y with + let* answer = local_ctxt#generic_json_call meth ?body uri in + match answer with | Ok (`Not_found _) | Error [Tezos_rpc.RPC_context.Not_found _] -> delegate () | Ok x -> @@ -166,4 +166,38 @@ class http_local_ctxt (printer : Tezos_client_base.Client_context.printer) in return_ok x | Error _ as err -> Lwt.return err + + method generic_media_type_call + : Service.meth -> + ?body:Data_encoding.json -> + Uri.t -> + RPC_context.generic_call_result Tezos_error_monad.Error_monad.tzresult + Lwt.t = + let open Lwt_syntax in + fun meth ?body uri -> + let meth_string = RPC_service.string_of_meth meth in + let uri_string = Uri.to_string uri in + let delegate () = + let* () = + Events.(emit delegate_media_type_call_to_http) + (meth_string, uri_string) + in + http_ctxt#generic_media_type_call meth ?body uri + in + if method_is_writer meth then delegate () + else + let* answer = local_ctxt#generic_media_type_call meth ?body uri in + match answer with + | Ok (`Json (`Not_found _)) + | Ok (`Binary (`Not_found _)) + | Ok (`Other (_, `Not_found _)) + | Error [Tezos_rpc.RPC_context.Not_found _] -> + delegate () + | Ok x -> + let* () = + Events.(emit done_media_type_call_locally) + (meth_string, uri_string) + in + return_ok x + | Error _ as err -> Lwt.return err end diff --git a/src/lib_proxy/proxy_events.ml b/src/lib_proxy/proxy_events.ml index d5b3f48dec40..2d5dfd5222e5 100644 --- a/src/lib_proxy/proxy_events.ml +++ b/src/lib_proxy/proxy_events.ml @@ -41,3 +41,21 @@ let done_json_call_locally = ~msg:"locally done generic json call: {method} {uri}" ("method", Data_encoding.string) ("uri", Data_encoding.string) + +let delegate_media_type_call_to_http = + declare_2 + ~section + ~level + ~name:"delegate_media_type_call_to_http" + ~msg:"delegating to http generic media type call: {method} {uri}" + ("method", Data_encoding.string) + ("uri", Data_encoding.string) + +let done_media_type_call_locally = + declare_2 + ~section + ~level + ~name:"done_media_type_call_locally" + ~msg:"locally done generic media type call: {method} {uri}" + ("method", Data_encoding.string) + ("uri", Data_encoding.string) diff --git a/src/lib_rpc/RPC_context.ml b/src/lib_rpc/RPC_context.ml index 846c2fdccbb8..93f7f3ff8e41 100644 --- a/src/lib_rpc/RPC_context.ml +++ b/src/lib_rpc/RPC_context.ml @@ -73,7 +73,7 @@ class type t = inherit streamed end -type ('o, 'e) rest_result = +type ('o, 'e) rest = [ `Ok of 'o | `Conflict of 'e | `Error of 'e @@ -81,7 +81,13 @@ type ('o, 'e) rest_result = | `Not_found of 'e | `Gone of 'e | `Unauthorized of 'e ] - tzresult + +type ('o, 'e) rest_result = ('o, 'e) rest tzresult + +type generic_call_result = + [ `Json of (Data_encoding.json, Data_encoding.json option) rest + | `Binary of (string, string option) rest + | `Other of (string * string) option * (string, string option) rest ] class type json = object @@ -93,6 +99,12 @@ class type json = Uri.t -> (Data_encoding.json, Data_encoding.json option) rest_result Lwt.t + method generic_media_type_call : + RPC_service.meth -> + ?body:Data_encoding.json -> + Uri.t -> + generic_call_result tzresult Lwt.t + method base : Uri.t end diff --git a/src/lib_rpc/RPC_context.mli b/src/lib_rpc/RPC_context.mli index c3779771f36c..c237b934d530 100644 --- a/src/lib_rpc/RPC_context.mli +++ b/src/lib_rpc/RPC_context.mli @@ -73,7 +73,8 @@ class type t = inherit streamed end -type ('o, 'e) rest_result = +(** ['o] is the type of the result (output) and ['e] the type of the error *) +type ('o, 'e) rest = [ `Ok of 'o | `Conflict of 'e | `Error of 'e @@ -81,7 +82,16 @@ type ('o, 'e) rest_result = | `Not_found of 'e | `Gone of 'e | `Unauthorized of 'e ] - tzresult + +type ('o, 'e) rest_result = ('o, 'e) rest tzresult + +(** The type of a generic call result *) +type generic_call_result = + [ `Json of (Data_encoding.json, Data_encoding.json option) rest + | `Binary of (string, string option) rest + | `Other of + (string * string) option * (string, string option) rest + (* [(string * string) option] corresponds to the content type *) ] class type json = object @@ -93,6 +103,12 @@ class type json = Uri.t -> (Data_encoding.json, Data_encoding.json option) rest_result Lwt.t + method generic_media_type_call : + RPC_service.meth -> + ?body:Data_encoding.json -> + Uri.t -> + generic_call_result tzresult Lwt.t + method base : Uri.t end diff --git a/src/lib_rpc_http/RPC_client.ml b/src/lib_rpc_http/RPC_client.ml index 31deb31793e1..59e7dabe56ec 100644 --- a/src/lib_rpc_http/RPC_client.ml +++ b/src/lib_rpc_http/RPC_client.ml @@ -100,10 +100,18 @@ module type S = sig (Data_encoding.json, Data_encoding.json option) RPC_context.rest_result Lwt.t - type content_type = string * string + type content_type = Media_type.Content_type.t type content = Cohttp_lwt.Body.t * content_type option * Media_type.t option + val generic_media_type_call : + ?headers:(string * string) list -> + accept:Media_type.t list -> + ?body:Data_encoding.json -> + [< RPC_service.meth] -> + Uri.t -> + RPC_context.generic_call_result tzresult Lwt.t + val generic_call : ?headers:(string * string) list -> ?accept:Media_type.t list -> @@ -127,7 +135,7 @@ module Make (Client : Resto_cohttp_client.Client.CALL) = struct let full_logger = Client.full_logger - type content_type = string * string + type content_type = Media_type.Content_type.t type content = Cohttp_lwt.Body.t * content_type option * Media_type.t option @@ -167,106 +175,174 @@ module Make (Client : Resto_cohttp_client.Client.CALL) = struct | `Unauthorized_host host -> request_failed meth uri (Unauthorized_host host) - let handle_error meth uri (body, media, _) f = + let handle_error (body, content_type, _) f = Cohttp_lwt.Body.is_empty body >>= fun empty -> - if empty then return (f None) + if empty then return (content_type, f None) else - match media with + Cohttp_lwt.Body.to_string body >>= fun body -> + return (content_type, f (Some body)) + + let jsonify_other meth uri content_type error : + (Data_encoding.json, Data_encoding.json option) RPC_context.rest_result + Lwt.t = + let jsonify_body string_body = + match content_type with | Some ("application", "json") | None -> ( - Cohttp_lwt.Body.to_string body >>= fun body -> - match Data_encoding.Json.from_string body with - | Ok body -> return (f (Some body)) + match Data_encoding.Json.from_string string_body with + | Ok json_body -> return json_body | Error msg -> request_failed meth uri (Unexpected_content { - content = body; + content = string_body; media_type = Media_type.(name json); error = msg; })) - | Some (l, r) -> - Cohttp_lwt.Body.to_string body >>= fun body -> + | Some content_type -> request_failed meth uri (Unexpected_content_type { - received = l ^ "/" ^ r; + received = + Format.asprintf "%a" Media_type.Content_type.pp content_type; acceptable = [Media_type.(name json)]; - body; + body = string_body; }) + in + let jsonify_body_opt = function + | None -> return_none + | Some string_body -> + jsonify_body string_body >>=? fun json_body -> return_some json_body + in + match error with + | `Conflict s -> jsonify_body_opt s >|=? fun s -> `Conflict s + | `Error s -> jsonify_body_opt s >|=? fun s -> `Error s + | `Forbidden s -> jsonify_body_opt s >|=? fun s -> `Forbidden s + | `Not_found s -> jsonify_body_opt s >|=? fun s -> `Not_found s + | `Gone s -> jsonify_body_opt s >|=? fun s -> `Gone s + | `Unauthorized s -> jsonify_body_opt s >|=? fun s -> `Unauthorized s + | `Ok s -> jsonify_body s >|=? fun s -> `Ok s + + let post_process_error_responses response meth uri accept = + match response with + | `Conflict body -> handle_error body (fun v -> `Conflict v) + | `Error body -> handle_error body (fun v -> `Error v) + | `Forbidden body -> handle_error body (fun v -> `Forbidden v) + | `Not_found body -> + (* The client's proxy mode matches on the `Not_found returned here, + to detect that a local RPC is unavailable at generic_json_call, + and hence that delegation to the endpoint must be done. *) + handle_error body (fun v -> `Not_found v) + | `Gone body -> handle_error body (fun v -> `Gone v) + | `Unauthorized body -> handle_error body (fun v -> `Unauthorized v) + | `Ok (body, (Some _ as content_type), _) -> + Cohttp_lwt.Body.to_string body >>= fun body -> + request_failed + meth + uri + (Unexpected_content_type + { + received = + Format.asprintf + "%a" + (Format.pp_print_option Media_type.Content_type.pp) + content_type; + acceptable = List.map Media_type.name accept; + body; + }) + | `Ok (body, None, _) -> + Cohttp_lwt.Body.to_string body >>= fun body -> return (None, `Ok body) - let generic_json_call ?headers ?body meth uri : - (Data_encoding.json, Data_encoding.json option) RPC_context.rest_result - Lwt.t = + let post_process_json_response ~body meth uri = + match Data_encoding.Json.from_string body with + | Ok json -> return json + | Error msg -> + request_failed + meth + uri + (Unexpected_content + {content = body; media_type = Media_type.(name json); error = msg}) + + let post_process_bson_response ~body meth uri = + match + Json_repr_bson.bytes_to_bson + ~laziness:false + ~copy:false + (Bytes.of_string body) + with + | exception Json_repr_bson.Bson_decoding_error (msg, _, pos) -> + let error = Format.asprintf "(at offset: %d) %s" pos msg in + request_failed + meth + uri + (Unexpected_content + {content = body; media_type = Media_type.(name bson); error}) + | bson -> + return + (Json_repr.convert + (module Json_repr_bson.Repr) + (module Json_repr.Ezjsonm) + bson) + + let generic_json_call ?headers ?body meth uri = let body = Option.map (fun b -> Cohttp_lwt.Body.of_string (Data_encoding.Json.to_string b)) body in let media = Media_type.json in - generic_call meth ?headers ~accept:Media_type.[bson; json] ?body ~media uri - >>=? function - | `Ok (body, (Some ("application", "json") | None), _) -> ( + generic_call ?headers ?body meth ~accept:Media_type.[json; bson] ~media uri + >>=? fun response -> + match response with + | `Ok (body, Some ("application", "json"), _) -> Cohttp_lwt.Body.to_string body >>= fun body -> - match Data_encoding.Json.from_string body with - | Ok json -> return (`Ok json) - | Error msg -> - request_failed - meth - uri - (Unexpected_content - { - content = body; - media_type = Media_type.(name json); - error = msg; - })) - | `Ok (body, Some ("application", "bson"), _) -> ( + post_process_json_response ~body meth uri >>=? fun body -> + return (`Ok body) + | `Ok (body, Some ("application", "bson"), _) -> Cohttp_lwt.Body.to_string body >>= fun body -> - match - Json_repr_bson.bytes_to_bson - ~laziness:false - ~copy:false - (Bytes.of_string body) - with - | exception Json_repr_bson.Bson_decoding_error (msg, _, pos) -> - let error = Format.asprintf "(at offset: %d) %s" pos msg in - request_failed - meth - uri - (Unexpected_content - {content = body; media_type = Media_type.(name bson); error}) - | bson -> - return - (`Ok - (Json_repr.convert - (module Json_repr_bson.Repr) - (module Json_repr.Ezjsonm) - bson))) - | `Ok (body, Some (l, r), _) -> + post_process_bson_response ~body meth uri >>=? fun body -> + return (`Ok body) + | _ -> + post_process_error_responses response meth uri Media_type.[json; bson] + >>=? fun (content_type, other) -> + jsonify_other meth uri content_type other + + (* This function checks that the content type of the answer belongs to accepted ones in [accept]. If not, it is processed as an error. If the answer lacks content-type, the response is decoded as JSON if possible. *) + let generic_media_type_call ?headers ~accept ?body meth uri : + RPC_context.generic_call_result tzresult Lwt.t = + let body = + Option.map + (fun b -> Cohttp_lwt.Body.of_string (Data_encoding.Json.to_string b)) + body + in + let media = Media_type.json in + generic_call meth ?headers ~accept ?body ~media uri >>=? fun response -> + match response with + | `Ok (body, Some ("application", "octet-stream"), _) + when List.mem ~equal:( == ) Media_type.octet_stream accept -> Cohttp_lwt.Body.to_string body >>= fun body -> - request_failed - meth - uri - (Unexpected_content_type - { - received = l ^ "/" ^ r; - acceptable = [Media_type.(name json)]; - body; - }) - | `Conflict body -> handle_error meth uri body (fun v -> `Conflict v) - | `Error body -> handle_error meth uri body (fun v -> `Error v) - | `Forbidden body -> handle_error meth uri body (fun v -> `Forbidden v) - | `Not_found body -> - (* The client's proxy mode matches on the `Not_found returned here, - to detect that a local RPC is unavailable at generic_json_call, - and hence that delegation to the endpoint must be done. *) - handle_error meth uri body (fun v -> `Not_found v) - | `Gone body -> handle_error meth uri body (fun v -> `Gone v) - | `Unauthorized body -> - handle_error meth uri body (fun v -> `Unauthorized v) + return (`Binary (`Ok body)) + | `Ok (body, Some ("application", "json"), _) + when List.mem ~equal:( == ) Media_type.json accept -> + Cohttp_lwt.Body.to_string body >>= fun body -> + post_process_json_response ~body meth uri >>=? fun body -> + return (`Json (`Ok body)) + | `Ok (body, Some ("application", "bson"), _) + when List.mem ~equal:( == ) Media_type.bson accept -> + Cohttp_lwt.Body.to_string body >>= fun body -> + post_process_bson_response ~body meth uri >>=? fun body -> + return (`Json (`Ok body)) + | _ -> ( + post_process_error_responses response meth uri accept + >>=? fun (content_type, other_resp) -> + (* We attempt to decode in JSON. It might + work. *) + jsonify_other meth uri content_type other_resp >>= function + | Ok jsonified -> return (`Json jsonified) + | Error _ -> return (`Other (content_type, other_resp))) let handle accept (meth, uri, ans) = match ans with @@ -382,14 +458,20 @@ module Make (Client : Resto_cohttp_client.Client.CALL) = struct class http_ctxt config media_types : RPC_context.json = let base = config.endpoint in let logger = config.logger in + let call meth uri f = + let path = Uri.path uri and query = Uri.query uri in + let prefix = Uri.path base in + let prefixed_path = if prefix = "" then path else prefix ^ "/" ^ path in + let uri = Uri.with_path base prefixed_path in + let uri = Uri.with_query uri query in + f meth uri + in object method generic_json_call meth ?body uri = - let path = Uri.path uri and query = Uri.query uri in - let prefix = Uri.path base in - let prefixed_path = if prefix = "" then path else prefix ^ "/" ^ path in - let uri = Uri.with_path base prefixed_path in - let uri = Uri.with_query uri query in - generic_json_call meth ?body uri + call meth uri (generic_json_call ?body) + + method generic_media_type_call meth ?body uri = + call meth uri (generic_media_type_call ?body ~accept:config.media_type) method call_service : 'm 'p 'q 'i 'o. diff --git a/src/lib_rpc_http/RPC_client.mli b/src/lib_rpc_http/RPC_client.mli index 2289dfc4585b..25a45099fc9a 100644 --- a/src/lib_rpc_http/RPC_client.mli +++ b/src/lib_rpc_http/RPC_client.mli @@ -100,6 +100,14 @@ module type S = sig (Data_encoding.json, Data_encoding.json option) RPC_context.rest_result Lwt.t + val generic_media_type_call : + ?headers:(string * string) list -> + accept:Media_type.t list -> + ?body:Data_encoding.json -> + [< Resto.meth] -> + Uri.t -> + RPC_context.generic_call_result tzresult Lwt.t + type content_type = string * string type content = Cohttp_lwt.Body.t * content_type option * Media_type.t option diff --git a/src/lib_rpc_http/media_type.ml b/src/lib_rpc_http/media_type.ml index 64b7dabc84e3..f7d7b9fb167f 100644 --- a/src/lib_rpc_http/media_type.ml +++ b/src/lib_rpc_http/media_type.ml @@ -170,3 +170,21 @@ let encoding : t RPC_encoding.t = ("application/bson", bson); ("application/octet-stream", octet_stream); ] + +module Content_type = struct + type t = string * string + + let json = ("application", "json") + + let bson = ("application", "bson") + + let octet_stream = ("application", "octet-stream") + + let pp fmt (l, r) = Format.fprintf fmt "%s/%s" l r +end + +let of_content_type c = + if c = Content_type.json then Some json + else if c = Content_type.bson then Some bson + else if c = Content_type.octet_stream then Some octet_stream + else None diff --git a/src/lib_rpc_http/media_type.mli b/src/lib_rpc_http/media_type.mli index 33687fe8b3d7..d5711307b412 100644 --- a/src/lib_rpc_http/media_type.mli +++ b/src/lib_rpc_http/media_type.mli @@ -47,3 +47,17 @@ val accept_header : t list -> string val first_complete_media : t list -> ((string * string) * t) option val encoding : t RPC_encoding.t + +module Content_type : sig + type t = string * string + + val json : t + + val bson : t + + val octet_stream : t + + val pp : Format.formatter -> t -> unit +end + +val of_content_type : Content_type.t -> t option diff --git a/src/proto_001_PtCJ7pwo/lib_client/alpha_client_context.ml b/src/proto_001_PtCJ7pwo/lib_client/alpha_client_context.ml index a6e0c76473d4..546b29355a3f 100644 --- a/src/proto_001_PtCJ7pwo/lib_client/alpha_client_context.ml +++ b/src/proto_001_PtCJ7pwo/lib_client/alpha_client_context.ml @@ -43,6 +43,8 @@ class wrap_rpc_context (t : RPC_context.json) : rpc_context = method generic_json_call = t#generic_json_call + method generic_media_type_call = t#generic_media_type_call + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/proto_002_PsYLVpVv/lib_client/alpha_client_context.ml b/src/proto_002_PsYLVpVv/lib_client/alpha_client_context.ml index a6e0c76473d4..546b29355a3f 100644 --- a/src/proto_002_PsYLVpVv/lib_client/alpha_client_context.ml +++ b/src/proto_002_PsYLVpVv/lib_client/alpha_client_context.ml @@ -43,6 +43,8 @@ class wrap_rpc_context (t : RPC_context.json) : rpc_context = method generic_json_call = t#generic_json_call + method generic_media_type_call = t#generic_media_type_call + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/proto_003_PsddFKi3/lib_client/alpha_client_context.ml b/src/proto_003_PsddFKi3/lib_client/alpha_client_context.ml index a6e0c76473d4..546b29355a3f 100644 --- a/src/proto_003_PsddFKi3/lib_client/alpha_client_context.ml +++ b/src/proto_003_PsddFKi3/lib_client/alpha_client_context.ml @@ -43,6 +43,8 @@ class wrap_rpc_context (t : RPC_context.json) : rpc_context = method generic_json_call = t#generic_json_call + method generic_media_type_call = t#generic_media_type_call + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/proto_004_Pt24m4xi/lib_client/alpha_client_context.ml b/src/proto_004_Pt24m4xi/lib_client/alpha_client_context.ml index 9900abb9045d..495d0bc84c36 100644 --- a/src/proto_004_Pt24m4xi/lib_client/alpha_client_context.ml +++ b/src/proto_004_Pt24m4xi/lib_client/alpha_client_context.ml @@ -43,6 +43,8 @@ class wrap_rpc_context (t : RPC_context.json) : rpc_context = method generic_json_call = t#generic_json_call + method generic_media_type_call = t#generic_media_type_call + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/proto_005_PsBabyM1/lib_client/protocol_client_context.ml b/src/proto_005_PsBabyM1/lib_client/protocol_client_context.ml index 448bcd2d6105..1d3a15e9fe03 100644 --- a/src/proto_005_PsBabyM1/lib_client/protocol_client_context.ml +++ b/src/proto_005_PsBabyM1/lib_client/protocol_client_context.ml @@ -50,6 +50,8 @@ class wrap_rpc_context (t : RPC_context.json) : rpc_context = method generic_json_call = t#generic_json_call + method generic_media_type_call = t#generic_media_type_call + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/proto_006_PsCARTHA/lib_client/protocol_client_context.ml b/src/proto_006_PsCARTHA/lib_client/protocol_client_context.ml index 437387a9fb86..c4633362bed3 100644 --- a/src/proto_006_PsCARTHA/lib_client/protocol_client_context.ml +++ b/src/proto_006_PsCARTHA/lib_client/protocol_client_context.ml @@ -50,6 +50,8 @@ class wrap_rpc_context (t : RPC_context.json) : rpc_context = method generic_json_call = t#generic_json_call + method generic_media_type_call = t#generic_media_type_call + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/proto_007_PsDELPH1/lib_client/protocol_client_context.ml b/src/proto_007_PsDELPH1/lib_client/protocol_client_context.ml index 40d5c90dcf86..cd41a489c7e0 100644 --- a/src/proto_007_PsDELPH1/lib_client/protocol_client_context.ml +++ b/src/proto_007_PsDELPH1/lib_client/protocol_client_context.ml @@ -53,6 +53,8 @@ class wrap_rpc_context (t : RPC_context.json) : rpc_context = method generic_json_call = t#generic_json_call + method generic_media_type_call = t#generic_media_type_call + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/proto_008_PtEdo2Zk/lib_client/protocol_client_context.ml b/src/proto_008_PtEdo2Zk/lib_client/protocol_client_context.ml index 9aab0e521407..06c2f09263e8 100644 --- a/src/proto_008_PtEdo2Zk/lib_client/protocol_client_context.ml +++ b/src/proto_008_PtEdo2Zk/lib_client/protocol_client_context.ml @@ -53,6 +53,8 @@ class wrap_rpc_context (t : RPC_context.json) : rpc_context = method generic_json_call = t#generic_json_call + method generic_media_type_call = t#generic_media_type_call + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/proto_009_PsFLoren/lib_client/protocol_client_context.ml b/src/proto_009_PsFLoren/lib_client/protocol_client_context.ml index 2063d712c002..3ad8de5d6050 100644 --- a/src/proto_009_PsFLoren/lib_client/protocol_client_context.ml +++ b/src/proto_009_PsFLoren/lib_client/protocol_client_context.ml @@ -53,6 +53,8 @@ class wrap_rpc_context (t : RPC_context.json) : rpc_context = method generic_json_call = t#generic_json_call + method generic_media_type_call = t#generic_media_type_call + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/proto_010_PtGRANAD/lib_client/protocol_client_context.ml b/src/proto_010_PtGRANAD/lib_client/protocol_client_context.ml index 2063d712c002..3ad8de5d6050 100644 --- a/src/proto_010_PtGRANAD/lib_client/protocol_client_context.ml +++ b/src/proto_010_PtGRANAD/lib_client/protocol_client_context.ml @@ -53,6 +53,8 @@ class wrap_rpc_context (t : RPC_context.json) : rpc_context = method generic_json_call = t#generic_json_call + method generic_media_type_call = t#generic_media_type_call + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/proto_011_PtHangz2/lib_client/protocol_client_context.ml b/src/proto_011_PtHangz2/lib_client/protocol_client_context.ml index 2063d712c002..3ad8de5d6050 100644 --- a/src/proto_011_PtHangz2/lib_client/protocol_client_context.ml +++ b/src/proto_011_PtHangz2/lib_client/protocol_client_context.ml @@ -53,6 +53,8 @@ class wrap_rpc_context (t : RPC_context.json) : rpc_context = method generic_json_call = t#generic_json_call + method generic_media_type_call = t#generic_media_type_call + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/proto_alpha/lib_client/protocol_client_context.ml b/src/proto_alpha/lib_client/protocol_client_context.ml index 904d95821a48..1d2fc145eeb6 100644 --- a/src/proto_alpha/lib_client/protocol_client_context.ml +++ b/src/proto_alpha/lib_client/protocol_client_context.ml @@ -53,6 +53,8 @@ class wrap_rpc_context (t : RPC_context.json) : rpc_context = method generic_json_call = t#generic_json_call + method generic_media_type_call = t#generic_media_type_call + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> diff --git a/src/proto_alpha/lib_delegate/test/mockup_simulator/faked_client_context.ml b/src/proto_alpha/lib_delegate/test/mockup_simulator/faked_client_context.ml index 99ef6799821a..d5e58d187999 100644 --- a/src/proto_alpha/lib_delegate/test/mockup_simulator/faked_client_context.ml +++ b/src/proto_alpha/lib_delegate/test/mockup_simulator/faked_client_context.ml @@ -58,6 +58,9 @@ class faked_ctxt (hooks : Faked_services.hooks) (chain_id : Chain_id.t) : method generic_json_call meth ?body uri = local_ctxt#generic_json_call meth ?body uri + method generic_media_type_call meth ?body uri = + local_ctxt#generic_media_type_call meth ?body uri + method call_service : 'm 'p 'q 'i 'o. (([< Resto.meth] as 'm), unit, 'p, 'q, 'i, 'o) RPC_service.t -> -- GitLab From a98c64f20ce7838b0dc1754a976a53079dbad72a Mon Sep 17 00:00:00 2001 From: Killian Delarue Date: Thu, 9 Dec 2021 19:05:53 +0100 Subject: [PATCH 2/3] bin_client: change generic_json_call to generic_media_type_call --- CHANGES.rst | 1 + src/bin_client/client_rpc_commands.ml | 48 ++++++++++++++++++--------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index eaf9e744f2b3..2aed0edd8f1f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -135,6 +135,7 @@ Client code. These options can be used to override the values normally returned by the ``NOW`` and ``LEVEL`` instructions. +- The output of ``tezos-client``'s RPC commands now uses the format specified by the ``--media-type``. Baker / Endorser / Accuser -------------------------- diff --git a/src/bin_client/client_rpc_commands.ml b/src/bin_client/client_rpc_commands.ml index 0a7efd00eba1..90d4664cd0cd 100644 --- a/src/bin_client/client_rpc_commands.ml +++ b/src/bin_client/client_rpc_commands.ml @@ -377,25 +377,41 @@ let fill_in ?(show_optionals = true) schema = | Any | Object {properties = []; _} -> Lwt.return_ok (`O []) | _ -> editor_fill_in ~show_optionals schema -let display_answer (cctxt : #Client_context.full) = function - | `Ok json -> cctxt#answer "%a" Json_repr.(pp (module Ezjsonm)) json - | `Not_found _ -> cctxt#error "No service found at this URL\n%!" - | `Gone _ -> - cctxt#error - "Requested data concerns a pruned block and target resource is no \ - longer available\n\ - %!" - | `Unauthorized None -> - cctxt#error "@[[HTTP 403] Access denied to: %a@]@." Uri.pp cctxt#base - | `Error (Some json) -> +let display_answer (cctxt : #Client_context.full) : + RPC_context.generic_call_result -> unit Lwt.t = function + | `Json (`Ok json) -> cctxt#answer "%a" Json_repr.(pp (module Ezjsonm)) json + | `Binary (`Ok binary) -> cctxt#answer "%a" Hex.pp (Hex.of_string binary) + | `Json (`Error (Some error)) -> cctxt#error "@[Command failed: @[%a@]@]@." (Format.pp_print_list Error_monad.pp) (Data_encoding.Json.destruct (Data_encoding.list Error_monad.error_encoding) - json) - | `Error None | `Unauthorized _ | `Forbidden _ | `Conflict _ -> - cctxt#error "Unexpected server answer\n%!" + error) + | `Binary (`Error (Some error)) -> ( + match Data_encoding.Binary.of_string Error_monad.trace_encoding error with + | Ok trace -> + cctxt#error + "@[Command failed: @[%a@]@]@." + Error_monad.pp_print_trace + trace + | Error msg -> + cctxt#error + "@[Error whilst decoding the server response: @[%a@]@]@." + Data_encoding.Binary.pp_read_error + msg) + | `Json (`Not_found _) | `Binary (`Not_found _) | `Other (_, `Not_found _) -> + cctxt#error "No service found at this URL\n%!" + | `Json (`Gone _) | `Binary (`Gone _) | `Other (_, `Gone _) -> + cctxt#error + "Requested data concerns a pruned block and target resource is no \ + longer available\n\ + %!" + | `Json (`Unauthorized _) + | `Binary (`Unauthorized _) + | `Other (_, `Unauthorized _) -> + cctxt#error "@[[HTTP 403] Access denied to: %a@]@." Uri.pp cctxt#base + | _ -> cctxt#error "Unexpected server answer\n%!" let call ?body meth raw_url (cctxt : #Client_context.full) = let uri = Uri.of_string raw_url in @@ -411,7 +427,7 @@ let call ?body meth raw_url (cctxt : #Client_context.full) = cctxt#warning "This URL did not expect a JSON input but one was provided\n%!") >>= fun () -> - cctxt#generic_json_call meth ?body uri >>=? fun answer -> + cctxt#generic_media_type_call meth ?body uri >>=? fun answer -> display_answer cctxt answer >|= ok | Some {input = Some input; _} -> ( (match body with @@ -420,7 +436,7 @@ let call ?body meth raw_url (cctxt : #Client_context.full) = >>= function | Error msg -> cctxt#error "%s" msg | Ok body -> - cctxt#generic_json_call meth ~body uri >>=? fun answer -> + cctxt#generic_media_type_call meth ~body uri >>=? fun answer -> display_answer cctxt answer >|= ok)) | _ -> cctxt#error "No service found at this URL\n%!" -- GitLab From c016c488e712184f9399c084f7db6da02337c676 Mon Sep 17 00:00:00 2001 From: Killian Delarue Date: Mon, 22 Nov 2021 14:03:42 +0100 Subject: [PATCH 3/3] tezt: check client binary mode answer --- tezt/_regressions/rpc/binary_rpc.out | 12 ++++++++ tezt/tests/RPC_test.ml | 41 ++++++++++++++++++---------- 2 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 tezt/_regressions/rpc/binary_rpc.out diff --git a/tezt/_regressions/rpc/binary_rpc.out b/tezt/_regressions/rpc/binary_rpc.out new file mode 100644 index 000000000000..828826b25d6c --- /dev/null +++ b/tezt/_regressions/rpc/binary_rpc.out @@ -0,0 +1,12 @@ +tezt/_regressions/rpc/binary_rpc.out + +./tezos-client --media-type json rpc get /chains/main/blocks/head/header/shell +{ "level": 0, "proto": 0, + "predecessor": "BLockGenesisGenesisGenesisGenesisGenesisf79b5d1CoW2", + "timestamp": "[TIMESTAMP]", "validation_pass": 0, + "operations_hash": "LLoZS2LW3rEi7KYU4ouBQtorua37aWWCtpDmv1n2x3xoKi6sVXLWp", + "fitness": [], + "context": "CoVBYdAGWBoDTkiVXJEGX6FQvDN1oGCPJu8STMvaTYdeh7N3KGTz" } + +./tezos-client --media-type binary rpc get /chains/main/blocks/head/header/shell +00000000008fcf233671b6a04fcf679d2a381c2544ea6c1ea29ba6157776ed8424c7ccd00b000000005b37aac4000e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a80000000046eda4bd10fa370d99881852b74be8a0e2f407afb9d4722fefffcfbe76878a66 diff --git a/tezt/tests/RPC_test.ml b/tezt/tests/RPC_test.ml index dac94d83bf90..cd052b163e26 100644 --- a/tezt/tests/RPC_test.ml +++ b/tezt/tests/RPC_test.ml @@ -916,19 +916,30 @@ let test_blacklist address () = let* _success_resp = Client.rpc GET ["network"; "connections"] client in unit -(* Test RPC with binary mode. *) -let start_binary address = - let node = Node.create ~rpc_host:address [] in +let binary_regression_test () = + let node = Node.create ~rpc_host:"127.0.0.1" [] in let endpoint = Client.(Node node) in let* () = Node.config_init node [] in let* () = Node.identity_generate node in let* () = Node.run node [] in - Client.init ~endpoint ~media_type:Binary () - -let test_client_binary_mode address () = - let* client = start_binary address in - let* _success_resp = Client.rpc GET ["network"; "connections"] client in - unit + let* json_client = Client.init ~endpoint ~media_type:Json () in + let* binary_client = Client.init ~endpoint ~media_type:Binary () in + let call_rpc client = + Client.spawn_rpc + ~hooks + GET + ["chains"; "main"; "blocks"; "head"; "header"; "shell"] + client + |> Process.check_and_read_stdout + in + let* json_result = call_rpc json_client in + let* binary_result = call_rpc binary_client in + let* decoded_binary_result = + Codec.decode ~name:"block_header.shell" binary_result + in + if JSON.unannotate decoded_binary_result = Ezjsonm.from_string json_result + then Lwt.return_unit + else Test.fail "Unexpected binary answer" let test_no_service_at_valid_prefix address () = let node = Node.create ~rpc_host:address [] in @@ -950,6 +961,13 @@ let test_no_service_at_valid_prefix address () = unit let register () = + Regression.register + ~__FILE__ + ~title:"Binary RPC regression tests" + ~tags:["rpc"; "regression"; "binary"] + ~output_file:"binary_rpc" + ~regression_output_path:"tezt/_regressions/rpc/" + binary_regression_test ; let alpha_consensus_threshold = [(["consensus_threshold"], Some "0")] in let alpha_overrides = Some alpha_consensus_threshold in let register_alpha test_mode_tag = @@ -1047,11 +1065,6 @@ let register () = ~title:(mk_title "blacklist" addr) ~tags:["rpc"; "acl"] (test_blacklist addr) ; - Test.register - ~__FILE__ - ~title:(mk_title "client binary mode" addr) - ~tags:["rpc"; "client"; "binary"] - (test_client_binary_mode addr) ; Test.register ~__FILE__ ~title:(mk_title "no_service_at_valid_prefix" addr) -- GitLab