2016-09-08 21:13:10 +04:00
|
|
|
(**************************************************************************)
|
|
|
|
(* *)
|
2018-02-06 00:17:03 +04:00
|
|
|
(* Copyright (c) 2014 - 2018. *)
|
2016-09-08 21:13:10 +04:00
|
|
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
|
|
|
(* *)
|
|
|
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
|
|
|
(* *)
|
|
|
|
(**************************************************************************)
|
|
|
|
|
|
|
|
(* Tezos Command line interface - Generic JSON RPC interface *)
|
|
|
|
|
2017-04-05 01:35:41 +04:00
|
|
|
open Lwt.Infix
|
2016-09-08 21:13:10 +04:00
|
|
|
open Cli_entries
|
|
|
|
open Json_schema
|
|
|
|
|
|
|
|
(*-- Assisted, schema directed input fill in --------------------------------*)
|
|
|
|
|
|
|
|
exception Unsupported_construct
|
|
|
|
|
|
|
|
type input = {
|
|
|
|
int : int -> int -> string option -> string list -> int Lwt.t ;
|
|
|
|
float : string option -> string list -> float Lwt.t ;
|
|
|
|
string : string option -> string list -> string Lwt.t ;
|
|
|
|
bool : string option -> string list -> bool Lwt.t ;
|
|
|
|
continue : string option -> string list -> bool Lwt.t ;
|
|
|
|
display : string -> unit Lwt.t ;
|
|
|
|
}
|
|
|
|
|
|
|
|
(* generic JSON generation from a schema with callback for random or
|
|
|
|
interactive filling *)
|
2017-09-14 19:17:45 +04:00
|
|
|
let fill_in ?(show_optionals=true) input schema =
|
2016-09-08 21:13:10 +04:00
|
|
|
let rec element path { title ; kind }=
|
|
|
|
match kind with
|
2016-11-08 19:35:53 +04:00
|
|
|
| Integer { minimum ; maximum } ->
|
|
|
|
let minimum =
|
|
|
|
match minimum with
|
|
|
|
| None -> min_int
|
|
|
|
| Some (m, `Inclusive) -> int_of_float m
|
|
|
|
| Some (m, `Exclusive) -> int_of_float m + 1 in
|
|
|
|
let maximum =
|
|
|
|
match maximum with
|
|
|
|
| None -> max_int
|
|
|
|
| Some (m, `Inclusive) -> int_of_float m
|
|
|
|
| Some (m, `Exclusive) -> int_of_float m - 1 in
|
|
|
|
input.int minimum maximum title path >>= fun i ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return (`Float (float i))
|
2016-11-08 19:35:53 +04:00
|
|
|
| Number _ ->
|
2016-09-08 21:13:10 +04:00
|
|
|
input.float title path >>= fun f ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return (`Float f)
|
2016-09-08 21:13:10 +04:00
|
|
|
| Boolean ->
|
|
|
|
input.bool title path >>= fun f ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return (`Bool f)
|
2016-09-08 21:13:10 +04:00
|
|
|
| String _ ->
|
|
|
|
input.string title path >>= fun f ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return (`String f)
|
2016-09-08 21:13:10 +04:00
|
|
|
| Combine ((One_of | Any_of), elts) ->
|
|
|
|
let nb = List.length elts in
|
|
|
|
input.int 0 (nb - 1) (Some "Select the schema to follow") path >>= fun n ->
|
|
|
|
element path (List.nth elts n)
|
2017-04-05 01:35:41 +04:00
|
|
|
| Combine ((All_of | Not), _) -> Lwt.fail Unsupported_construct
|
2016-09-08 21:13:10 +04:00
|
|
|
| Def_ref name ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return (`String (Json_query.json_pointer_of_path name))
|
2016-09-08 21:13:10 +04:00
|
|
|
| Id_ref _ | Ext_ref _ ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.fail Unsupported_construct
|
2016-09-08 21:13:10 +04:00
|
|
|
| Array (elts, _) ->
|
|
|
|
let rec fill_loop acc n ls =
|
|
|
|
match ls with
|
2017-04-05 01:35:41 +04:00
|
|
|
| [] -> Lwt.return acc
|
2016-09-08 21:13:10 +04:00
|
|
|
| elt :: elts ->
|
|
|
|
element (string_of_int n :: path) elt >>= fun json ->
|
|
|
|
fill_loop (json :: acc) (succ n) elts
|
|
|
|
in
|
|
|
|
fill_loop [] 0 elts >>= fun acc ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return (`A (List.rev acc))
|
2016-09-08 21:13:10 +04:00
|
|
|
| Object { properties } ->
|
2017-09-14 19:17:45 +04:00
|
|
|
let properties =
|
|
|
|
if show_optionals
|
|
|
|
then properties
|
|
|
|
else (List.filter (fun (_, _, b, _) -> b) properties) in
|
2016-09-08 21:13:10 +04:00
|
|
|
let rec fill_loop acc ls =
|
|
|
|
match ls with
|
2017-04-05 01:35:41 +04:00
|
|
|
| [] -> Lwt.return acc
|
2016-09-08 21:13:10 +04:00
|
|
|
| (n, elt, _, _) :: elts ->
|
|
|
|
element (n :: path) elt >>= fun json ->
|
|
|
|
fill_loop ((n, json) :: acc) elts
|
|
|
|
in
|
|
|
|
fill_loop [] properties >>= fun acc ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return (`O (List.rev acc))
|
2016-09-08 21:13:10 +04:00
|
|
|
| Monomorphic_array (elt, specs) ->
|
|
|
|
let rec fill_loop acc min n max =
|
|
|
|
if n > max then
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return acc
|
2016-09-08 21:13:10 +04:00
|
|
|
else
|
|
|
|
element (string_of_int n :: path) elt >>= fun json ->
|
2017-04-05 01:35:41 +04:00
|
|
|
(if n < min then Lwt.return true else input.continue title path) >>= function
|
2016-09-08 21:13:10 +04:00
|
|
|
| true -> fill_loop (json :: acc) min (succ n) max
|
2017-04-05 01:35:41 +04:00
|
|
|
| false -> Lwt.return (json :: acc)
|
2016-09-08 21:13:10 +04:00
|
|
|
in
|
|
|
|
let max = match specs.max_items with None -> max_int | Some m -> m in
|
|
|
|
fill_loop [] specs.min_items 0 max >>= fun acc ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return (`A (List.rev acc))
|
|
|
|
| Any -> Lwt.fail Unsupported_construct
|
|
|
|
| Dummy -> Lwt.fail Unsupported_construct
|
|
|
|
| Null -> Lwt.return `Null
|
2016-09-08 21:13:10 +04:00
|
|
|
in
|
|
|
|
element [] (Json_schema.root schema)
|
|
|
|
|
2017-09-14 19:17:45 +04:00
|
|
|
let random_fill_in ?(show_optionals=true) schema =
|
2017-04-05 01:35:41 +04:00
|
|
|
let display _ = Lwt.return () in
|
2016-11-22 14:16:56 +04:00
|
|
|
let int min max _ _ =
|
|
|
|
let max = Int64.of_int max
|
|
|
|
and min = Int64.of_int min in
|
|
|
|
let range = Int64.sub max min in
|
|
|
|
let random_int64 = Int64.add (Random.int64 range) min in
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return (Int64.to_int random_int64) in
|
|
|
|
let string _title _ = Lwt.return "" in
|
|
|
|
let float _ _ = Lwt.return (Random.float infinity) in
|
|
|
|
let bool _ _ = Lwt.return (Random.int 2 = 0) in
|
|
|
|
let continue _ _ = Lwt.return (Random.int 4 = 0) in
|
|
|
|
Lwt.catch
|
2016-09-08 21:13:10 +04:00
|
|
|
(fun () ->
|
2017-09-14 19:17:45 +04:00
|
|
|
fill_in ~show_optionals
|
2016-09-08 21:13:10 +04:00
|
|
|
{ int ; float ; string ; bool ; display ; continue }
|
|
|
|
schema >>= fun json ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return (Ok json))
|
2016-09-08 21:13:10 +04:00
|
|
|
(fun e ->
|
|
|
|
let msg = Printf.sprintf "Fill-in failed %s\n%!" (Printexc.to_string e) in
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return (Error msg))
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-09-14 19:17:45 +04:00
|
|
|
let editor_fill_in ?(show_optionals=true) schema =
|
2016-09-08 21:13:10 +04:00
|
|
|
let tmp = Filename.temp_file "tezos_rpc_call_" ".json" in
|
|
|
|
let rec init () =
|
|
|
|
(* write a temp file with instructions *)
|
2017-09-14 19:17:45 +04:00
|
|
|
random_fill_in ~show_optionals schema >>= function
|
2017-04-05 01:35:41 +04:00
|
|
|
| Error msg -> Lwt.return (Error msg)
|
2016-09-08 21:13:10 +04:00
|
|
|
| Ok json ->
|
2017-11-13 17:29:28 +04:00
|
|
|
Lwt_io.(with_file ~mode:Output tmp (fun fp ->
|
2018-02-08 13:51:01 +04:00
|
|
|
write_line fp (Data_encoding.Json.to_string json))) >>= fun () ->
|
2016-09-08 21:13:10 +04:00
|
|
|
edit ()
|
|
|
|
and edit () =
|
|
|
|
(* launch the user's editor on it *)
|
|
|
|
let editor_cmd =
|
|
|
|
try let ed = Sys.getenv "EDITOR" in Lwt_process.shell (ed ^ " " ^ tmp)
|
|
|
|
with Not_found ->
|
2017-04-05 01:35:41 +04:00
|
|
|
try let ed = Sys.getenv "VISUAL" in Lwt_process.shell (ed ^ " " ^ tmp)
|
|
|
|
with Not_found ->
|
|
|
|
if Sys.win32 then
|
|
|
|
(* TODO: I have no idea what I'm doing here *)
|
|
|
|
("", [| "notepad.exe" ; tmp |])
|
|
|
|
else
|
|
|
|
(* TODO: vi on MacOSX ? *)
|
|
|
|
("", [| "nano" ; tmp |])
|
2016-09-08 21:13:10 +04:00
|
|
|
in
|
|
|
|
(Lwt_process.open_process_none editor_cmd) # status >>= function
|
|
|
|
| Unix.WEXITED 0 ->
|
|
|
|
reread () >>= fun json ->
|
|
|
|
delete () >>= fun () ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return json
|
2016-09-08 21:13:10 +04:00
|
|
|
| Unix.WSIGNALED x | Unix.WSTOPPED x | Unix.WEXITED x ->
|
|
|
|
let msg = Printf.sprintf "FAILED %d \n%!" x in
|
|
|
|
delete () >>= fun () ->
|
2017-04-05 01:35:41 +04:00
|
|
|
Lwt.return (Error msg)
|
2016-09-08 21:13:10 +04:00
|
|
|
and reread () =
|
|
|
|
(* finally reread the file *)
|
2017-11-13 17:29:28 +04:00
|
|
|
Lwt_io.(with_file ~mode:Input tmp (fun fp -> read fp)) >>= fun text ->
|
2018-02-08 13:51:01 +04:00
|
|
|
match Data_encoding.Json.from_string text with
|
2017-04-05 01:35:41 +04:00
|
|
|
| Ok r -> Lwt.return (Ok r)
|
|
|
|
| Error msg -> Lwt.return (Error (Printf.sprintf "bad input: %s" msg))
|
2016-09-08 21:13:10 +04:00
|
|
|
and delete () =
|
|
|
|
(* and delete the temp file *)
|
|
|
|
Lwt_unix.unlink tmp
|
|
|
|
in
|
|
|
|
init ()
|
|
|
|
|
|
|
|
(*-- Nice list display ------------------------------------------------------*)
|
|
|
|
|
|
|
|
let rec count =
|
2017-12-09 06:51:58 +04:00
|
|
|
let open RPC_description in
|
2016-09-08 21:13:10 +04:00
|
|
|
function
|
2017-11-27 09:13:12 +04:00
|
|
|
| Empty -> 0
|
2016-09-08 21:13:10 +04:00
|
|
|
| Dynamic _ -> 1
|
2017-11-27 09:13:12 +04:00
|
|
|
| Static { services ; subdirs } ->
|
2017-12-09 06:51:58 +04:00
|
|
|
let service = RPC_service.MethMap.cardinal services in
|
2016-09-08 21:13:10 +04:00
|
|
|
let subdirs =
|
|
|
|
match subdirs with
|
|
|
|
| None -> 0
|
|
|
|
| Some (Suffixes subdirs) ->
|
2017-12-09 06:51:58 +04:00
|
|
|
Resto.StringMap.fold (fun _ t r -> r + count t) subdirs 0
|
2016-09-08 21:13:10 +04:00
|
|
|
| Some (Arg (_, subdir)) -> count subdir in
|
|
|
|
service + subdirs
|
|
|
|
|
|
|
|
(*-- Commands ---------------------------------------------------------------*)
|
|
|
|
|
2017-11-07 20:38:11 +04:00
|
|
|
let list url (cctxt : Client_commands.full_context) =
|
2017-11-27 09:13:12 +04:00
|
|
|
let args = String.split '/' url in
|
2017-11-07 20:38:11 +04:00
|
|
|
Client_node_rpcs.describe cctxt
|
2017-04-05 01:35:41 +04:00
|
|
|
~recurse:true args >>=? fun tree ->
|
2017-12-09 06:51:58 +04:00
|
|
|
let open RPC_description in
|
2016-09-08 21:13:10 +04:00
|
|
|
let collected_args = ref [] in
|
|
|
|
let collect arg =
|
2017-12-09 06:51:58 +04:00
|
|
|
if not (arg.RPC_arg.descr <> None && List.mem arg !collected_args) then
|
2016-09-08 21:13:10 +04:00
|
|
|
collected_args := arg :: !collected_args in
|
|
|
|
let display_paragraph ppf description =
|
|
|
|
Format.fprintf ppf "@, @[%a@]"
|
|
|
|
(fun ppf words -> List.iter (Format.fprintf ppf "%s@ ") words)
|
2017-11-27 09:13:12 +04:00
|
|
|
(String.split ' ' description)
|
2016-09-08 21:13:10 +04:00
|
|
|
in
|
|
|
|
let display_arg ppf arg =
|
2017-12-09 06:51:58 +04:00
|
|
|
match arg.RPC_arg.descr with
|
|
|
|
| None -> Format.fprintf ppf "%s" arg.RPC_arg.name
|
2016-09-08 21:13:10 +04:00
|
|
|
| Some descr ->
|
2017-12-09 06:51:58 +04:00
|
|
|
Format.fprintf ppf "<%s>%a" arg.RPC_arg.name display_paragraph descr
|
2016-09-08 21:13:10 +04:00
|
|
|
in
|
|
|
|
let display_service ppf (_path, tpath, service) =
|
2017-11-27 09:13:12 +04:00
|
|
|
Format.fprintf ppf "- %s /%s"
|
2017-12-09 06:51:58 +04:00
|
|
|
(RPC_service.string_of_meth service.meth)
|
2017-11-27 09:13:12 +04:00
|
|
|
(String.concat "/" tpath) ;
|
2016-09-08 21:13:10 +04:00
|
|
|
match service.description with
|
|
|
|
| None | Some "" -> ()
|
|
|
|
| Some description -> display_paragraph ppf description
|
|
|
|
in
|
2017-11-27 09:13:12 +04:00
|
|
|
let display_services ppf (_path, tpath, services) =
|
|
|
|
Format.pp_print_list
|
|
|
|
(fun ppf (_,s) -> display_service ppf (_path, tpath, s))
|
|
|
|
ppf
|
2017-12-09 06:51:58 +04:00
|
|
|
(RPC_service.MethMap.bindings services)
|
2017-11-27 09:13:12 +04:00
|
|
|
in
|
2016-09-08 21:13:10 +04:00
|
|
|
let rec display ppf (path, tpath, tree) =
|
|
|
|
match tree with
|
|
|
|
| Dynamic description -> begin
|
|
|
|
Format.fprintf ppf "- /%s <dynamic>" (String.concat "/" tpath) ;
|
|
|
|
match description with
|
|
|
|
| None | Some "" -> ()
|
|
|
|
| Some description -> display_paragraph ppf description
|
|
|
|
end
|
2017-11-27 09:13:12 +04:00
|
|
|
| Empty -> ()
|
|
|
|
| Static { services ; subdirs = None } ->
|
|
|
|
display_services ppf (path, tpath, services)
|
|
|
|
| Static { services ; subdirs = Some (Suffixes subdirs) } -> begin
|
2017-12-09 06:51:58 +04:00
|
|
|
match RPC_service.MethMap.cardinal services, Resto.StringMap.bindings subdirs with
|
2017-11-27 09:13:12 +04:00
|
|
|
| 0, [] -> ()
|
|
|
|
| 0, [ n, solo ] ->
|
2016-09-08 21:13:10 +04:00
|
|
|
display ppf (path @ [ n ], tpath @ [ n ], solo)
|
2017-11-27 09:13:12 +04:00
|
|
|
| _, items when count tree >= 3 && path <> [] ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Format.fprintf ppf "@[<v 2>+ %s/@,%a@]"
|
|
|
|
(String.concat "/" path) (display_list tpath) items
|
2017-11-27 09:13:12 +04:00
|
|
|
| _, items when count tree >= 3 && path <> [] ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Format.fprintf ppf "@[<v 2>+ %s@,%a@,%a@]"
|
|
|
|
(String.concat "/" path)
|
2017-11-27 09:13:12 +04:00
|
|
|
display_services (path, tpath, services)
|
2016-09-08 21:13:10 +04:00
|
|
|
(display_list tpath) items
|
2017-11-27 09:13:12 +04:00
|
|
|
| 0, (n, t) :: items ->
|
2016-09-08 21:13:10 +04:00
|
|
|
Format.fprintf ppf "%a"
|
|
|
|
display (path @ [ n ], tpath @ [ n ], t) ;
|
|
|
|
List.iter
|
|
|
|
(fun (n, t) ->
|
|
|
|
Format.fprintf ppf "@,%a"
|
|
|
|
display (path @ [ n ], tpath @ [ n ], t))
|
|
|
|
items
|
2017-11-27 09:13:12 +04:00
|
|
|
| _, items ->
|
|
|
|
display_services ppf (path, tpath, services) ;
|
2016-09-08 21:13:10 +04:00
|
|
|
List.iter
|
|
|
|
(fun (n, t) ->
|
|
|
|
Format.fprintf ppf "@,%a"
|
|
|
|
display (path @ [ n ], tpath @ [ n ], t))
|
|
|
|
items
|
|
|
|
end
|
2017-11-27 09:13:12 +04:00
|
|
|
| Static { services ; subdirs = Some (Arg (arg, solo)) }
|
2017-12-09 06:51:58 +04:00
|
|
|
when RPC_service.MethMap.cardinal services = 0 ->
|
2016-09-08 21:13:10 +04:00
|
|
|
collect arg ;
|
2017-12-09 06:51:58 +04:00
|
|
|
let name = Printf.sprintf "<%s>" arg.RPC_arg.name in
|
2016-09-08 21:13:10 +04:00
|
|
|
display ppf (path @ [ name ], tpath @ [ name ], solo)
|
2017-11-27 09:13:12 +04:00
|
|
|
| Static { services;
|
2016-09-08 21:13:10 +04:00
|
|
|
subdirs = Some (Arg (arg, solo)) } ->
|
|
|
|
collect arg ;
|
2017-11-27 09:13:12 +04:00
|
|
|
display_services ppf (path, tpath, services) ;
|
2016-09-08 21:13:10 +04:00
|
|
|
Format.fprintf ppf "@," ;
|
2017-12-09 06:51:58 +04:00
|
|
|
let name = Printf.sprintf "<%s>" arg.RPC_arg.name in
|
2016-09-08 21:13:10 +04:00
|
|
|
display ppf (path @ [ name ], tpath @ [ name ], solo)
|
|
|
|
and display_list tpath =
|
|
|
|
Format.pp_print_list
|
|
|
|
(fun ppf (n,t) -> display ppf ([ n ], tpath @ [ n ], t))
|
|
|
|
in
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#message "@ @[<v 2>Available services:@ @ %a@]@."
|
2016-11-22 20:28:25 +04:00
|
|
|
display (args, args, tree) >>= fun () ->
|
2017-04-05 01:35:41 +04:00
|
|
|
if !collected_args <> [] then begin
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#message "@,@[<v 2>Dynamic parameter description:@ @ %a@]@."
|
2017-04-05 01:35:41 +04:00
|
|
|
(Format.pp_print_list display_arg) !collected_args >>= fun () ->
|
|
|
|
return ()
|
|
|
|
end else return ()
|
2016-09-08 21:13:10 +04:00
|
|
|
|
|
|
|
|
2017-11-07 20:38:11 +04:00
|
|
|
let schema url (cctxt : Client_commands.full_context) =
|
2017-11-27 09:13:12 +04:00
|
|
|
let args = String.split '/' url in
|
2017-12-09 06:51:58 +04:00
|
|
|
let open RPC_description in
|
2017-11-07 20:38:11 +04:00
|
|
|
Client_node_rpcs.describe cctxt ~recurse:false args >>=? function
|
2017-11-27 09:13:12 +04:00
|
|
|
| Static { services } -> begin
|
2017-12-09 06:51:58 +04:00
|
|
|
match RPC_service.MethMap.find `POST services with
|
2017-11-27 09:13:12 +04:00
|
|
|
| exception Not_found ->
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#message
|
2017-11-27 09:13:12 +04:00
|
|
|
"No service found at this URL (but this is a valid prefix)\n%!" >>= fun () ->
|
|
|
|
return ()
|
|
|
|
| { input = Some input ; output } ->
|
|
|
|
let json = `O [ "input", Json_schema.to_json input ;
|
|
|
|
"output", Json_schema.to_json output ] in
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#message "%a" Json_repr.(pp (module Ezjsonm)) json >>= fun () ->
|
2017-11-27 09:13:12 +04:00
|
|
|
return ()
|
|
|
|
| { input = None ; output } ->
|
|
|
|
let json = `O [ "output", Json_schema.to_json output ] in
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#message "%a" Json_repr.(pp (module Ezjsonm)) json >>= fun () ->
|
2017-11-27 09:13:12 +04:00
|
|
|
return ()
|
|
|
|
end
|
2017-04-07 23:21:20 +04:00
|
|
|
| _ ->
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#message
|
2017-04-07 23:21:20 +04:00
|
|
|
"No service found at this URL (but this is a valid prefix)\n%!" >>= fun () ->
|
|
|
|
return ()
|
|
|
|
|
2017-11-07 20:38:11 +04:00
|
|
|
let format url (cctxt : #Client_commands.logging_rpcs) =
|
2017-11-27 09:13:12 +04:00
|
|
|
let args = String.split '/' url in
|
2017-12-09 06:51:58 +04:00
|
|
|
let open RPC_description in
|
2017-11-07 20:38:11 +04:00
|
|
|
Client_node_rpcs.describe cctxt ~recurse:false args >>=? function
|
2017-11-27 09:13:12 +04:00
|
|
|
| Static { services } -> begin
|
2017-12-09 06:51:58 +04:00
|
|
|
match RPC_service.MethMap.find `POST services with
|
2017-11-27 09:13:12 +04:00
|
|
|
| exception Not_found ->
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#message
|
2017-11-27 09:13:12 +04:00
|
|
|
"No service found at this URL (but this is a valid prefix)\n%!" >>= fun () ->
|
|
|
|
return ()
|
|
|
|
| { input = Some input ; output } ->
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#message
|
2017-11-27 09:13:12 +04:00
|
|
|
"@[<v 0>\
|
|
|
|
@[<v 2>Input format:@,%a@]@,\
|
|
|
|
@[<v 2>Output format:@,%a@]@,\
|
|
|
|
@]"
|
|
|
|
Json_schema.pp input
|
|
|
|
Json_schema.pp output >>= fun () ->
|
|
|
|
return ()
|
|
|
|
| { input = None ; output } ->
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#message
|
2017-11-27 09:13:12 +04:00
|
|
|
"@[<v 0>\
|
|
|
|
@[<v 2>Output format:@,%a@]@,\
|
|
|
|
@]"
|
|
|
|
Json_schema.pp output >>= fun () ->
|
|
|
|
return ()
|
|
|
|
end
|
2016-09-08 21:13:10 +04:00
|
|
|
| _ ->
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#message
|
2017-04-05 01:35:41 +04:00
|
|
|
"No service found at this URL (but this is a valid prefix)\n%!" >>= fun () ->
|
|
|
|
return ()
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-09-14 19:17:45 +04:00
|
|
|
let fill_in ?(show_optionals=true) schema =
|
2016-09-08 21:13:10 +04:00
|
|
|
let open Json_schema in
|
|
|
|
match (root schema).kind with
|
|
|
|
| Null -> Lwt.return (Ok `Null)
|
|
|
|
| Any | Object { properties = [] } -> Lwt.return (Ok (`O []))
|
2017-09-14 19:17:45 +04:00
|
|
|
| _ -> editor_fill_in ~show_optionals schema
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-12-09 01:08:29 +04:00
|
|
|
let display_answer (cctxt : #Client_commands.full_context) = function
|
|
|
|
| `Ok json ->
|
|
|
|
cctxt#message "%a"
|
|
|
|
Json_repr.(pp (module Ezjsonm)) json >>= fun () ->
|
|
|
|
return ()
|
|
|
|
| `Not_found _ ->
|
|
|
|
cctxt#message "No service found at this URL\n%!" >>= fun () ->
|
|
|
|
return ()
|
|
|
|
| `Unauthorized _ | `Error _ | `Forbidden _ | `Conflict _ ->
|
|
|
|
cctxt#message "Unexpected server answer\n%!" >>= fun () ->
|
|
|
|
return ()
|
|
|
|
|
|
|
|
let call raw_url (cctxt : #Client_commands.full_context) =
|
|
|
|
let uri = Uri.of_string raw_url in
|
|
|
|
let args = String.split_path (Uri.path uri) in
|
2017-11-07 20:38:11 +04:00
|
|
|
Client_node_rpcs.describe cctxt ~recurse:false args >>=? function
|
2017-11-27 09:13:12 +04:00
|
|
|
| Static { services } -> begin
|
2017-12-09 06:51:58 +04:00
|
|
|
match RPC_service.MethMap.find `POST services with
|
2017-11-27 09:13:12 +04:00
|
|
|
| exception Not_found ->
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#message
|
2017-11-27 09:13:12 +04:00
|
|
|
"No service found at this URL (but this is a valid prefix)\n%!" >>= fun () ->
|
2017-04-05 01:35:41 +04:00
|
|
|
return ()
|
2017-12-09 01:08:29 +04:00
|
|
|
| { input = None } ->
|
|
|
|
cctxt#generic_json_call `POST uri >>=?
|
|
|
|
display_answer cctxt
|
2017-11-27 09:13:12 +04:00
|
|
|
| { input = Some input } ->
|
2017-09-14 19:17:45 +04:00
|
|
|
fill_in ~show_optionals:false input >>= function
|
2017-11-27 09:13:12 +04:00
|
|
|
| Error msg ->
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#error "%s" msg >>= fun () ->
|
2017-11-27 09:13:12 +04:00
|
|
|
return ()
|
|
|
|
| Ok json ->
|
2017-12-09 01:08:29 +04:00
|
|
|
cctxt#generic_json_call `POST ~body:json uri >>=?
|
|
|
|
display_answer cctxt
|
2016-09-08 21:13:10 +04:00
|
|
|
end
|
|
|
|
| _ ->
|
2017-12-09 01:08:29 +04:00
|
|
|
cctxt#message "No service found at this URL\n%!" >>= fun () ->
|
2017-04-05 01:35:41 +04:00
|
|
|
return ()
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2017-12-09 01:08:29 +04:00
|
|
|
let call_with_json raw_url json (cctxt: Client_commands.full_context) =
|
|
|
|
let uri = Uri.of_string raw_url in
|
2018-02-08 13:51:01 +04:00
|
|
|
match Data_encoding.Json.from_string json with
|
2017-02-15 18:15:24 +04:00
|
|
|
| Error err ->
|
2017-11-07 20:38:11 +04:00
|
|
|
cctxt#error
|
2017-04-05 01:35:41 +04:00
|
|
|
"Failed to parse the provided json: %s\n%!"
|
2017-02-15 18:15:24 +04:00
|
|
|
err
|
2017-12-09 01:08:29 +04:00
|
|
|
| Ok body ->
|
|
|
|
cctxt#generic_json_call `POST ~body uri >>=?
|
|
|
|
display_answer cctxt
|
2017-02-15 18:15:24 +04:00
|
|
|
|
2017-09-12 19:10:19 +04:00
|
|
|
let call_with_file_or_json url maybe_file (cctxt: Client_commands.full_context) =
|
|
|
|
begin
|
|
|
|
match TzString.split ':' ~limit:1 maybe_file with
|
|
|
|
| [ "file" ; filename] ->
|
|
|
|
(* Mostly copied from src/client/client_aliases.ml *)
|
|
|
|
Lwt.catch
|
|
|
|
(fun () ->
|
|
|
|
Lwt_io.(with_file ~mode:Input filename read) >>= fun content ->
|
|
|
|
return content)
|
|
|
|
(fun exn ->
|
|
|
|
failwith
|
|
|
|
"cannot read file (%s)" (Printexc.to_string exn))
|
|
|
|
| _ -> return maybe_file
|
|
|
|
end >>=? fun json ->
|
|
|
|
call_with_json url json cctxt
|
|
|
|
|
2016-12-03 16:05:02 +04:00
|
|
|
let group =
|
|
|
|
{ Cli_entries.name = "rpc" ;
|
|
|
|
title = "Commands for the low level RPC layer" }
|
2016-09-08 21:13:10 +04:00
|
|
|
|
2016-12-03 16:05:02 +04:00
|
|
|
let commands = [
|
2017-04-05 12:22:41 +04:00
|
|
|
|
2018-01-29 13:43:07 +04:00
|
|
|
command
|
|
|
|
~desc: "List the protocol versions that this client understands."
|
2017-09-19 13:31:35 +04:00
|
|
|
no_options
|
2016-12-03 16:05:02 +04:00
|
|
|
(fixed [ "list" ; "versions" ])
|
2017-11-07 20:38:11 +04:00
|
|
|
(fun () (cctxt : Client_commands.full_context) ->
|
2016-12-03 16:05:02 +04:00
|
|
|
Lwt_list.iter_s
|
2017-11-07 20:38:11 +04:00
|
|
|
(fun (ver, _) -> cctxt#message "%a" Protocol_hash.pp_short ver)
|
2017-04-05 01:35:41 +04:00
|
|
|
(Client_commands.get_versions ()) >>= fun () ->
|
|
|
|
return ()) ;
|
2017-04-05 12:22:41 +04:00
|
|
|
|
2018-01-29 13:43:07 +04:00
|
|
|
command ~group
|
|
|
|
~desc: "List RPCs under a given URL prefix.\n\
|
|
|
|
Some parts of the RPC service hierarchy depend on parameters,\n\
|
|
|
|
they are marked by a suffix `<dynamic>`.\n\
|
|
|
|
You can list these sub-hierarchies by providing a concrete URL prefix \
|
|
|
|
whose arguments are set to a valid value."
|
2017-09-19 13:31:35 +04:00
|
|
|
no_options
|
2018-01-29 13:43:07 +04:00
|
|
|
(prefixes [ "rpc" ; "list" ] @@ string ~name:"url" ~desc: "the URL prefix" @@ stop)
|
|
|
|
(fun () -> list) ;
|
2017-04-05 12:22:41 +04:00
|
|
|
|
2018-01-29 13:43:07 +04:00
|
|
|
command ~group
|
|
|
|
~desc: "Alias to `rpc list /`."
|
2017-09-19 13:31:35 +04:00
|
|
|
no_options
|
2018-01-29 13:43:07 +04:00
|
|
|
(prefixes [ "rpc" ; "list" ] @@ stop)
|
|
|
|
(fun () -> (list "/"));
|
2017-04-05 12:22:41 +04:00
|
|
|
|
2018-01-29 13:43:07 +04:00
|
|
|
command ~group
|
|
|
|
~desc: "Get the input and output JSON schemas of an RPC."
|
2017-09-19 13:31:35 +04:00
|
|
|
no_options
|
2018-01-29 13:43:07 +04:00
|
|
|
(prefixes [ "rpc" ; "schema" ] @@ string ~name: "url" ~desc: "the RPC url" @@ stop)
|
2017-09-19 13:31:35 +04:00
|
|
|
(fun () -> schema) ;
|
2017-04-05 12:22:41 +04:00
|
|
|
|
2018-01-29 13:43:07 +04:00
|
|
|
command ~group
|
|
|
|
~desc: "Get the humanoid readable input and output formats of an RPC."
|
2017-09-19 13:31:35 +04:00
|
|
|
no_options
|
2018-01-29 13:43:07 +04:00
|
|
|
(prefixes [ "rpc" ; "format" ] @@ string ~name: "url" ~desc: "the RPC URL" @@ stop)
|
2017-09-19 13:31:35 +04:00
|
|
|
(fun () -> format) ;
|
2017-04-07 23:21:20 +04:00
|
|
|
|
2018-01-29 13:43:07 +04:00
|
|
|
command ~group
|
|
|
|
~desc: "Call an RPC.\n\
|
|
|
|
If input data is needed, a text editor will be popped up."
|
2017-09-19 13:31:35 +04:00
|
|
|
no_options
|
2018-01-29 13:43:07 +04:00
|
|
|
(prefixes [ "rpc" ; "call" ] @@ string ~name: "url" ~desc: "the RPC URL" @@ stop)
|
2017-09-19 13:31:35 +04:00
|
|
|
(fun () -> call) ;
|
2017-04-05 12:22:41 +04:00
|
|
|
|
2018-01-29 13:43:07 +04:00
|
|
|
command ~group
|
|
|
|
~desc: "Call an RPC providing input data via the command line."
|
2017-09-19 13:31:35 +04:00
|
|
|
no_options
|
2018-01-29 13:43:07 +04:00
|
|
|
(prefixes [ "rpc" ; "call" ] @@ string ~name: "url" ~desc: "the RPC URL"
|
2017-09-12 19:10:19 +04:00
|
|
|
@@ prefix "with"
|
|
|
|
@@ string ~name:"input"
|
2018-01-29 13:43:07 +04:00
|
|
|
~desc:"the raw JSON input to the RPC\n\
|
|
|
|
For instance, use `{}` to send the empty document.\n\
|
|
|
|
Alternatively, use `file:path` to read the JSON data from a file."
|
2017-09-12 19:10:19 +04:00
|
|
|
@@ stop)
|
|
|
|
(fun () -> call_with_file_or_json)
|
2017-04-05 12:22:41 +04:00
|
|
|
|
2016-12-03 16:05:02 +04:00
|
|
|
]
|