123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316moduleOt=Pb_codegen_ocaml_typemoduleF=Pb_codegen_formattingletsp=Pb_codegen_util.sp(** Function which returns all the possible pattern match for reading a JSON
value into an OCaml value. The protobuf JSON encoding rules
are defined here:
https://developers.google.com/protocol-buffers/docs/proto3#json *)letfield_pattern_match~r_name~rf_labelfield_type=matchfield_typewith|Ot.Ft_basic_typebt->letdecoderuntime_f=sp"Pbrt_yojson.%s json_value \"%s\" \"%s\""runtime_fr_namerf_labelinletexp=matchbtwith|Ot.Bt_string->decode"string"|Ot.Bt_float->decode"float"|Ot.Bt_int->decode"int"|Ot.Bt_int32->decode"int32"|Ot.Bt_int64->decode"int64"|Ot.Bt_uint32->sp"`unsigned (%s)"(decode"int32")|Ot.Bt_uint64->sp"`unsigned (%s)"(decode"int64")|Ot.Bt_bool->decode"bool"|Ot.Bt_bytes->decode"bytes"in"json_value",exp|Ot.Ft_unit->"json_value",sp"Pbrt_yojson.unit json_value \"%s\" \"%s\""r_namerf_label(* TODO Wrapper: add similar one for wrapper type (with different
runtime functions) *)|Ot.Ft_user_defined_typeudt->letf_name=letfunction_prefix="decode_json"inPb_codegen_util.function_name_of_user_defined~function_prefixudtinletvalue_expression="("^f_name^" json_value)"in"json_value",value_expression|_->assertfalse(* Generate all the pattern matches for a record field *)letgen_rft_nolabelsc~r_name~rf_label(field_type,_,_)=letjson_label=Pb_codegen_util.camel_case_of_labelrf_labelinletmatch_variable_name,exp=field_pattern_match~r_name~rf_labelfield_typeinF.linepsc"| (\"%s\", %s) -> "json_labelmatch_variable_name;F.linepsc" v.%s <- %s"rf_labelexp(* Generate all the pattern matches for a repeated field *)letgen_rft_repeated_fieldsc~r_name~rf_labelrepeated_field=let_,field_type,_,_,_=repeated_fieldinletjson_label=Pb_codegen_util.camel_case_of_labelrf_labelinF.linepsc"| (\"%s\", `List l) -> begin"json_label;F.sub_scopesc(funsc->F.linepsc"v.%s <- List.map (function"rf_label;letmatch_variable_name,exp=field_pattern_match~r_name~rf_labelfield_typeinF.linepsc" | %s -> %s"match_variable_nameexp;F.linesc") l;");F.linesc"end"letgen_rft_optional_fieldsc~r_name~rf_labeloptional_field=letfield_type,_,_,_=optional_fieldinletjson_label=Pb_codegen_util.camel_case_of_labelrf_labelinletmatch_variable_name,exp=field_pattern_match~r_name~rf_labelfield_typeinF.linepsc"| (\"%s\", %s) -> "json_labelmatch_variable_name;F.linepsc" v.%s <- Some (%s)"rf_labelexp(* Generate pattern match for a variant field *)letgen_rft_variant_fieldsc~r_name~rf_label{Ot.v_constructors;_}=List.iter(fun{Ot.vc_constructor;vc_field_type;_}->letjson_label=Pb_codegen_util.camel_case_of_constructorvc_constructorinmatchvc_field_typewith|Ot.Vct_nullary->F.linepsc"| (\"%s\", _) -> v.%s <- %s"json_labelrf_labelvc_constructor|Ot.Vct_non_nullary_constructorfield_type->letmatch_variable_name,exp=field_pattern_match~r_name~rf_labelfield_typeinF.linepsc"| (\"%s\", %s) -> "json_labelmatch_variable_name;F.linepsc" v.%s <- %s (%s)"rf_labelvc_constructorexp)v_constructorsletgen_rft_assoc_fieldsc~r_name~rf_label~assoc_type~key_type~value_type=letjson_label=Pb_codegen_util.camel_case_of_labelrf_labelinF.linepsc"| (\"%s\", `Assoc assoc) ->"json_label;F.sub_scopesc(funsc->letvalue_name,value_exp=field_pattern_match~r_name~rf_labelvalue_typeinletkey_name="key"inletkey_exp=matchkey_typewith|Ot.Bt_string->"key"|Ot.Bt_int->"(Int.of_string key)"|Ot.Bt_int32->"(Int32.of_string key)"|Ot.Bt_int64->"(Int64.of_string key)"|Ot.Bt_uint32->"(`unsigned (Int32.of_string key))"|Ot.Bt_uint64->"(`unsigned (Int64.of_string key))"|Ot.Bt_bool->"(Bool.of_string key)"|Ot.Bt_float->Printf.eprintf"float cannot be used as a map key type";exit1|Ot.Bt_bytes->Printf.eprintf"bytes cannot be used as a map key type";exit1inF.linesc"let assoc =";F.sub_scopesc(funsc->F.linesc"assoc";F.linepsc"|> List.map (fun (%s, %s) -> (%s, %s)) "key_namevalue_namekey_expvalue_exp;F.linesc"|> List.to_seq";(* Passing through [Hashtbl.of_seq] even in the [At_list] case ensures that if there
is a repeated key we take the last value associated with it. *)F.linesc"|> Hashtbl.of_seq");F.linesc"in";letassoc_exp=matchassoc_typewith|Ot.At_hashtable->"assoc"|Ot.At_list->"assoc |> Hashtbl.to_seq |> List.of_seq"inF.linepsc"v.%s <- %s"rf_labelassoc_exp)(* Generate decode function for a record *)letgen_record?and_{Ot.r_name;r_fields}sc=letmutable_record_name=Pb_codegen_util.mutable_record_namer_nameinF.linesc@@sp"%s decode_json_%s d ="(Pb_codegen_util.let_decl_of_andand_)r_name;F.sub_scopesc(funsc->F.linepsc"let v = default_%s () in"mutable_record_name;F.linesc@@"let assoc = match d with";F.linesc@@" | `Assoc assoc -> assoc";F.linesc@@" | _ -> assert(false)";(* TODO raise E *)F.linesc@@"in";F.linesc"List.iter (function ";F.sub_scopesc(funsc->(* Generate pattern match for all the possible message field *)List.iter(fun{Ot.rf_label;rf_field_type;_}->matchrf_field_typewith|Ot.Rft_nolabelnolabel_field->gen_rft_nolabelsc~r_name~rf_labelnolabel_field|Ot.Rft_repeatedrepeated_field->gen_rft_repeated_fieldsc~r_name~rf_labelrepeated_field|Ot.Rft_variantvariant_field->gen_rft_variant_fieldsc~r_name~rf_labelvariant_field|Ot.Rft_optionaloptional_field->gen_rft_optional_fieldsc~r_name~rf_labeloptional_field|Ot.Rft_required_->Printf.eprintf"Only proto3 syntax supported in JSON encoding";exit1|Ot.Rft_associative(assoc_type,_,(key_type,_),(value_type,_))->gen_rft_assoc_fieldsc~r_name~rf_label~assoc_type~key_type~value_type)r_fields;(* Unknown fields are simply ignored *)F.empty_linesc;F.linesc"| (_, _) -> () (*Unknown fields are ignored*)");F.linesc") assoc;";(* Transform the mutable record in an immutable one *)F.linesc"({";F.sub_scopesc(funsc->List.iter(fun{Ot.rf_label;_}->F.linepsc"%s = v.%s;"rf_labelrf_label)r_fields);F.linepsc"} : %s)"r_name)(* Generate decode function for an empty record *)letgen_unit?and_{Ot.er_name}sc=F.linesc@@sp"%s decode_json_%s d ="(Pb_codegen_util.let_decl_of_andand_)er_name;F.linesc(sp"Pbrt_yojson.unit d \"%s\" \"%s\""er_name"empty record")(* Generate decode function for a variant type *)letgen_variant?and_{Ot.v_name;v_constructors}sc=(* helper function for each constructor case *)letprocess_v_constructorsc{Ot.vc_constructor;vc_field_type;_}=letjson_label=Pb_codegen_util.camel_case_of_constructorvc_constructorinmatchvc_field_typewith|Ot.Vct_nullary->F.linepsc"| (\"%s\", _)::_-> (%s : %s)"json_labelvc_constructorv_name|Ot.Vct_non_nullary_constructorfield_type->letmatch_,exp=letr_name=v_nameandrf_label=vc_constructorinfield_pattern_match~r_name~rf_labelfield_typeinF.linepsc"| (\"%s\", %s)::_ -> "json_labelmatch_;F.linepsc" (%s (%s) : %s)"vc_constructorexpv_nameinF.linepsc"%s decode_json_%s json ="(Pb_codegen_util.let_decl_of_andand_)v_name;F.sub_scopesc(funsc->(* even though a variant should be an object with a single field,
* it is possible other fields are present in the JSON object. Therefore
* we still need a loop to iterate over the key/value, even if in 99.99%
* of the cases it will be a single iteration *)F.linesc"let assoc = match json with";F.linesc" | `Assoc assoc -> assoc";F.linesc" | _ -> assert(false)";(* TODO raise E *)F.linesc"in";F.linesc"let rec loop = function";F.sub_scopesc(funsc->(* termination condition *)F.linepsc"| [] -> Pbrt_yojson.E.malformed_variant \"%s\""v_name;List.iter(process_v_constructorsc)v_constructors;F.empty_linesc;F.linesc"| _ :: tl -> loop tl");F.linesc"in";F.linesc"loop assoc")letgen_const_variant?and_{Ot.cv_name;cv_constructors}sc=F.linepsc"%s decode_json_%s json ="(Pb_codegen_util.let_decl_of_andand_)cv_name;F.sub_scopesc(funsc->F.linesc"match json with";List.iter(fun{Ot.cvc_name;cvc_string_value;_}->F.linepsc"| `String \"%s\" -> (%s : %s)"cvc_string_valuecvc_namecv_name)cv_constructors;F.linepsc"| _ -> Pbrt_yojson.E.malformed_variant \"%s\""cv_name)letgen_struct?and_tsc=let{Ot.spec;_}=tinlethas_encoded=matchspecwith|Ot.Recordr->gen_record?and_rsc;true|Ot.Variantv->gen_variant?and_vsc;true|Ot.Const_variantv->gen_const_variant?and_vsc;true|Ot.Unitu->gen_unit?and_usc;trueinhas_encodedletgen_sig?and_tsc=let_=and_inlet{Ot.spec;_}=tinletftype_name=F.linepsc"val decode_json_%s : Yojson.Basic.t -> %s"type_nametype_name;F.linepsc("(** [decode_json_%s decoder] decodes a "^^"[%s] value from [decoder] *)")type_nametype_nameinmatchspecwith|Ot.Record{Ot.r_name;_}->fr_name;true|Ot.Variant{Ot.v_name;_}->fv_name;true|Ot.Const_variant{Ot.cv_name;_}->fcv_name;true|Ot.Unit{Ot.er_name;_}->fer_name;trueletocamldoc_title="JSON Decoding"letrequires_mutable_records=trueletplugin:Pb_codegen_plugin.t=letmoduleP=structletgen_sig=gen_sigletgen_struct=gen_structletocamldoc_title=ocamldoc_titleletrequires_mutable_records=requires_mutable_recordsendin(moduleP)