From d0f6ca8473dbd6fed982efa8620899e5b3406876 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Fri, 15 Mar 2019 11:16:03 +0100
Subject: [PATCH] [server] new request API

---
 src/plugins/server/data.ml           |  51 +++---
 src/plugins/server/doc.ml            |   2 +-
 src/plugins/server/doc.mli           |   3 +-
 src/plugins/server/kernel_ast.ml     |  15 +-
 src/plugins/server/kernel_fc.ml      |  16 +-
 src/plugins/server/kernel_project.ml |  19 +--
 src/plugins/server/request.ml        | 247 ++++++++++++++++++++++++++-
 src/plugins/server/request.mli       | 143 ++++++++++++++++
 src/plugins/server/syntax.ml         |  36 +++-
 src/plugins/server/syntax.mli        |  13 +-
 10 files changed, 480 insertions(+), 65 deletions(-)

diff --git a/src/plugins/server/data.ml b/src/plugins/server/data.ml
index 1a8849d9879..0089341017a 100644
--- a/src/plugins/server/data.ml
+++ b/src/plugins/server/data.ml
@@ -201,7 +201,7 @@ let text_page = Doc.page `Kernel ~title:"Rich Text Format" ~filename:"text.md"
 module Jtext =
 struct
   include Jany
-  let syntax = Syntax.publish text_page ~name:"text"
+  let syntax = Syntax.publish ~page:text_page ~name:"text"
       ~synopsis:Syntax.any ~descr:(Markdown.praw "Formatted text.")
 end
 
@@ -237,8 +237,14 @@ struct
       | None -> None
       | Some v ->
         let jd = D.to_json v in
-        defaults := Fmap.add name jd !defaults ; Some jd
-    in fdocs := (name , D.syntax , def , descr) :: !fdocs ;
+        defaults := Fmap.add name jd !defaults ;
+        Some (Markdown.tt @@ Json.to_string jd) in
+    fdocs := Syntax.{
+        fd_name = name ;
+        fd_syntax = D.syntax ;
+        fd_default = def ;
+        fd_descr = descr ;
+      } :: !fdocs ;
     let member r = Fmap.mem name r in
     let getter r = D.of_json (Fmap.find name r) in
     let setter r v = Fmap.add name (D.to_json v) r in
@@ -246,7 +252,12 @@ struct
 
   let option (type a) name ~descr (d : a data) : a option field =
     let module D = (val d) in
-    fdocs := (name , Syntax.option D.syntax , None , descr) :: !fdocs ;
+    fdocs := Syntax.{
+        fd_name = name ;
+        fd_syntax = D.syntax ;
+        fd_default = None ;
+        fd_descr = descr ;
+      } :: !fdocs ;
     let member r = Fmap.mem name r in
     let getter r =
       try Some (D.of_json (Fmap.find name r)) with Not_found -> None in
@@ -255,34 +266,11 @@ struct
       | Some v -> Fmap.add name (D.to_json v) r in
     { member ; getter ; setter }
 
-  let fields () =
-    let field = `Center "Field" in
-    let format = `Center "Format" in
-    let default = `Center "Default" in
-    let descr = `Left "Description" in
-    if Fmap.is_empty !defaults then
-      Markdown.table [ field ; format ; descr ]
-        (List.map
-           (fun (fd,sy,_def,descr) ->
-              [ Markdown.tt fd ; Syntax.format sy ; descr ])
-           !fdocs)
-    else
-      let mk_syntax def sy = if def <> None then Syntax.option sy else sy in
-      let mk_default = function
-        | None -> Markdown.text []
-        | Some js -> Markdown.tt (Json.to_string js) in
-      Markdown.table [ field ; format ; default ; descr ]
-        (List.map
-           (fun (fd,sy,def,descr) -> [
-                Markdown.tt fd ;
-                Syntax.format @@ mk_syntax def sy ;
-                mk_default def ; descr ;
-              ])
-           !fdocs)
+  let fields () = Syntax.fields ~kind:"Field" !fdocs
 
   let syntax =
     let descr = Markdown.( R.descr </> mk_block fields ) in
-    Syntax.publish R.page ~name:R.name ~synopsis:(Syntax.record []) ~descr
+    Syntax.publish ~page:R.page ~name:R.name ~synopsis:(Syntax.record []) ~descr
 
   let of_json js =
     List.fold_left
@@ -317,7 +305,7 @@ sig
 end
 
 let publish_id (module A : Info) =
-  Syntax.publish A.page ~name:A.name ~synopsis:Syntax.int ~descr:A.descr
+  Syntax.publish ~page:A.page ~name:A.name ~synopsis:Syntax.int ~descr:A.descr
 
 module INDEXER(M : Map)(I : Info) :
 sig
@@ -520,7 +508,8 @@ struct
       (struct
         type t = E.t
 
-        let syntax = Syntax.publish E.page ~name:E.name
+        let syntax = Syntax.publish
+            ~page:E.page ~name:E.name
             ~synopsis:Syntax.ident
             ~descr:Markdown.( E.descr </> mk_block values )
 
diff --git a/src/plugins/server/doc.ml b/src/plugins/server/doc.ml
index 0bfc615de78..351c563e823 100644
--- a/src/plugins/server/doc.ml
+++ b/src/plugins/server/doc.ml
@@ -80,7 +80,7 @@ let page chapter ~title ~filename =
                  sections=[] } in
     pages := Pages.add path page !pages ; page
 
-let publish page ?name ?(index=[]) ~title content sections =
+let publish ~page ?name ?(index=[]) ~title content sections =
   let id = match name with Some id -> id | None -> title in
   let href = `Section( page.path , id ) in
   let section = Markdown.section ?name ~title content sections in
diff --git a/src/plugins/server/doc.mli b/src/plugins/server/doc.mli
index 7a9db9e4b42..e204916f44b 100644
--- a/src/plugins/server/doc.mli
+++ b/src/plugins/server/doc.mli
@@ -51,7 +51,8 @@ val page : chapter -> title:string -> filename:string -> page
     If index items are provided, they are added
     to the server documentation index.
 *)
-val publish : page ->
+val publish :
+  page:page ->
   ?name:string ->
   ?index:string list ->
   title:string ->
diff --git a/src/plugins/server/kernel_ast.ml b/src/plugins/server/kernel_ast.ml
index acef66913aa..adf62a4c4e5 100644
--- a/src/plugins/server/kernel_ast.ml
+++ b/src/plugins/server/kernel_ast.ml
@@ -28,15 +28,14 @@ module Jutil = Yojson.Basic.Util
 (* --- Frama-C Ast Services                                               --- *)
 (* -------------------------------------------------------------------------- *)
 
-let ast_page =
-  Doc.page `Kernel ~title:"Ast Services" ~filename:"ast.md"
+let page = Doc.page `Kernel ~title:"Ast Services" ~filename:"ast.md"
 
 module ExecCompute = Request.Register(Junit)(Junit)
     (struct
       let kind = `EXEC
       let name = "Kernel.Ast.ExecCompute"
       let descr = Markdown.rm "Ensures that AST is computed"
-      let page = ast_page
+      let page = page
       let details = []
       type input = unit
       type output = unit
@@ -107,8 +106,7 @@ module PP = Printer_tag.Make(Tag)
 module Stmt = Data.Collection
     (struct
       type t = stmt
-      let syntax = Syntax.publish ast_page
-          ~name:"stmt"
+      let syntax = Syntax.publish ~page ~name:"stmt"
           ~synopsis:Syntax.ident
           ~descr:(Markdown.praw "Code statement identifier")
       let to_json st = `String (Tag.of_stmt st)
@@ -137,8 +135,7 @@ module Ki = Data.Collection
 module Kf = Data.Collection
     (struct
       type t = kernel_function
-      let syntax = Syntax.publish ast_page
-          ~name:"function"
+      let syntax = Syntax.publish ~page ~name:"function"
           ~synopsis:Syntax.ident
           ~descr:(Markdown.praw "Function, identified by its global name.")
       let to_json kf = `String (Kernel_function.get_name kf)
@@ -156,7 +153,7 @@ module GetFunctions = Request.Register(Junit)(Kf.Jlist)
       let kind = `GET
       let name = "Kernel.Ast.GetFunctions"
       let descr = Markdown.rm "Collect all functions in the AST"
-      let page = ast_page
+      let page = page
       let details = []
       type input = unit
       type output = kernel_function list
@@ -171,7 +168,7 @@ module PrintFunction = Request.Register(Kf)(Jtext)
       let kind = `GET
       let name = "Kernel.Ast.PrintFunction"
       let descr = Markdown.rm "Print the AST of a function"
-      let page = ast_page
+      let page = page
       let details = []
       type input = kernel_function
       type output = json
diff --git a/src/plugins/server/kernel_fc.ml b/src/plugins/server/kernel_fc.ml
index efaae9a32c8..893b961fd22 100644
--- a/src/plugins/server/kernel_fc.ml
+++ b/src/plugins/server/kernel_fc.ml
@@ -28,8 +28,7 @@ module Senv = Server_parameters
 
 open Data
 
-let fc_page =
-  Doc.page `Kernel ~title:"Kernel Services" ~filename:"kernel.md"
+let page = Doc.page `Kernel ~title:"Kernel Services" ~filename:"kernel.md"
 
 (* -------------------------------------------------------------------------- *)
 (* --- Config                                                             --- *)
@@ -64,7 +63,7 @@ module GetConfig =
     (Junit)
     (ConfigInfo)
     (struct
-      let page = fc_page
+      let page = page
       let kind = `GET
       let name = "Kernel.GetConfig"
       let descr = Markdown.rm "Kernel configuration"
@@ -82,8 +81,7 @@ module GetConfig =
 module RawSource =
 struct
   type t = Filepath.position
-  let syntax = Syntax.publish fc_page
-      ~name:"source"
+  let syntax = Syntax.publish ~page ~name:"source"
       ~synopsis:(Syntax.record [ "file" , Syntax.string ; "line" , Syntax.int ])
       ~descr:(Markdown.praw "Source position. The file path is normalized, \
                              and the line number starts at one.")
@@ -110,7 +108,7 @@ module LogSource = Collection(RawSource)
 module RawKind =
 struct
   type t = Log.kind
-  let page = fc_page
+  let page = page
   let name = "Kind"
   let descr = Markdown.praw "Frama-C message category."
   let values = [
@@ -134,7 +132,7 @@ struct
 
   module R = Record
       (struct
-        let page = fc_page
+        let page = page
         let name = "log"
         let descr = Markdown.praw "Message event record."
       end)
@@ -212,7 +210,7 @@ module SetLogs =
       let name = "Kernel.SetLogs"
       let descr = Markdown.rm "Turn logs monitoring on/off"
       let details = []
-      let page = fc_page
+      let page = page
       let kind = `SET
       type input = bool
       type output = unit
@@ -227,7 +225,7 @@ module GetLogs =
       let name = "Kernel.GetLogs"
       let descr = Markdown.rm "Flush emitted logs since last call (max 100)"
       let details = []
-      let page = fc_page
+      let page = page
       let kind = `GET
       type input = unit
       type output = Log.event list
diff --git a/src/plugins/server/kernel_project.ml b/src/plugins/server/kernel_project.ml
index b9c7d963469..8cb431633c0 100644
--- a/src/plugins/server/kernel_project.ml
+++ b/src/plugins/server/kernel_project.ml
@@ -27,13 +27,12 @@
 open Data
 module Jutil = Yojson.Basic.Util
 
-let project_page =
-  Doc.page `Kernel ~title:"Project Management" ~filename:"project.md"
+let page = Doc.page `Kernel ~title:"Project Management" ~filename:"project.md"
 
 module ProjectInfo =
 struct
   type t = Project.t
-  let syntax = Syntax.publish project_page ~name:"project"
+  let syntax = Syntax.publish ~page ~name:"project"
       ~synopsis:(Syntax.(record ["id",string;"name",string;"current",boolean]))
       ~descr:(Markdown.praw "Project informations")
   let name_of_json = function
@@ -53,7 +52,7 @@ end
 module ProjectRequest =
 struct
   type t = Project.t * string * json
-  let syntax = Syntax.publish project_page ~name:"project"
+  let syntax = Syntax.publish ~page ~name:"project"
       ~synopsis:(Syntax.(record ["project",string;"request",string;"data",any]))
       ~descr:(Markdown.praw "Request to be executed on the specified project.")
   let of_json js =
@@ -76,7 +75,7 @@ module GetCurrent =
     (Junit)
     (ProjectInfo)
     (struct
-      let page = project_page
+      let page = page
       let kind = `GET
       let name = "Kernel.Project.GetCurrent"
       let descr = Markdown.rm "Returns the current project"
@@ -91,7 +90,7 @@ module SetCurrent =
     (ProjectInfo)
     (Junit)
     (struct
-      let page = project_page
+      let page = page
       let kind = `SET
       let name = "Kernel.Project.SetCurrent"
       let descr = Markdown.rm "Switches the current project"
@@ -106,7 +105,7 @@ module GetProjects =
     (Junit)
     (Jlist(ProjectInfo))
     (struct
-      let page = project_page
+      let page = page
       let kind = `GET
       let name = "Kernel.Project.GetList"
       let descr = Markdown.rm "List of projects"
@@ -121,7 +120,7 @@ module GetOn =
     (ProjectRequest)
     (Jany)
     (struct
-      let page = project_page
+      let page = page
       let kind = `GET
       let name = "Kernel.Project.GetOn"
       let descr = Markdown.rm "Execute a GET request within the given project"
@@ -136,7 +135,7 @@ module SetOn =
     (ProjectRequest)
     (Jany)
     (struct
-      let page = project_page
+      let page = page
       let kind = `SET
       let name = "Kernel.Project.SetOn"
       let descr = Markdown.rm "Execute a SET request within the given project"
@@ -151,7 +150,7 @@ module ExecOn =
     (ProjectRequest)
     (Jany)
     (struct
-      let page = project_page
+      let page = page
       let kind = `EXEC
       let name = "Kernel.Project.ExecOn"
       let descr = Markdown.rm "Execute an EXEC request within the given project"
diff --git a/src/plugins/server/request.ml b/src/plugins/server/request.ml
index 80ef1bf3242..ca448e9a289 100644
--- a/src/plugins/server/request.ml
+++ b/src/plugins/server/request.ml
@@ -21,6 +21,7 @@
 (**************************************************************************)
 
 module Senv = Server_parameters
+module Jutil = Yojson.Basic.Util
 
 (* -------------------------------------------------------------------------- *)
 (* --- Request Registry                                                   --- *)
@@ -43,6 +44,9 @@ sig
   val to_json : t -> json
 end
 
+type 'a input = (module Input with type t = 'a)
+type 'a output = (module Output with type t = 'a)
+
 module type RequestInfo =
 sig
   type input
@@ -137,7 +141,7 @@ struct
         [[ Syntax.format Input.syntax ;
            Syntax.format Output.syntax ; Rq.descr ]]
     in
-    Doc.publish Rq.page ~index:[Rq.name] ~title synopsis Rq.details
+    Doc.publish ~page:Rq.page ~index:[Rq.name] ~title synopsis Rq.details
 
   let () =
     check_name Rq.name ;
@@ -148,3 +152,244 @@ struct
 end
 
 (* -------------------------------------------------------------------------- *)
+(* --- Multiple Fields Requests                                           --- *)
+(* -------------------------------------------------------------------------- *)
+
+module Fmap = Map.Make(String)
+
+type rq = {
+  mutable param : json Fmap.t ;
+  mutable result : json Fmap.t ;
+}
+
+let fmap_of_json r js =
+  List.fold_left
+    (fun r (fd,js) -> Fmap.add fd js r)
+    r (Jutil.to_assoc js)
+
+let fmap_to_json r =
+  `Assoc (Fmap.fold (fun fd js r -> (fd,js)::r) r [])
+
+type 'a param = rq -> 'a
+type 'a result = rq -> 'a -> unit
+
+(* -------------------------------------------------------------------------- *)
+(* --- Input/Output Request Processing                                    --- *)
+(* -------------------------------------------------------------------------- *)
+
+type _ rq_input =
+  | Pnone
+  | Pdata : 'a input -> 'a rq_input
+  | Pfields : Syntax.field list -> unit rq_input
+
+type _ rq_output =
+  | Rnone
+  | Rdata : 'a output -> 'a rq_output
+  | Rfields : Syntax.field list -> unit rq_output
+
+(* json input processing *)
+let mk_input (type a) name (input : a rq_input) : (rq -> json -> a) =
+  match input with
+  | Pnone -> Senv.fatal "No input defined for request '%s'" name
+  | Pdata d -> let module D = (val d) in (fun _rq js -> D.of_json js)
+  | Pfields _ -> (fun rq js -> rq.param <- fmap_of_json rq.param js)
+
+(* json output processing *)
+let mk_output (type b) name (output : b rq_output) : (rq -> b -> json) =
+  match output with
+  | Rnone -> Senv.fatal "No output defined for request '%s'" name
+  | Rdata d -> let module D = (val d) in (fun _rq v -> D.to_json v)
+  | Rfields _ -> (fun rq () -> fmap_to_json rq.result)
+
+(* json input syntax *)
+let sy_input (type a) (input : a rq_input) : Syntax.t =
+  match input with
+  | Pnone -> assert false
+  | Pdata d -> let module D = (val d) in D.syntax
+  | Pfields _ -> Syntax.record []
+
+(* json output syntax *)
+let sy_output (type b) (output : b rq_output) : Syntax.t =
+  match output with
+  | Rnone -> assert false
+  | Rdata d -> let module D = (val d) in D.syntax
+  | Rfields _ -> Syntax.record []
+
+(* json input documentation *)
+let doc_input (type a) (input : a rq_input) : Markdown.block =
+  match input with
+  | Pnone -> assert false
+  | Pdata _ -> Markdown.empty
+  | Pfields fs -> Syntax.fields ~kind:"Input" (List.rev fs)
+
+(* json output syntax *)
+let doc_output (type b) (output : b rq_output) : Markdown.block =
+  match output with
+  | Rnone -> assert false
+  | Rdata _ -> Markdown.empty
+  | Rfields fs -> Syntax.fields ~kind:"Output" (List.rev fs)
+
+(* current input fields *)
+let fds_input (type a) name (input : a rq_input) : Syntax.field list =
+  match input with
+  | Pdata _ -> Senv.fatal "Can not define named parameters for request '%s'" name
+  | Pnone -> []
+  | Pfields fds -> fds
+
+(* current output fields *)
+let fds_output (type a) name (output : a rq_output) : Syntax.field list =
+  match output with
+  | Rdata _ -> Senv.fatal "Can not define named results request '%s'" name
+  | Rnone -> []
+  | Rfields fds -> fds
+
+(* -------------------------------------------------------------------------- *)
+(* --- Multi-Parameters Requests                                          --- *)
+(* -------------------------------------------------------------------------- *)
+
+type ('a,'b) signature = {
+  page : Doc.page ;
+  kind : kind ;
+  name : string ;
+  descr : Markdown.text ;
+  details : Markdown.block ;
+  mutable defined : bool ;
+  mutable defaults : json Fmap.t ;
+  mutable input : 'a rq_input ;
+  mutable output : 'b rq_output ;
+}
+
+let failure_missing name fmap =
+  Data.failure
+    (Printf.sprintf "Missing parameter '%s'" name)
+    (fmap_to_json fmap)
+
+(* -------------------------------------------------------------------------- *)
+(* --- Named Input Parameters Definitions                                 --- *)
+(* -------------------------------------------------------------------------- *)
+
+let param (type a b) (s : (unit,b) signature) ~name ~descr
+    ?default (input : a input) : a param =
+  let module D = (val input) in
+  let fd = Syntax.{
+      fd_name = name ;
+      fd_syntax = if default = None then D.syntax else Syntax.option D.syntax ;
+      fd_default = None ;
+      fd_descr = descr ;
+    } in
+  s.input <- Pfields (fd :: fds_input s.name s.input) ;
+  fun rq ->
+    try D.of_json (Fmap.find name rq.param)
+    with Not_found ->
+    match default with
+    | None -> failure_missing name rq.param
+    | Some v -> v
+
+let param_opt (type a b) (rq : (unit,b) signature) ~name ~descr
+    (input : a input) : a option param =
+  let module D = (val input) in
+  let fd = Syntax.{
+      fd_name = name ;
+      fd_syntax = Syntax.option D.syntax ;
+      fd_default = None ;
+      fd_descr = descr ;
+    } in
+  rq.input <- Pfields (fd :: fds_input rq.name rq.input) ;
+  fun rq ->
+    try Some(D.of_json (Fmap.find name rq.param))
+    with Not_found -> None
+
+(* -------------------------------------------------------------------------- *)
+(* --- Named Output Parameters Definitions                                --- *)
+(* -------------------------------------------------------------------------- *)
+
+let result (type a b) (s : (a,unit) signature) ~name ~descr
+    ?default (output : b output) : b result =
+  let module D = (val output) in
+  let fd = Syntax.{
+      fd_name = name ;
+      fd_syntax = D.syntax ;
+      fd_default = None ;
+      fd_descr = descr ;
+    } in
+  s.output <- Rfields (fd :: fds_output s.name s.output) ;
+  ( match default with None -> () | Some v ->
+        s.defaults <- Fmap.add name (D.to_json v) s.defaults ) ;
+  fun rq v -> rq.result <- Fmap.add name (D.to_json v) rq.result
+
+let result_opt (type a b) (s : (a,unit) signature) ~name ~descr
+    (output : b output) : b option result =
+  let module D = (val output) in
+  let fd = Syntax.{
+      fd_name = name ;
+      fd_syntax = Syntax.option D.syntax ;
+      fd_default = None ;
+      fd_descr = descr ;
+    } in
+  s.output <- Rfields (fd :: fds_output s.name s.output) ;
+  fun rq opt ->
+    match opt with None -> () | Some v ->
+      rq.result <- Fmap.add name (D.to_json v) rq.result
+
+(* -------------------------------------------------------------------------- *)
+(* --- Opened Signature Definition                                        --- *)
+(* -------------------------------------------------------------------------- *)
+
+let signature
+    ~page ~kind ~name ~descr ?(details=Markdown.empty)
+    ?input ?output () =
+  check_name name ;
+  check_page page name ;
+  check_kind kind name ;
+  let input = match input with None -> Pnone | Some d -> Pdata d in
+  let output = match output with None -> Rnone | Some d -> Rdata d in
+  {
+    page ; kind ; name ; descr ; details ;
+    defaults = Fmap.empty ;
+    input ; output ; defined = false ;
+  }
+
+(* -------------------------------------------------------------------------- *)
+(* --- Opened Signature Process                                           --- *)
+(* -------------------------------------------------------------------------- *)
+
+let register_sig (type a b) (s : (a,b) signature) (process : rq -> a -> b) =
+  if s.defined then
+    Senv.fatal "Request '%s' is defined twice" s.name ;
+  let input = mk_input s.name s.input in
+  let output = mk_output s.name s.output in
+  let defaults = s.defaults in
+  let processor js =
+    let rq = { param = Fmap.empty ; result = defaults } in
+    js |> input rq |> process rq |> output rq
+  in
+  let skind = Main.string_of_kind s.kind in
+  let title =  Printf.sprintf "`%s` %s" skind s.name in
+  let pp_syntax fmt sy = Markdown.pp_text fmt (Syntax.format sy) in
+  let synopsis = Markdown.fmt_block (fun fmt ->
+      Format.fprintf fmt "> `'%s'` ( %a ) : %a" s.name
+        pp_syntax (sy_input s.input)
+        pp_syntax (sy_output s.output)
+    ) in
+  let content =
+    Markdown.concat [
+      Markdown.par s.descr ;
+      synopsis ;
+      s.details ;
+      doc_input s.input ;
+      doc_output s.output ;
+    ] in
+  let _ = Doc.publish ~page:s.page ~name:s.name ~title content [] in
+  Main.register s.kind s.name processor ;
+  s.defined <- true
+
+(* -------------------------------------------------------------------------- *)
+(* --- Request Registration                                               --- *)
+(* -------------------------------------------------------------------------- *)
+
+let register ~page ~kind ~name ~descr ?details ~input ~output ~process () =
+  register_sig
+    (signature ~page ~kind ~name ~descr ?details ~input ~output ())
+    (fun _rq v -> process v)
+
+(* -------------------------------------------------------------------------- *)
diff --git a/src/plugins/server/request.mli b/src/plugins/server/request.mli
index bdd9eed2f4a..966f7da9b70 100644
--- a/src/plugins/server/request.mli
+++ b/src/plugins/server/request.mli
@@ -41,6 +41,149 @@ sig
   val to_json : t -> json
 end
 
+type 'a input = (module Input with type t = 'a)
+type 'b output = (module Output with type t = 'b)
+
+(** {2 Simple Requests Registration} *)
+
+(** Register a simple request of type [(a -> b)]. *)
+val register :
+  page:Doc.page ->
+  kind:kind ->
+  name:string ->
+  descr:Markdown.text ->
+  ?details:Markdown.block ->
+  input:'a input ->
+  output:'b output ->
+  process:('a -> 'b) ->
+  unit -> unit
+
+(** {2 Requests with Named Parameters}
+
+    The API below allows for creating requests with
+    named and optional parameters. Although such requests
+    could be defined with simple registration and {i record} datatypes,
+    the helpers below allow more flexibility and a better correspondance
+    between optional parameters and OCaml option types.
+
+    To register a request with named parameters and/or named results,
+    you first create a {i signature}. Then you define named
+    parameters and results, and finally you {i register} the processing
+    function:
+
+    {[
+      let () =
+        let s = Request.signature ~page ~kind ~name ~descr () in
+        let get_a = Request.param s ~name:"a" ~descr:"…" (module A) in
+        let get_b = Request.param s ~name:"b" ~descr:"…" (module B) in
+        let set_c = Request.result s ~name:"c" ~descr:"…" (module C) in
+        let set_d = Request.result s ~name:"d" ~descr:"…" (module D) in
+        Request.register_sig s
+          (fun rq () ->
+             let (c,d) = some_job (get_a rq) (get_b rq) in
+             set_c rq c ; set_d rq d)
+    ]}
+
+*)
+
+(** Under definition request signature. *)
+type ('a,'b) signature
+
+(** Create an opened request signature.
+    Depending on whether [~input] and [~output] datatype are provided,
+    you shall define named parameters and results before registering the
+    request processing function. *)
+val signature :
+  page:Doc.page ->
+  kind:kind ->
+  name:string ->
+  descr:Markdown.text ->
+  ?details:Markdown.block ->
+  ?input:'a input ->
+  ?output:'b output ->
+  unit -> ('a,'b) signature
+
+(** Request JSON parameters. *)
+type rq
+
+(** Named input parameter. *)
+type 'a param = rq -> 'a
+
+(** Named output parameter. *)
+type 'b result = rq -> 'b -> unit
+
+(** Register the request JSON processing function.
+    This call finalize the signature definition and shall be called
+    once on the signature. *)
+val register_sig : ('a,'b) signature -> (rq -> 'a -> 'b) -> unit
+
+(** {2 Named Parameters and Results}
+
+    The functions bellow must be called on a freshly created signature
+    {i before} its final registration. The obtained getters and setters
+    shall be only used within the registered process.
+
+    The correspondance between input/output JSON syntax and OCaml values
+    is summarized in the tables below.Abstract_domain
+
+    For named input parameters:
+    [
+
+        API:                    Input JSON   OCaml Getter
+        -----------------------------------------------------
+        Request.param            { f: a  }    'a (* might raise an exception *)
+        Request.param ~default   { f: a? }    'a (* defined by default *)
+        Request.param_opt        { f: a? }    'a option
+
+    ]
+
+
+    For named output parameters:
+    [
+
+        API:                    Input JSON   OCaml Setter
+        ----------------------------------------------------
+        Request.result           { f: a  }    'a (* shall be set by process *)
+        Request.result ~default  { f: a  }    'a (* defined by default *)
+        Request.param_opt        { f: a? }    'a option
+
+    ]
+
+*)
+
+
+(** Named input parameter. If a default value is provided,
+    the JSON input field becomes optional. Otherwized, it is required. *)
+val param : (unit,'b) signature ->
+  name:string ->
+  descr:Markdown.text ->
+  ?default:'a ->
+  'a input -> 'a param
+
+(** Named optional input parameter. *)
+val param_opt : (unit,'b) signature ->
+  name:string ->
+  descr:Markdown.text ->
+  'a input -> 'a option param
+
+(** Named output parameter. If a default value is provided,
+    the JSON output field is initialized with it.
+    Otherwized, it shall be set at each invocation of the request processing
+    funciton. *)
+val result : ('a,unit) signature ->
+  name:string ->
+  descr:Markdown.text ->
+  ?default:'b ->
+  'b output -> 'b result
+
+(** Named optional output parameter. The initial value is set to [None]. *)
+val result_opt : ('a,unit) signature ->
+  name:string ->
+  descr:Markdown.text ->
+  'b output -> 'b option result
+
+(** {2 Functorial Interface} *)
+
 module type RequestInfo =
 sig
   type input
diff --git a/src/plugins/server/syntax.ml b/src/plugins/server/syntax.ml
index 79e030cb641..63cbcd0abab 100644
--- a/src/plugins/server/syntax.ml
+++ b/src/plugins/server/syntax.ml
@@ -61,7 +61,7 @@ let format { descr } = descr
 let protect a =
   if a.atomic then a.descr else Markdown.(rm "(" <+> a.descr <+> rm ")")
 
-let publish page ~name ~synopsis ~descr =
+let publish ~page ~name ~synopsis ~descr =
   check_name name ;
   check_page page name ;
   let title = Printf.sprintf "`Data` %s" name in
@@ -70,7 +70,7 @@ let publish page ~name ~synopsis ~descr =
         name Markdown.pp_text synopsis.descr
     ) in
   let content = Markdown.( syntax </> descr ) in
-  let href = Doc.publish page ~name ~title ~index:[name] content [] in
+  let href = Doc.publish ~page ~name ~title ~index:[name] content [] in
   atom @@ Markdown.href ~title:name href
 
 let any = atom @@ Markdown.it "any"
@@ -105,4 +105,36 @@ let record fds =
       Markdown.(glue ~sep:(raw " `;` ") (List.map field fds))
   in atom @@ Markdown.(tt "{" <+> fields <+> tt "}")
 
+type field = {
+  fd_name : string ;
+  fd_syntax : t ;
+  fd_default : Markdown.text option ;
+  fd_descr : Markdown.text ;
+}
+
+let fields ~kind (fds : field list) =
+  let c_field = `Center kind in
+  let c_format = `Center "Format" in
+  let c_default = `Center "Default" in
+  let c_descr = `Left "Description" in
+  if List.for_all (fun f -> f.fd_default = None) fds then
+    Markdown.table [ c_field ; c_format ; c_descr ]
+      (List.map
+         (fun f ->
+            [ Markdown.tt f.fd_name ; format f.fd_syntax ; f.fd_descr ])
+         fds)
+    else
+      let mk_syntax def sy = if def <> None then option sy else sy in
+      let mk_default = function
+        | None -> Markdown.text []
+        | Some default -> default in
+      Markdown.table [ c_field ; c_format ; c_default ; c_descr ]
+        (List.map
+           (fun f -> [
+                Markdown.tt f.fd_name ;
+                format @@ mk_syntax f.fd_default f.fd_syntax ;
+                mk_default f.fd_default ; f.fd_descr ;
+              ])
+           fds)
+
 (* -------------------------------------------------------------------------- *)
diff --git a/src/plugins/server/syntax.mli b/src/plugins/server/syntax.mli
index de443d599c0..6b6262ffb3e 100644
--- a/src/plugins/server/syntax.mli
+++ b/src/plugins/server/syntax.mli
@@ -31,7 +31,8 @@ val format : t -> Markdown.text
 (** The provided synopsis must be very short, to fit in one line.
     Extended definition, like record fields and such, must be detailed in
     the description block. *)
-val publish : Doc.page -> name:string -> synopsis:t -> descr:Markdown.block -> t
+val publish :
+  page:Doc.page -> name:string -> synopsis:t -> descr:Markdown.block -> t
 
 val any : t
 val int : t (* small, non-decimal, number *)
@@ -48,4 +49,14 @@ val union : t list -> t
 val option : t -> t
 val record : (string * t) list -> t
 
+type field = {
+  fd_name : string ;
+  fd_syntax : t ;
+  fd_default : Markdown.text option ;
+  fd_descr : Markdown.text ;
+}
+
+(** Builds a table with fields column named « Kind » *)
+val fields : kind:string -> field list -> Markdown.block
+
 (* -------------------------------------------------------------------------- *)
-- 
GitLab