-module(oas@generator@schema). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([custom_type/5, encode_fn/1, to_decoder/2, gen_top_decoder_needs_name/3, to_decode_fn/2, to_encoder/2, fields_to_encode_body/4, to_encode_fn/2, generate/1]). -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( " This module builds ASTs from schemas.\n" " It does not do any file generation\n" ). -file("src/oas/generator/schema.gleam", 116). ?DOC( " Generate type aliases from OpenAPI lifted schemas.\n" " All objects are converted to names\n" ). -spec to_type( oas@generator@lift:schema(integer()), oas@generator@misc:module_() ) -> glance:type(). to_type(Lifted, Module) -> case Lifted of {named, <<"#/components/schemas/"/utf8, Inner/binary>>} -> Mod = case Module of schema -> none; _ -> {some, <<"schema"/utf8>>} end, {named_type, oas@generator@ast:name_for_gleam_type(Inner), Mod, []}; {named, Ref} -> erlang:error(#{gleam_error => panic, message => (<<"not referencing schema component ref: "/utf8, Ref/binary>>), module => <<"oas/generator/schema"/utf8>>, function => <<"to_type"/utf8>>, line => 126}); {primitive, Primitive} -> case Primitive of boolean -> {named_type, <<"Bool"/utf8>>, none, []}; integer -> {named_type, <<"Int"/utf8>>, none, []}; number -> {named_type, <<"Float"/utf8>>, none, []}; string -> {named_type, <<"String"/utf8>>, none, []}; null -> {named_type, <<"Nil"/utf8>>, none, []}; always -> {named_type, <<"Json"/utf8>>, {some, <<"json"/utf8>>}, []}; never -> {named_type, <<"Never"/utf8>>, {some, <<"utils"/utf8>>}, []} end; {array, Items} -> {named_type, <<"List"/utf8>>, none, [to_type(Items, Module)]}; {tuple, Items@1} -> {tuple_type, gleam@list:map( Items@1, fun(_capture) -> to_type(_capture, Module) end )}; {compound, Index} -> Type_ = <<"Internal"/utf8, (erlang:integer_to_binary(Index))/binary>>, {named_type, Type_, none, []}; {dictionary, Values} -> {named_type, <<"Dict"/utf8>>, {some, <<"dict"/utf8>>}, [{named_type, <<"String"/utf8>>, none, []}, to_type(Values, Module)]}; unsupported -> {named_type, <<"Json"/utf8>>, {some, <<"json"/utf8>>}, []} end. -file("src/oas/generator/schema.gleam", 77). ?DOC( " Generate a custom type from an object type.\n" " TODO make private\n" ). -spec custom_type( binary(), list({binary(), {oas@generator@lift:schema(integer()), boolean()}}), gleam@option:option(oas@generator@lift:schema(integer())), list(binary()), oas@generator@misc:module_() ) -> glance:custom_type(). custom_type(Name, Properties, Additional, Required, Module) -> Fields = gleam@list:map( Properties, fun(Property) -> {Key, {Schema, Nullable}} = Property, Type_ = to_type(Schema, Module), {labelled_variant_field, case not gleam@list:contains(Required, Key) orelse Nullable of false -> Type_; true -> {named_type, <<"Option"/utf8>>, none, [Type_]} end, oas@generator@ast:name_for_gleam_field_or_var(Key)} end ), Fields@1 = case Additional of {some, Schema@1} -> Key@1 = <<"additionalProperties"/utf8>>, Type_@1 = to_type(Schema@1, Module), Extra = {labelled_variant_field, {named_type, <<"Dict"/utf8>>, {some, <<"dict"/utf8>>}, [{named_type, <<"String"/utf8>>, none, []}, Type_@1]}, oas@generator@ast:name_for_gleam_field_or_var(Key@1)}, lists:append(Fields, [Extra]); none -> Fields end, Name@1 = oas@generator@ast:name_for_gleam_type(Name), {custom_type, Name@1, public, false, [], [{variant, Name@1, Fields@1}]}. -file("src/oas/generator/schema.gleam", 335). -spec encode_fn(binary()) -> binary(). encode_fn(Name) -> oas@generator@ast:name_for_gleam_field_or_var( <> ). -file("src/oas/generator/schema.gleam", 510). -spec decoder(binary()) -> binary(). decoder(Name) -> oas@generator@ast:name_for_gleam_field_or_var( <> ). -file("src/oas/generator/schema.gleam", 515). ?DOC(" Not used anywhere else\n"). -spec alias(binary(), glance:type()) -> glance:type_alias(). alias(To, Type_) -> {type_alias, To, public, [], Type_}. -file("src/oas/generator/schema.gleam", 519). -spec always_decode() -> glance:expression(). always_decode() -> oas@generator@ast:call2( <<"decode"/utf8>>, <<"new_primitive_decoder"/utf8>>, {string, <<"Nil"/utf8>>}, {fn, [{fn_parameter, {discarded, <<""/utf8>>}, none}], none, [{expression, {call, {variable, <<"Ok"/utf8>>}, [{unlabelled_field, {variable, <<"Nil"/utf8>>}}]}}]} ). -file("src/oas/generator/schema.gleam", 534). -spec never_decode() -> glance:expression(). never_decode() -> oas@generator@ast:call2( <<"decode"/utf8>>, <<"new_primitive_decoder"/utf8>>, {string, <<"Never"/utf8>>}, {fn, [{fn_parameter, {discarded, <<""/utf8>>}, none}], none, [{expression, {call, {variable, <<"Error"/utf8>>}, [{unlabelled_field, {variable, <<"Nil"/utf8>>}}]}}]} ). -file("src/oas/generator/schema.gleam", 454). -spec to_decoder( oas@generator@lift:schema(integer()), oas@generator@misc:module_() ) -> glance:expression(). to_decoder(Lifted, Module) -> case Lifted of {named, <<"#/components/schemas/"/utf8, Name/binary>>} -> Func = oas@generator@ast:name_for_gleam_field_or_var( <> ), case Module of schema -> {call, {variable, Func}, []}; _ -> oas@generator@ast:call0(<<"schema"/utf8>>, Func) end; {named, Ref} -> erlang:error(#{gleam_error => panic, message => (<<"not referencing schema component ref: "/utf8, Ref/binary>>), module => <<"oas/generator/schema"/utf8>>, function => <<"to_decoder"/utf8>>, line => 464}); {primitive, Primitive} -> case Primitive of boolean -> oas@generator@ast:access(<<"decode"/utf8>>, <<"bool"/utf8>>); integer -> oas@generator@ast:access(<<"decode"/utf8>>, <<"int"/utf8>>); number -> oas@generator@ast:access( <<"decode"/utf8>>, <<"float"/utf8>> ); string -> oas@generator@ast:access( <<"decode"/utf8>>, <<"string"/utf8>> ); null -> always_decode(); always -> oas@generator@ast:call0( <<"utils"/utf8>>, <<"dynamic_to_json"/utf8>> ); never -> never_decode() end; {tuple, Items} -> Uses = gleam@list:index_map( Items, fun(Item, Index) -> {use, [{pattern_variable, <<"e"/utf8, (erlang:integer_to_binary(Index))/binary>>}], oas@generator@ast:call1( <<"decode"/utf8>>, <<"then"/utf8>>, to_decoder(Item, Module) )} end ), Vars = gleam@list:index_map( Items, fun(_, Index@1) -> {variable, <<"e"/utf8, (erlang:integer_to_binary(Index@1))/binary>>} end ), {block, lists:append( Uses, [{expression, oas@generator@ast:call1( <<"decode"/utf8>>, <<"success"/utf8>>, {tuple, Vars} )}] )}; {array, Items@1} -> oas@generator@ast:call1( <<"decode"/utf8>>, <<"list"/utf8>>, to_decoder(Items@1, Module) ); {compound, Index@2} -> Func@1 = <<<<"internal_"/utf8, (erlang:integer_to_binary(Index@2))/binary>>/binary, "_decoder"/utf8>>, {call, {variable, Func@1}, []}; {dictionary, Values} -> oas@generator@ast:call2( <<"decode"/utf8>>, <<"dict"/utf8>>, oas@generator@ast:access(<<"decode"/utf8>>, <<"string"/utf8>>), to_decoder(Values, Module) ); unsupported -> oas@generator@ast:call0( <<"utils"/utf8>>, <<"dynamic_to_json"/utf8>> ) end. -file("src/oas/generator/schema.gleam", 355). -spec gen_top_decoder_needs_name( binary(), oas@generator@lift:schema(oas@generator@lift:fields()), oas@generator@misc:module_() ) -> list(glance:statement()). gen_top_decoder_needs_name(Name, Top, Module) -> case Top of {named, N} -> [{expression, to_decoder({named, N}, Module)}]; {primitive, P} -> [{expression, to_decoder({primitive, P}, Module)}]; {array, Items} -> [{expression, to_decoder({array, Items}, Module)}]; {tuple, Items@1} -> case to_decoder({tuple, Items@1}, Module) of {block, Statements} -> Statements; Other -> [{expression, Other}] end; {compound, {fields, Properties, Additional, Required}} -> Type_ = oas@generator@ast:name_for_gleam_type(Name), Zipped = gleam@list:map( Properties, fun(Property) -> {Key, {Schema, Nullable}} = Property, Is_optional = not gleam@list:contains(Required, Key) orelse Nullable, Field_decoder = to_decoder(Schema, Module), {{use, [{pattern_variable, oas@generator@ast:name_for_gleam_field_or_var( Key )}], case Is_optional of false -> oas@generator@ast:call2( <<"decode"/utf8>>, <<"field"/utf8>>, {string, Key}, Field_decoder ); true -> {call, oas@generator@ast:access( <<"decode"/utf8>>, <<"optional_field"/utf8>> ), [{unlabelled_field, {string, Key}}, {unlabelled_field, {variable, <<"None"/utf8>>}}, {unlabelled_field, oas@generator@ast:call1( <<"decode"/utf8>>, <<"optional"/utf8>>, Field_decoder )}]} end}, {labelled_field, oas@generator@ast:name_for_gleam_field_or_var(Key), {variable, oas@generator@ast:name_for_gleam_field_or_var( Key )}}} end ), Zipped@1 = case Additional of {some, Schema@1} -> Key@1 = <<"additionalProperties"/utf8>>, lists:append( Zipped, [{{use, [{pattern_variable, oas@generator@ast:name_for_gleam_field_or_var( Key@1 )}], oas@generator@ast:call2( <<"utils"/utf8>>, <<"decode_additional"/utf8>>, {list, gleam@list:map( Properties, fun(P@1) -> {K, _} = P@1, {string, K} end ), none}, to_decoder(Schema@1, Module) )}, {labelled_field, oas@generator@ast:name_for_gleam_field_or_var( Key@1 ), {variable, oas@generator@ast:name_for_gleam_field_or_var( Key@1 )}}}] ); none -> Zipped end, {Fields, Cons} = gleam@list:unzip(Zipped@1), Final = {expression, oas@generator@ast:call1( <<"decode"/utf8>>, <<"success"/utf8>>, case erlang:length(Cons) of 0 -> {variable, Type_}; _ -> {call, {variable, Type_}, Cons} end )}, lists:append(Fields, [Final]); {dictionary, Values} -> [{expression, to_decoder({dictionary, Values}, Module)}]; unsupported -> [{expression, to_decoder(unsupported, Module)}] end. -file("src/oas/generator/schema.gleam", 340). -spec to_decode_fn( {binary(), oas@generator@lift:schema(oas@generator@lift:fields())}, oas@generator@misc:module_() ) -> glance:function_(). to_decode_fn(Entry, Module) -> {Name, Top} = Entry, Body = gen_top_decoder_needs_name(Name, Top, Module), {function, decoder(Name), public, [], none, Body, {span, 0, 0}}. -file("src/oas/generator/schema.gleam", 320). -spec encode_tuple_body( list(oas@generator@lift:schema(integer())), glance:expression(), oas@generator@misc:module_() ) -> glance:expression(). encode_tuple_body(Items, Arg, Module) -> oas@generator@ast:call1( <<"utils"/utf8>>, <<"merge"/utf8>>, {list, gleam@list:index_map( Items, fun(Item, Index) -> {call, to_encoder(Item, Module), [{unlabelled_field, {tuple_index, Arg, Index}}]} end ), none} ). -file("src/oas/generator/schema.gleam", 259). ?DOC(" This handles a lifted encoder\n"). -spec to_encoder( oas@generator@lift:schema(integer()), oas@generator@misc:module_() ) -> glance:expression(). to_encoder(Lifted, Module) -> case Lifted of {named, <<"#/components/schemas/"/utf8, Inner/binary>>} -> Func = encode_fn(Inner), case Module of schema -> {variable, Func}; _ -> oas@generator@ast:access(<<"schema"/utf8>>, Func) end; {named, _} -> erlang:error(#{gleam_error => panic, message => <<"unexpected ref"/utf8>>, module => <<"oas/generator/schema"/utf8>>, function => <<"to_encoder"/utf8>>, line => 268}); {primitive, boolean} -> oas@generator@ast:access(<<"json"/utf8>>, <<"bool"/utf8>>); {primitive, integer} -> oas@generator@ast:access(<<"json"/utf8>>, <<"int"/utf8>>); {primitive, number} -> oas@generator@ast:access(<<"json"/utf8>>, <<"float"/utf8>>); {primitive, string} -> oas@generator@ast:access(<<"json"/utf8>>, <<"string"/utf8>>); {primitive, null} -> {fn, [{fn_parameter, {discarded, <<""/utf8>>}, {some, {named_type, <<"Nil"/utf8>>, none, []}}}], none, [{expression, oas@generator@ast:call0( <<"json"/utf8>>, <<"null"/utf8>> )}]}; {primitive, always} -> {fn, [{fn_parameter, {named, <<"data"/utf8>>}, none}], none, [{expression, {variable, <<"data"/utf8>>}}]}; {primitive, never} -> {fn, [{fn_parameter, {discarded, <<"data"/utf8>>}, none}], none, [{expression, {panic, {some, {string, <<"never value cannot be encoded"/utf8>>}}}}]}; {array, Items} -> {fn_capture, none, oas@generator@ast:access(<<"json"/utf8>>, <<"array"/utf8>>), [], [{unlabelled_field, to_encoder(Items, Module)}]}; {tuple, Items@1} -> {fn, [{fn_parameter, {named, <<"data"/utf8>>}, none}], none, [{expression, encode_tuple_body( Items@1, {variable, <<"data"/utf8>>}, Module )}]}; {compound, Index} -> {variable, encode_fn( <<"internal_"/utf8, (erlang:integer_to_binary(Index))/binary>> )}; {dictionary, Values} -> {fn_capture, none, oas@generator@ast:access(<<"utils"/utf8>>, <<"dict"/utf8>>), [], [{unlabelled_field, to_encoder(Values, Module)}]}; unsupported -> {fn, [{fn_parameter, {named, <<"data"/utf8>>}, none}], none, [{expression, {variable, <<"data"/utf8>>}}]} end. -file("src/oas/generator/schema.gleam", 211). -spec fields_to_encode_body( list({binary(), {oas@generator@lift:schema(integer()), boolean()}}), list(binary()), gleam@option:option(oas@generator@lift:schema(integer())), oas@generator@misc:module_() ) -> glance:expression(). fields_to_encode_body(Properties, Required, Additional, Module) -> oas@generator@ast:call1( <<"utils"/utf8>>, <<"object"/utf8>>, {list, gleam@list:map( Properties, fun(Property) -> {Key, {Schema, Nullable}} = Property, Arg = oas@generator@ast:access( <<"data"/utf8>>, oas@generator@ast:name_for_gleam_field_or_var(Key) ), Cast = to_encoder(Schema, Module), Value = case not gleam@list:contains(Required, Key) orelse Nullable of false -> {call, Cast, [{unlabelled_field, Arg}]}; true -> oas@generator@ast:call2( <<"json"/utf8>>, <<"nullable"/utf8>>, Arg, Cast ) end, {tuple, [{string, Key}, Value]} end ), case Additional of {some, Values} -> {some, oas@generator@ast:call1( <<"dict"/utf8>>, <<"to_list"/utf8>>, oas@generator@ast:call2( <<"dict"/utf8>>, <<"map_values"/utf8>>, oas@generator@ast:access( <<"data"/utf8>>, <<"additional_properties"/utf8>> ), {fn, [{fn_parameter, {discarded, <<"key"/utf8>>}, none}, {fn_parameter, {named, <<"value"/utf8>>}, none}], none, [{expression, {call, to_encoder(Values, Module), [{unlabelled_field, {variable, <<"value"/utf8>>}}]}}]} ) )}; none -> none end} ). -file("src/oas/generator/schema.gleam", 156). ?DOC( " This handles top to encoder\n" " TODO make this private\n" ). -spec to_encode_fn( {binary(), oas@generator@lift:schema(oas@generator@lift:fields())}, oas@generator@misc:module_() ) -> glance:function_(). to_encode_fn(Entry, Module) -> {Name, Top} = Entry, Type_ = oas@generator@ast:name_for_gleam_type(Name), Arg = {variable, <<"data"/utf8>>}, Exp = case Top of {named, N} -> _pipe = to_encoder({named, N}, Module), {call, _pipe, [{unlabelled_field, Arg}]}; {primitive, null} -> oas@generator@ast:call0(<<"json"/utf8>>, <<"null"/utf8>>); {primitive, always} -> Arg; {primitive, never} -> {panic, {some, {string, <<"never value cannot be encoded"/utf8>>}}}; {primitive, P} -> _pipe@1 = to_encoder({primitive, P}, Module), {call, _pipe@1, [{unlabelled_field, Arg}]}; {array, Items} -> oas@generator@ast:call2( <<"json"/utf8>>, <<"array"/utf8>>, Arg, to_encoder(Items, Module) ); {tuple, Items@1} -> encode_tuple_body(Items@1, Arg, Module); {compound, {fields, Properties, Additional, Required}} -> fields_to_encode_body(Properties, Required, Additional, Module); {dictionary, Values} -> oas@generator@ast:call2( <<"utils"/utf8>>, <<"dict"/utf8>>, Arg, to_encoder(Values, Module) ); unsupported -> Arg end, Ignored = case Top of {primitive, null} -> true; {primitive, never} -> true; {compound, {fields, Properties@1, none, _}} -> gleam@list:is_empty(Properties@1); _ -> false end, Assignment = case Ignored of true -> {discarded, <<"data"/utf8>>}; false -> {named, <<"data"/utf8>>} end, {function, encode_fn(Name), public, [{function_parameter, none, Assignment, {some, {named_type, Type_, none, []}}}], none, [{expression, Exp}], {span, 0, 0}}. -file("src/oas/generator/schema.gleam", 15). ?DOC(" From a dictionary of schemas generate all custom types, type aliases, encoders and decoders.\n"). -spec generate(gleam@dict:dict(binary(), oas:schema())) -> {list(glance:custom_type()), list(glance:type_alias()), list(glance:function_())}. generate(Schemas) -> Module = schema, {Internal, Named} = gleam@list:map_fold( begin _pipe = Schemas, maps:to_list(_pipe) end, [], fun(Acc, Entry) -> {Name, Schema} = Entry, {Top, _, Acc@1} = oas@generator@lift:do_lift({inline, Schema}, Acc), {Acc@1, {Name, Top}} end ), Named@1 = lists:append( Named, begin _pipe@1 = Internal, _pipe@2 = lists:reverse(_pipe@1), gleam@list:index_map( _pipe@2, fun(Fields, Index) -> {<<"internal_"/utf8, (erlang:integer_to_binary(Index))/binary>>, {compound, Fields}} end ) end ), gleam@list:fold( Named@1, {[], [], []}, fun(Acc@2, Entry@1) -> {Name@1, Top@1} = Entry@1, {Custom_types, Type_aliases, Fns} = Acc@2, Fns@1 = lists:append( Fns, [to_encode_fn({Name@1, Top@1}, Module), to_decode_fn({Name@1, Top@1}, Module)] ), Name@2 = oas@generator@ast:name_for_gleam_type(Name@1), {Custom_types@1, Type_aliases@1} = case Top@1 of {named, Name@3} -> Type_ = to_type({named, Name@3}, Module), {Custom_types, [alias(Name@3, Type_) | Type_aliases]}; {primitive, Primitive} -> Type_@1 = to_type({primitive, Primitive}, Module), {Custom_types, [alias(Name@2, Type_@1) | Type_aliases]}; {array, Items} -> Type_@2 = to_type({array, Items}, Module), {Custom_types, [alias(Name@2, Type_@2) | Type_aliases]}; {tuple, Items@1} -> Type_@3 = to_type({tuple, Items@1}, Module), {Custom_types, [alias(Name@2, Type_@3) | Type_aliases]}; {compound, {fields, Properties, Additional, Required}} -> Type_@4 = custom_type( Name@2, Properties, Additional, Required, Module ), {[Type_@4 | Custom_types], Type_aliases}; {dictionary, Values} -> Type_@5 = to_type({dictionary, Values}, Module), {Custom_types, [alias(Name@2, Type_@5) | Type_aliases]}; unsupported -> Type_@6 = to_type(unsupported, Module), {Custom_types, [alias(Name@2, Type_@6) | Type_aliases]} end, {Custom_types@1, Type_aliases@1, Fns@1} end ).