Skip to content

Commit f8761c0

Browse files
committed
deriver for services
1 parent 80f029d commit f8761c0

File tree

7 files changed

+150
-43
lines changed

7 files changed

+150
-43
lines changed

src/ppx/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
(optional)
55
(modules ppx_common)
66
(preprocess (pps ppxlib.metaquot))
7-
(libraries ppxlib))
7+
(libraries ppx_deriving_encoding.lib))
88

99
(library
1010
(name ppx_client)

src/ppx/ppx.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
(**************************************************************************)
1010

1111
let () =
12-
Ppxlib.Driver.register_transformation "ez_api" ~impl:Ppx_common.impl
12+
Ppxlib.Driver.register_transformation "ez_api" ~impl:Ppx_common.impl;
13+
Ppx_common.derivers ()

src/ppx/ppx_client.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
(**************************************************************************)
1010

1111
let () =
12-
Ppxlib.Driver.register_transformation "ez_api_client" ~impl:(Ppx_common.impl ~kind:`client)
12+
Ppxlib.Driver.register_transformation "ez_api_client" ~impl:(Ppx_common.impl ~kind:`client);
13+
Ppx_common.derivers ()

src/ppx/ppx_common.ml

Lines changed: 135 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -45,29 +45,19 @@ type options = {
4545
service : expression option;
4646
}
4747

48-
let empty ~loc = pexp_construct ~loc (llid ~loc "EzAPI.Empty") None
4948
let raw e =
5049
let loc = e.pexp_loc in
51-
let e =
52-
eapply ~loc (evar ~loc "List.filter_map") [ evar ~loc "EzAPI.Mime.parse"; e ] in
53-
pexp_construct ~loc (llid ~loc "EzAPI.Raw") @@ Some e
54-
let json e =
55-
let loc = e.pexp_loc in
56-
pexp_construct ~loc (llid ~loc "EzAPI.Json") @@ Some e
57-
58-
let options ?register ?name loc =
59-
let register = match register with
60-
| None -> pexp_construct ~loc (llid ~loc "true") None
61-
| Some register -> register in
62-
let name = match name with
63-
| None -> [%expr None]
64-
| Some name -> esome (estring ~loc name) in {
65-
path = pexp_ident ~loc (llid ~loc "EzAPI.Path.root");
66-
input = empty ~loc; output = empty ~loc; errors = [%expr None]; params = [%expr None];
67-
section = [%expr None]; name; descr = [%expr None];
68-
security = [%expr None]; register; input_example = [%expr None]; hide = [%expr None];
69-
output_example = [%expr None]; error_type = ptyp_constr ~loc (llid ~loc "exn") [];
70-
security_type = ptyp_constr ~loc (llid ~loc "EzAPI.no_security") [];
50+
[%expr EzAPI.Raw (List.filter_map EzAPI.Mime.parse [%e e])]
51+
52+
let options loc = {
53+
path = [%expr EzAPI.Path.root];
54+
input = [%expr EzAPI.Empty];
55+
output = [%expr EzAPI.Empty];
56+
errors = [%expr None]; params = [%expr None];
57+
section = [%expr None]; name=[%expr None]; descr = [%expr None];
58+
security = [%expr None]; register=[%expr true]; input_example = [%expr None];
59+
hide = [%expr None]; output_example = [%expr None]; error_type = [%type: exn];
60+
security_type = [%type: EzAPI.no_security];
7161
debug = false; directory = None; service = None
7262
}
7363

@@ -100,23 +90,22 @@ let string_literal = function
10090
| Ppxlib.Pconst_string (s, _, _) -> Some s
10191
| _ -> None
10292

103-
let get_options ~loc ?name ?(client=false) p =
104-
let register = if not client then None else Some (pexp_construct ~loc (llid ~loc "false") None) in
93+
let get_options ~loc ?(options=options loc) ?name p =
10594
match p with
10695
| PStr [ {pstr_desc=Pstr_eval ({pexp_desc=Pexp_record (l, _); _}, _); _} ] ->
10796
let l = List.filter_map (function ({txt=Lident s; loc}, e) -> Some (s, loc, e) | _ -> None) l in
10897
List.fold_left (fun (name, acc) (s, loc, e) -> match s with
109-
| "path" -> begin match e.pexp_desc with
98+
| "path" | "p" -> begin match e.pexp_desc with
11099
| Pexp_constant cst ->
111100
begin match string_literal cst with
112101
| Some s -> name, { acc with path = parse_path ~loc:e.pexp_loc s }
113102
| _ -> Format.eprintf "path should be a string literal"; name, acc
114103
end
115104
| _ -> Format.eprintf "path should be a literal"; name, acc
116105
end
117-
| "input" -> name, { acc with input = json e }
106+
| "input" -> name, { acc with input = [%expr EzAPI.Json [%e e]] }
118107
| "raw_input" -> name, { acc with input = raw e }
119-
| "output" -> name, { acc with output = json e }
108+
| "output" -> name, { acc with output = [%expr EzAPI.Json [%e e]] }
120109
| "raw_output" -> name, { acc with output = raw e }
121110
| "params" -> name, { acc with params = esome e }
122111
| "errors" -> name, { acc with errors = esome e; error_type = ptyp_any ~loc }
@@ -130,8 +119,9 @@ let get_options ~loc ?name ?(client=false) p =
130119
| _ -> Format.eprintf "name should be a string literal"; name, acc
131120
end
132121
| _ ->
133-
Format.eprintf "name should be a literal";
134-
name, acc
122+
match name with
123+
| Some n -> Some n, { acc with name = [%expr Some [%e estring ~loc n]] }
124+
| _ -> name, acc
135125
end
136126
| "descr" -> name, { acc with descr = esome e }
137127
| "security" -> name, { acc with security = esome e; security_type = ptyp_any ~loc }
@@ -150,20 +140,21 @@ let get_options ~loc ?name ?(client=false) p =
150140
end
151141
| "service" ->
152142
name, { acc with service = Some e; error_type = ptyp_any ~loc; security_type = ptyp_any ~loc }
153-
| _ -> name, acc) (name, options ?register ?name loc) l
143+
| _ -> name, acc) (name, options) l
154144
| PStr [ {pstr_desc=Pstr_eval ({pexp_desc=Pexp_ident _; _} as e, _); _} ] ->
155-
let o = options ?register ?name loc in
156-
name, { o with service = Some e; error_type = ptyp_any ~loc; security_type = ptyp_any ~loc }
145+
name, { options with service = Some e; error_type = ptyp_any ~loc; security_type = ptyp_any ~loc }
146+
| PStr [ {pstr_desc=Pstr_eval ({pexp_desc=Pexp_constant Pconst_string (s, loc, _); _}, _); _} ] ->
147+
name, { options with path = parse_path ~loc s }
157148
| PStr s ->
158149
Format.eprintf "attribute not understood %a@." Pprintast.structure s;
159-
name, options ?register ?name loc
150+
name, options
160151
| _ ->
161152
Format.eprintf "attribute not understood@.";
162-
name, options ?register ?name loc
153+
name, options
163154

164-
let service_value ?name ?client ~meth ~loc p =
155+
let service_value ?name ?options ~meth ~loc p =
165156
let meth = pexp_variant ~loc (String.uppercase_ascii meth) None in
166-
let name, options = get_options ~loc ?name ?client p in
157+
let name, options = get_options ?name ?options ~loc p in
167158
match name with
168159
| None -> Location.raise_errorf ~loc "service doesn't have a name"
169160
| Some name ->
@@ -344,6 +335,8 @@ let server ~loc p =
344335
[%expr EzLwtSys.run (fun () -> [%e server_aux e])]
345336
| _ -> Location.raise_errorf ~loc "server options not understood"
346337

338+
(** main mapper *)
339+
347340
let deprecate =
348341
let t : (string, unit) Hashtbl.t = Hashtbl.create 10 in
349342
fun s ->
@@ -442,14 +435,52 @@ let transform ?kind () =
442435
(* client service *)
443436
| Pstr_attribute a when List.mem a.attr_name.txt methods ->
444437
deprecate a.attr_name.txt;
445-
let service, _, _ = service_value ~client:true ~meth:a.attr_name.txt ~loc:a.attr_loc a.attr_payload in
438+
let loc = a.attr_loc in
439+
let options = { (options loc) with register = [%expr false] } in
440+
let service, _, _ = service_value ~options ~meth:a.attr_name.txt ~loc:a.attr_loc a.attr_payload in
446441
service :: acc
447442
| Pstr_extension (({txt; loc}, PStr [ { pstr_desc = Pstr_value (_, [ { pvb_expr; pvb_pat= {ppat_desc=Ppat_var {txt=name; _}; _}; _} ]); _} ]), _) when List.mem txt methods ->
448-
let service, _, _ = service_value ~name ~client:true ~meth:txt ~loc @@ PStr [ pstr_eval ~loc pvb_expr [] ] in
443+
let options = { (options loc) with register = [%expr false] } in
444+
let service, _, _ = service_value ~name ~options ~meth:txt ~loc @@ PStr [ pstr_eval ~loc pvb_expr [] ] in
449445
service :: acc
450446
| Pstr_extension (({txt; loc}, p), _) when List.mem txt methods ->
451-
let service, _, _ = service_value ~client:true ~meth:txt ~loc p in
447+
let options = { (options loc) with register = [%expr false] } in
448+
let service, _, _ = service_value ~options ~meth:txt ~loc p in
452449
service :: acc
450+
| Pstr_type (_rec_flag, [ t ]) ->
451+
let loc = t.ptype_loc in
452+
begin match List.find_opt (fun a -> List.mem a.attr_name.txt methods) t.ptype_attributes with
453+
| None -> (super#structure_item it) :: acc
454+
| Some a ->
455+
let meth = a.attr_name.txt in
456+
let enc =
457+
let open Ppx_deriving_encoding_lib.Encoding in
458+
let {enc; _} = expressions t in
459+
enc in
460+
let input, output = match meth with
461+
| "get" | "put" -> [%expr Some EzAPI.Empty], [%expr EzAPI.Json [%e enc]]
462+
| _ -> [%expr EzAPI.Json [%e enc]], [%expr Some EzAPI.Empty] in
463+
let options = { (options loc) with register = [%expr false]; input; output } in
464+
let name = t.ptype_name.txt ^ "_s" in
465+
let service, _, _ = service_value ~name ~options ~meth ~loc a.attr_payload in
466+
service :: it :: acc
467+
end
468+
| Pstr_type (_rec_flag, [ t_input; t_output ]) ->
469+
let loc = t_input.ptype_loc in
470+
begin match List.find_opt (fun a -> List.mem a.attr_name.txt methods) t_output.ptype_attributes with
471+
| None -> (super#structure_item it) :: acc
472+
| Some a ->
473+
let meth = a.attr_name.txt in
474+
let input, output =
475+
let open Ppx_deriving_encoding_lib.Encoding in
476+
let {enc=enc_input; _} = expressions t_input in
477+
let {enc=enc_output; _} = expressions t_output in
478+
[%expr EzAPI.Json [%e enc_input]], [%expr EzAPI.Json [%e enc_output]] in
479+
let options = { (options loc) with register = [%expr false]; input; output } in
480+
let name = t_input.ptype_name.txt ^ "_s" in
481+
let service, _, _ = service_value ~name ~options ~meth ~loc a.attr_payload in
482+
service :: it :: acc
483+
end
453484
| _ -> (self#structure_item it) :: acc
454485
) [] str
455486

@@ -460,3 +491,69 @@ let transform ?kind () =
460491
end
461492

462493
let impl ?kind str = (transform ?kind ())#structure str
494+
495+
let deriver_str_gen meth ~loc ~path:_ (_rec_flag, l) path input output errors params section name
496+
descr security register hide input_example output_example debug =
497+
let options = options loc in
498+
let sname = match l with t :: _ -> Some (t.ptype_name.txt ^ "_s") | [] -> None in
499+
let input, output = match meth, l with
500+
| _, [ t_input; t_output ] ->
501+
[%expr EzAPI.Json ([%e evar ~loc (t_input.ptype_name.txt ^ "_enc")] ())],
502+
[%expr EzAPI.Json ([%e evar ~loc (t_output.ptype_name.txt ^ "_enc")] ())]
503+
| ("get" | "put"), t :: _ ->
504+
Option.value ~default:options.input input,
505+
[%expr EzAPI.Json [%e evar ~loc (t.ptype_name.txt ^ "_enc")]]
506+
| _, t :: _ ->
507+
[%expr EzAPI.Json [%e evar ~loc (t.ptype_name.txt ^ "_enc")]],
508+
Option.value ~default:options.output output
509+
| _ -> Option.value ~default:options.input input, Option.value ~default:options.output output in
510+
let path = match path with
511+
| Some { pexp_desc = Pexp_constant cst; pexp_loc=loc; _ } ->
512+
begin match string_literal cst with
513+
| Some s -> parse_path ~loc s
514+
| _ -> Format.eprintf "path should be a string literal"; options.path
515+
end
516+
| _ -> options.path in
517+
let security_type, security = match security with
518+
| None -> options.security_type, options.security
519+
| Some e -> [%type: _], e in
520+
let options = {
521+
options with
522+
path; input; output;
523+
errors = Option.value ~default:options.errors errors;
524+
params = Option.value ~default:options.params params;
525+
section = Option.value ~default:options.section section;
526+
name = Option.value ~default:options.name name;
527+
descr = Option.value ~default:options.descr descr;
528+
security; security_type;
529+
register = Option.value ~default:[%expr false] register;
530+
hide = Option.value ~default:options.hide hide;
531+
input_example = Option.value ~default:options.input_example input_example;
532+
output_example = Option.value ~default:options.output_example output_example;
533+
debug;
534+
} in
535+
let s, _, _ = service_value ~meth ~loc ~options ?name:sname (PStr []) in
536+
[ s ]
537+
538+
let derivers () =
539+
let open Ppxlib.Deriving in
540+
List.iter (fun meth ->
541+
let args_str = Args.(
542+
empty
543+
+> arg "path" __
544+
+> arg "input" __
545+
+> arg "output" __
546+
+> arg "errors" __
547+
+> arg "params" __
548+
+> arg "section" __
549+
+> arg "name" __
550+
+> arg "descr" __
551+
+> arg "security" __
552+
+> arg "register" __
553+
+> arg "hide" __
554+
+> arg "input_example" __
555+
+> arg "output_example" __
556+
+> flag "debug"
557+
) in
558+
let str_type_decl = Generator.make args_str (deriver_str_gen meth) in
559+
ignore @@ add meth ~str_type_decl) methods

src/ppx/ppx_server.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
(**************************************************************************)
1010

1111
let () =
12-
Ppxlib.Driver.register_transformation "ez_api_server" ~impl:(Ppx_common.impl ~kind:`server)
12+
Ppxlib.Driver.register_transformation "ez_api_server" ~impl:(Ppx_common.impl ~kind:`server);
13+
Ppx_common.derivers ()

test/ppx/dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
(library
22
(name test_ppx_lib)
33
(modules test_ppx_lib)
4-
(preprocess (pps ez_api.ppx)))
4+
(preprocess (pps ez_api.ppx ppx_deriving_encoding)))
55

66
(executable
77
(name test_ppx_server)

test/ppx/test_ppx_lib.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
type nonrec test_derive_input = {
2+
foo: string;
3+
bar: int;
4+
}
5+
and test_derive_output = int
6+
[@@post {path="/test/getter"; debug}]
7+
18
let%post echo_input = {
29
path="/echo_input"; raw_input=["text/plain"];
310
output=Json_encoding.(obj1 (req "test" string))

0 commit comments

Comments
 (0)