-module(sara@json). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/sara/json.gleam"). -export([json_serializable_builder/1]). -export_type([generated_code/0, custom_encoder/0, custom_decoder/0, custom_zero_value/0, custom_codec/0, config/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " JSON Serialization Code Generator\n" "\n" " This module provides a builder that automatically generates JSON encoding\n" " and decoding functions for Gleam custom types annotated with `//@json_encode`\n" " and/or `//@json_decode`.\n" "\n" " ## Basic Usage\n" "\n" " Annotate your types:\n" "\n" " ```gleam\n" " //@json_encode\n" " //@json_decode\n" " pub type User {\n" " User(name: String, age: Int)\n" " }\n" " ```\n" "\n" " This generates a companion `*_json.gleam` file containing:\n" " - `pub fn user_to_json(user: User) -> json.Json`\n" " - `pub fn user_json_decoder() -> decode.Decoder(User)`\n" "\n" " ## Custom Codecs\n" "\n" " For types requiring special serialization logic, provide a `CustomCodec`:\n" "\n" " ```gleam\n" " json.json_serializable_builder(\n" " json.Config([\n" " json.CustomCodec(\n" " type_name: \"Timestamp\",\n" " module_path: \"gleam/time/timestamp\",\n" " encode: fn(_, _, variable, _, _) {\n" " json.GeneratedCode(\n" " \"json.string(timestamp.to_rfc3339(\" <> variable <> \", calendar.utc_offset))\",\n" " [\"gleam/time/calendar\", \"gleam/time/timestamp\"],\n" " [],\n" " )\n" " },\n" " decode: fn(_, _, _, _) {\n" " json.GeneratedCode(\n" " \"{\n" " use date <- decode.then(decode.string)\n" " case timestamp.parse_rfc3339(date) {\n" " Ok(timestamp) -> decode.success(timestamp)\n" " Error(_) -> decode.failure(timestamp.system_time(), \\\"Timestamp\\\")\n" " }\n" " }\",\n" " [\"gleam/time/calendar\", \"gleam/time/timestamp\"],\n" " [],\n" " )\n" " },\n" " zero: fn(_, _, _, _) {\n" " Ok(json.GeneratedCode(\"timestamp.system_time()\", [\"gleam/time/timestamp\"], []))\n" " },\n" " ),\n" " ]),\n" " )\n" " ```\n" "\n" " ## Codec Resolution Order\n" "\n" " 1. **Built-in types**: `Int`, `Float`, `Bool`, `String`, `List`, tuples\n" " 2. **Custom codecs**: Types registered via `CustomCodec` in config\n" " 3. **Annotated types**: Types with `//@json_encode` or `//@json_decode`\n" " 4. **Fallback**: Unresolved types become function parameters\n" "\n" ). -type generated_code() :: {generated_code, binary(), list(binary()), list({binary(), binary()})}. -type custom_encoder() :: {custom_encoder, binary(), binary(), fun((builder@context:build_context(), glance:type(), binary(), builder@module:module_(), builder@module:module_()) -> generated_code())}. -type custom_decoder() :: {custom_decoder, binary(), binary(), fun((builder@context:build_context(), glance:type(), builder@module:module_(), builder@module:module_()) -> generated_code())}. -type custom_zero_value() :: {custom_zero_value, binary(), binary(), fun((builder@context:build_context(), glance:type(), builder@module:module_(), builder@module:module_()) -> {ok, generated_code()} | {error, nil})}. -type custom_codec() :: {custom_codec, binary(), binary(), fun((builder@context:build_context(), glance:type(), binary(), builder@module:module_(), builder@module:module_()) -> generated_code()), fun((builder@context:build_context(), glance:type(), builder@module:module_(), builder@module:module_()) -> generated_code()), fun((builder@context:build_context(), glance:type(), builder@module:module_(), builder@module:module_()) -> {ok, generated_code()} | {error, nil})}. -type config() :: {config, list(custom_codec())}. -file("src/sara/json.gleam", 1742). -spec module_name_for_path(binary()) -> binary(). module_name_for_path(Name) -> case Name of <<""/utf8>> -> <<""/utf8>>; _ -> <> end. -file("src/sara/json.gleam", 1682). -spec virtual_type_definition( {ok, builder@inspect:type_definition()} | {error, nil}, builder@context:build_context(), glance:type(), builder@module:module_() ) -> {ok, builder@inspect:type_definition()} | {error, nil}. virtual_type_definition(Result, Ctx, Type_, Current_module) -> case Result of {ok, _} -> Result; {error, _} -> case Type_ of {named_type, _, Name, Module, _} -> Is_prelude = builder@inspect:is_prelude_type( Ctx, Type_, Current_module ), case {Name, Module, Is_prelude} of {<<"Result"/utf8>>, none, true} -> {ok, {custom_type, {definition, [], {custom_type, {span, 0, 0}, <<"Result"/utf8>>, public, false, [<<"a"/utf8>>, <<"b"/utf8>>], [{variant, <<"Ok"/utf8>>, [{unlabelled_variant_field, {variable_type, {span, 0, 0}, <<"a"/utf8>>}}], []}, {variant, <<"Error"/utf8>>, [{unlabelled_variant_field, {variable_type, {span, 0, 0}, <<"b"/utf8>>}}], []}]}}, {module, <<""/utf8>>, erlang:element(3, Current_module), erlang:element(4, Current_module), erlang:element(5, Current_module), erlang:element(6, Current_module), erlang:element(7, Current_module), erlang:element(8, Current_module), erlang:element(9, Current_module), erlang:element(10, Current_module), erlang:element(11, Current_module)}}}; {_, _, _} -> Result end; _ -> Result end end. -file("src/sara/json.gleam", 1589). -spec get_zero_value_generic( config(), builder@context:build_context(), glance:type(), builder@module:module_(), builder@module:module_(), gleam@set:set(glance:variant()) ) -> {ok, generated_code()} | {error, nil}. get_zero_value_generic( Config, Ctx, Type_, Current_module, Original_module, Variant_path ) -> case virtual_type_definition( builder@inspect:find_type_definition(Ctx, Type_, Current_module), Ctx, Type_, Current_module ) of {ok, Type_defintion} -> case Type_defintion of {type_alias, Source, Module} -> get_zero_value( Config, Ctx, erlang:element(6, erlang:element(3, Source)), Module, Original_module, Variant_path ); {custom_type, Source@1, Module@1} -> Custom_zero_values = [{custom_zero_value, <<"Option"/utf8>>, <<"gleam/option"/utf8>>, fun(_, _, _, _) -> {ok, {generated_code, <<"option.None"/utf8>>, [<<"gleam/option"/utf8>>], []}} end}, {custom_zero_value, <<"Dict"/utf8>>, <<"gleam/dict"/utf8>>, fun(_, _, _, _) -> {ok, {generated_code, <<"dict.new()"/utf8>>, [<<"gleam/dict"/utf8>>], []}} end}], Decoder = gleam@list:find_map( lists:append( Custom_zero_values, gleam@list:map( erlang:element(2, Config), fun(Codec) -> {custom_zero_value, erlang:element(2, Codec), erlang:element(3, Codec), erlang:element(6, Codec)} end ) ), fun(Custom_decoder) -> case (erlang:element(3, erlang:element(3, Source@1)) =:= erlang:element(2, Custom_decoder)) andalso (erlang:element(4, Module@1) =:= erlang:element( 3, Custom_decoder )) of true -> {ok, erlang:element(4, Custom_decoder)}; _ -> {error, nil} end end ), case Decoder of {ok, Decode} -> Decode(Ctx, Type_, Current_module, Original_module); _ -> case erlang:element(5, erlang:element(3, Source@1)) of true -> {error, nil}; _ -> gleam@list:find_map( erlang:element( 7, erlang:element(3, Source@1) ), fun(Variant) -> get_variant_zero_value( Config, Ctx, Variant, Module@1, Variant_path ) end ) end end end; {error, _} -> {error, nil} end. -file("src/sara/json.gleam", 1528). -spec get_zero_value( config(), builder@context:build_context(), glance:type(), builder@module:module_(), builder@module:module_(), gleam@set:set(glance:variant()) ) -> {ok, generated_code()} | {error, nil}. get_zero_value( Config, Ctx, Type_, Current_module, Original_module, Variant_path ) -> Is_prelude = builder@inspect:is_prelude_type(Ctx, Type_, Current_module), case Type_ of {named_type, _, Name, _, _} -> case {Name, Is_prelude} of {<<"Int"/utf8>>, true} -> {ok, {generated_code, <<"0"/utf8>>, [], []}}; {<<"Float"/utf8>>, true} -> {ok, {generated_code, <<"0"/utf8>>, [], []}}; {<<"Bool"/utf8>>, true} -> {ok, {generated_code, <<"False"/utf8>>, [], []}}; {<<"String"/utf8>>, true} -> {ok, {generated_code, <<"\"\""/utf8>>, [], []}}; {<<"List"/utf8>>, true} -> {ok, {generated_code, <<"[]"/utf8>>, [], []}}; {_, _} -> get_zero_value_generic( Config, Ctx, Type_, Current_module, Original_module, Variant_path ) end; {tuple_type, _, Elements} -> Codes = gleam@list:try_map( Elements, fun(Element) -> get_zero_value( Config, Ctx, Element, Current_module, Original_module, Variant_path ) end ), case Codes of {ok, Codes@1} -> {ok, {generated_code, <<<<"#("/utf8, (begin _pipe = Codes@1, _pipe@1 = gleam@list:map( _pipe, fun(Code) -> erlang:element(2, Code) end ), gleam@string:join(_pipe@1, <<","/utf8>>) end)/binary>>/binary, ")"/utf8>>, begin _pipe@2 = gleam@list:map( Codes@1, fun(Code@1) -> erlang:element(3, Code@1) end ), gleam@list:fold( _pipe@2, [], fun(Acc, Imports) -> lists:append(Acc, Imports) end ) end, begin _pipe@3 = gleam@list:map( Codes@1, fun(Code@2) -> erlang:element(4, Code@2) end ), gleam@list:fold( _pipe@3, [], fun(Acc@1, Arguments) -> lists:append(Acc@1, Arguments) end ) end}}; {error, _} -> {error, nil} end; _ -> {error, nil} end. -file("src/sara/json.gleam", 1454). -spec get_variant_zero_value( config(), builder@context:build_context(), glance:variant(), builder@module:module_(), gleam@set:set(glance:variant()) ) -> {ok, generated_code()} | {error, nil}. get_variant_zero_value(Config, Ctx, Variant, Module, Variant_path) -> gleam@bool:guard( gleam@set:contains(Variant_path, Variant), {error, nil}, fun() -> Variant_path@1 = gleam@set:insert(Variant_path, Variant), Output = <<""/utf8>>, Fields = gleam@list:try_map( erlang:element(3, Variant), fun(Field) -> case erlang:element(2, Field) of {variable_type, _, _} -> {error, nil}; _ -> Zero = get_zero_value( Config, Ctx, erlang:element(2, Field), Module, Module, Variant_path@1 ), case Zero of {ok, Zero@1} -> {ok, {generated_code, <<(case Field of {labelled_variant_field, _, Label} -> <