Skip to content

Instantly share code, notes, and snippets.

@davesnx
Created February 3, 2025 15:07
Show Gist options
  • Save davesnx/78d8130b6f9cc01cd99a1afb20ba7719 to your computer and use it in GitHub Desktop.
Save davesnx/78d8130b6f9cc01cd99a1afb20ba7719 to your computer and use it in GitHub Desktop.
a few functions to replace of_yojson dependency
(*
TODO: If we want to remove the dependency on of_json, we need to implement the json decoder manually.
TODO: Maybe use a custom deriving called "rsc" or similar where it handles the JSON/Promise/React.element. *)
let rec make_of_json ~loc (type_ : core_type) value =
match type_.ptyp_desc with
| Ptyp_constr ({ txt = Lident "int"; _ }, _) -> value
| Ptyp_constr ({ txt = Lident "string"; _ }, _) -> value
| Ptyp_constr ({ txt = Lident "bool"; _ }, _) -> value
| Ptyp_constr ({ txt = Lident "float"; _ }, _) -> value
| Ptyp_constr ({ txt = Lident "unit"; _ }, _) -> value
| Ptyp_constr ({ txt = Lident "list"; _ }, list) ->
let inner = List.hd list in
let mapped = [%expr Stdlib.List.map (fun x -> [%e make_of_json ~loc inner [%expr x]]) [%e value]] in
pexp_variant ~loc:value.pexp_loc "List" (Some mapped)
| Ptyp_constr ({ txt = Lident "array"; _ }, array) ->
let inner = List.hd array in
let mapped = [%expr Stdlib.Array.map (fun x -> [%e make_of_json ~loc inner [%expr x]]) [%e value]] in
let as_list = [%expr Stdlib.Array.to_list [%e mapped]] in
pexp_variant ~loc:value.pexp_loc "List" (Some as_list)
| Ptyp_constr ({ txt = Lident "option"; _ }, option) ->
let inner = List.hd option in
let matched = [%expr match [%e value] with None -> `Null | Some x -> [%e make_of_json ~loc inner [%expr x]]] in
matched
(* TODO: Add json/yojson *)
(* | [%type: Yojson.Basic.t] -> pexp_variant ~loc:value.pexp_loc "Yojson" (Some value) *)
| Ptyp_constr ({ txt = lident; _ }, _) ->
let rec make_of_json_fn lident =
match lident with
| Lident name when name = "t" -> Lident "of_json"
| Lident name -> Lident (Printf.sprintf "%s_of_json" name)
| Ldot (modulePath, name) when name = "t" -> Ldot (modulePath, "of_json")
| Ldot (modulePath, name) -> Ldot (modulePath, Printf.sprintf "%s_of_json" name)
| Lapply (apply, longident) -> Lapply (apply, make_of_json_fn longident)
in
pexp_apply ~loc:value.pexp_loc (pexp_ident ~loc { txt = make_of_json_fn lident; loc }) [ (Nolabel, value) ]
| Ptyp_tuple tuple ->
let item_name index = "x" ^ Int.to_string index in
let loc = value.pexp_loc in
let descructuring =
ppat_tuple ~loc (List.mapi ~f:(fun index _ -> ppat_var ~loc { txt = item_name index; loc }) tuple)
in
let list =
List.mapi
~f:(fun index t ->
let identifier = pexp_ident ~loc { txt = Lident (item_name index); loc } in
make_of_json ~loc [%type: [%t t]] identifier)
tuple
in
pexp_let ~loc Nonrecursive
[ value_binding ~loc ~pat:descructuring ~expr:value ]
[%expr `List [%e pexp_list ~loc list]]
| Ptyp_var name ->
let msg = Printf.sprintf "server-reason-react: unsupported type: '%s" name in
[%expr [%ocaml.error [%e estring ~loc msg]]]
| Ptyp_arrow _ ->
[%expr
[%ocaml.error
"server-reason-react: callbacks are not supported in client components. Functions can't be serialized to the \
client."]]
(* | Ptyp_object _ -> error_cannot_create_json_encoder ~loc ~type_name:"objects"
| Ptyp_class _ -> error_cannot_create_json_encoder ~loc ~type_name:"classes"
| Ptyp_variant _ -> error_cannot_create_json_encoder ~loc ~type_name:"polyvariants"
| Ptyp_alias _ -> error_not_supported ~loc ~type_name:"aliases"
| Ptyp_extension _ -> error_not_supported ~loc ~type_name:"extensions"
| Ptyp_package _ -> error_not_supported ~loc ~type_name:"modules"
| Ptyp_poly _ -> error_not_supported ~loc ~type_name:"polymorphic types"
| Ptyp_any -> error_not_supported ~loc ~type_name:"'_' annotations" *)
| _ -> [%expr [%ocaml.error "server-reason-react: unsupported type"]]
(* let error_cannot_create_json_encoder ~loc ~type_name =
let msg =
Printf.sprintf
"server-reason-react: inline types such as %s, need to be a type definition with a json encoder. If the type is \
named 't' the encoder should be named 't_to_json', if the type is named 'foo' the encoder should be named \
'foo_to_json'."
type_name
in
[%expr [%ocaml.error [%e estring ~loc msg]]]
let error_not_supported ~loc ~type_name =
let msg =
Printf.sprintf
"server-reason-react: %s aren't supported in client components. Try using a type definition with a json encoder \
but there's no guarantee that it will work. Open an issue if you need it."
type_name
in
[%expr [%ocaml.error [%e estring ~loc msg]]] *)
let rec make_to_yojson ~loc (type_ : core_type) value =
match type_.ptyp_desc with
| Ptyp_constr ({ txt = Lident "int"; _ }, _) -> pexp_variant ~loc:value.pexp_loc "Int" (Some value)
| Ptyp_constr ({ txt = Lident "string"; _ }, _) -> pexp_variant ~loc:value.pexp_loc "String" (Some value)
| Ptyp_constr ({ txt = Lident "bool"; _ }, _) -> pexp_variant ~loc:value.pexp_loc "Bool" (Some value)
| Ptyp_constr ({ txt = Lident "float"; _ }, _) -> pexp_variant ~loc:value.pexp_loc "Float" (Some value)
| Ptyp_constr ({ txt = Lident "list"; _ }, list) ->
let inner = List.hd list in
let mapped = [%expr Stdlib.List.map (fun x -> [%e make_to_yojson ~loc inner [%expr x]]) [%e value]] in
pexp_variant ~loc:value.pexp_loc "List" (Some mapped)
| Ptyp_constr ({ txt = Lident "array"; _ }, array) ->
let inner = List.hd array in
let mapped = [%expr Stdlib.Array.map (fun x -> [%e make_to_yojson ~loc inner [%expr x]]) [%e value]] in
let as_list = [%expr Stdlib.Array.to_list [%e mapped]] in
pexp_variant ~loc:value.pexp_loc "List" (Some as_list)
| Ptyp_constr ({ txt = Lident "option"; _ }, option) ->
let inner = List.hd option in
let matched =
[%expr match [%e value] with None -> `Null | Some x -> [%e make_to_yojson ~loc inner [%expr x]]]
in
matched
| Ptyp_constr ({ txt = Lident "unit"; _ }, _) -> pexp_variant ~loc:value.pexp_loc "Null" None
(* TODO: Add json/yojson *)
(* | [%type: Yojson.Basic.t] -> pexp_variant ~loc:value.pexp_loc "Yojson" (Some value) *)
| Ptyp_constr ({ txt = lident; _ }, _) ->
let rec make_to_json_fn lident =
match lident with
| Lident name when name = "t" -> Lident "to_json"
| Lident name -> Lident (Printf.sprintf "%s_to_json" name)
| Ldot (modulePath, name) when name = "t" -> Ldot (modulePath, "to_json")
| Ldot (modulePath, name) -> Ldot (modulePath, Printf.sprintf "%s_to_json" name)
| Lapply (apply, longident) -> Lapply (apply, make_to_json_fn longident)
in
pexp_apply ~loc:value.pexp_loc (pexp_ident ~loc { txt = make_to_json_fn lident; loc }) [ (Nolabel, value) ]
| Ptyp_tuple tuple ->
let item_name index = "x" ^ Int.to_string index in
let loc = value.pexp_loc in
let descructuring =
ppat_tuple ~loc (List.mapi ~f:(fun index _ -> ppat_var ~loc { txt = item_name index; loc }) tuple)
in
let list =
List.mapi
~f:(fun index t ->
let identifier = pexp_ident ~loc { txt = Lident (item_name index); loc } in
make_to_yojson ~loc [%type: [%t t]] identifier)
tuple
in
pexp_let ~loc Nonrecursive
[ value_binding ~loc ~pat:descructuring ~expr:value ]
[%expr `List [%e pexp_list ~loc list]]
| Ptyp_var name ->
let msg = Printf.sprintf "server-reason-react: unsupported type: '%s" name in
[%expr [%ocaml.error [%e estring ~loc msg]]]
| Ptyp_arrow _ ->
[%expr
[%ocaml.error
"server-reason-react: callbacks are not supported in client components. Functions can't be serialized to the \
client."]]
| Ptyp_object _ -> error_cannot_create_json_encoder ~loc ~type_name:"objects"
| Ptyp_class _ -> error_cannot_create_json_encoder ~loc ~type_name:"classes"
| Ptyp_variant _ -> error_cannot_create_json_encoder ~loc ~type_name:"polyvariants"
| Ptyp_alias _ -> error_not_supported ~loc ~type_name:"aliases"
| Ptyp_extension _ -> error_not_supported ~loc ~type_name:"extensions"
| Ptyp_package _ -> error_not_supported ~loc ~type_name:"modules"
| Ptyp_poly _ -> error_not_supported ~loc ~type_name:"polymorphic types"
| Ptyp_any -> error_not_supported ~loc ~type_name:"'_' annotations"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment