-module(oaspec@openapi@hoist). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/oaspec/openapi/hoist.gleam"). -export([hoist/1]). -export_type([hoist_state/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. -type hoist_state() :: {hoist_state, gleam@dict:dict(binary(), oaspec@openapi@schema:schema_ref()), gleam@dict:dict(binary(), nil), gleam@dict:dict(binary(), nil)}. -file("src/oaspec/openapi/hoist.gleam", 89). ?DOC(" Determine if a SchemaObject is complex and should be hoisted.\n"). -spec needs_hoisting(oaspec@openapi@schema:schema_object()) -> boolean(). needs_hoisting(Schema_obj) -> case Schema_obj of {object_schema, _, _, _, _, _, _} -> true; {all_of_schema, _, _} -> true; {one_of_schema, _, _, _} -> true; {any_of_schema, _, _, _} -> true; {array_schema, _, {inline, Inner}, _, _, _} -> needs_hoisting(Inner); _ -> false end. -file("src/oaspec/openapi/hoist.gleam", 128). ?DOC(" Try incrementing suffixes until a unique name is found.\n"). -spec find_unique_name(binary(), integer(), hoist_state()) -> {binary(), hoist_state()}. find_unique_name(Base_name, Suffix, State) -> Candidate = <>, Type_name = oaspec@util@naming:schema_to_type_name(Candidate), case gleam@dict:has_key(erlang:element(3, State), Candidate) orelse gleam@dict:has_key( erlang:element(4, State), Type_name ) of false -> State@1 = {hoist_state, erlang:element(2, State), gleam@dict:insert(erlang:element(3, State), Candidate, nil), gleam@dict:insert(erlang:element(4, State), Type_name, nil)}, {Candidate, State@1}; true -> find_unique_name(Base_name, Suffix + 1, State) end. -file("src/oaspec/openapi/hoist.gleam", 101). ?DOC(" Generate a unique schema name, handling collisions by appending 2, 3, etc.\n"). -spec make_unique_name(binary(), hoist_state()) -> {binary(), hoist_state()}. make_unique_name(Base_name, State) -> Type_name = oaspec@util@naming:schema_to_type_name(Base_name), case gleam@dict:has_key(erlang:element(3, State), Base_name) orelse gleam@dict:has_key( erlang:element(4, State), Type_name ) of false -> State@1 = {hoist_state, erlang:element(2, State), gleam@dict:insert(erlang:element(3, State), Base_name, nil), gleam@dict:insert(erlang:element(4, State), Type_name, nil)}, {Base_name, State@1}; true -> find_unique_name(Base_name, 2, State) end. -file("src/oaspec/openapi/hoist.gleam", 161). ?DOC( " Hoist a single SchemaRef if it is inline and complex.\n" " Returns the (possibly replaced) SchemaRef and updated state.\n" " The `origin` argument records where the hoisted schema came from so\n" " downstream tooling can distinguish synthetic schemas from user-authored\n" " components.\n" ). -spec hoist_schema_ref( oaspec@openapi@schema:schema_ref(), binary(), binary(), oaspec@openapi@schema:origin_kind(), hoist_state() ) -> {oaspec@openapi@schema:schema_ref(), hoist_state()}. hoist_schema_ref(Schema_ref, Name_prefix, Name_suffix, Origin, State) -> case Schema_ref of {reference, _, _} -> {Schema_ref, State}; {inline, Schema_obj} -> case needs_hoisting(Schema_obj) of false -> {Schema_ref, State}; true -> Base_name = <<(oaspec@util@naming:to_pascal_case( Name_prefix ))/binary, (oaspec@util@naming:to_pascal_case(Name_suffix))/binary>>, {Hoisted_obj, State@1} = hoist_within_schema( Schema_obj, Base_name, State ), Hoisted_obj@1 = oaspec@openapi@schema:set_provenance( Hoisted_obj, Origin ), {Unique_name, State@2} = make_unique_name( Base_name, State@1 ), State@3 = {hoist_state, gleam@dict:insert( erlang:element(2, State@2), Unique_name, {inline, Hoisted_obj@1} ), erlang:element(3, State@2), erlang:element(4, State@2)}, Ref = <<"#/components/schemas/"/utf8, Unique_name/binary>>, {oaspec@openapi@schema:make_reference(Ref), State@3} end end. -file("src/oaspec/openapi/hoist.gleam", 236). ?DOC(" Recursively hoist within a SchemaObject's children.\n"). -spec hoist_within_schema( oaspec@openapi@schema:schema_object(), binary(), hoist_state() ) -> {oaspec@openapi@schema:schema_object(), hoist_state()}. hoist_within_schema(Schema_obj, Name_prefix, State) -> case Schema_obj of {object_schema, _, Properties, _, Additional_properties, _, _} = Obj -> {New_props, State@3} = begin _pipe = maps:to_list(Properties), gleam@list:fold( _pipe, {maps:new(), State}, fun(Acc, Entry) -> {Props_acc, State@1} = Acc, {Prop_name, Prop_ref} = Entry, Origin = {hoisted_property, Name_prefix, Prop_name}, {Hoisted_ref, State@2} = hoist_schema_ref( Prop_ref, Name_prefix, Prop_name, Origin, State@1 ), {gleam@dict:insert(Props_acc, Prop_name, Hoisted_ref), State@2} end ) end, {New_ap, State@5} = case Additional_properties of {typed, Ap_ref} -> {Hoisted, State@4} = hoist_schema_ref( Ap_ref, Name_prefix, <<"Value"/utf8>>, {hoisted_additional_properties, Name_prefix}, State@3 ), {{typed, Hoisted}, State@4}; Other -> {Other, State@3} end, Result = {object_schema, erlang:element(2, Obj), New_props, erlang:element(4, Obj), New_ap, erlang:element(6, Obj), erlang:element(7, Obj)}, {Result, State@5}; {array_schema, _, Items, _, _, _} = Arr -> {Hoisted_items, State@6} = hoist_schema_ref( Items, Name_prefix, <<"Item"/utf8>>, {hoisted_array_item, Name_prefix}, State ), {{array_schema, erlang:element(2, Arr), Hoisted_items, erlang:element(4, Arr), erlang:element(5, Arr), erlang:element(6, Arr)}, State@6}; {one_of_schema, Metadata, Schemas, Discriminator} -> {Hoisted_schemas_rev, State@9} = gleam@list:index_fold( Schemas, {[], State}, fun(Acc@1, S_ref, Idx) -> {Schemas_acc, State@7} = Acc@1, Suffix = <<"Variant"/utf8, (erlang:integer_to_binary(Idx))/binary>>, {Hoisted@1, State@8} = hoist_schema_ref_always( S_ref, Name_prefix, Suffix, {hoisted_one_of_variant, Name_prefix, Idx}, State@7 ), {[Hoisted@1 | Schemas_acc], State@8} end ), {{one_of_schema, Metadata, lists:reverse(Hoisted_schemas_rev), Discriminator}, State@9}; {any_of_schema, Metadata@1, Schemas@1, Discriminator@1} -> {Hoisted_schemas_rev@1, State@12} = gleam@list:index_fold( Schemas@1, {[], State}, fun(Acc@2, S_ref@1, Idx@1) -> {Schemas_acc@1, State@10} = Acc@2, Suffix@1 = <<"Variant"/utf8, (erlang:integer_to_binary(Idx@1))/binary>>, {Hoisted@2, State@11} = hoist_schema_ref_always( S_ref@1, Name_prefix, Suffix@1, {hoisted_any_of_variant, Name_prefix, Idx@1}, State@10 ), {[Hoisted@2 | Schemas_acc@1], State@11} end ), {{any_of_schema, Metadata@1, lists:reverse(Hoisted_schemas_rev@1), Discriminator@1}, State@12}; {all_of_schema, Metadata@2, Schemas@2} -> {Hoisted_schemas_rev@2, State@16} = gleam@list:index_fold( Schemas@2, {[], State}, fun(Acc@3, S_ref@2, Idx@2) -> {Schemas_acc@2, State@13} = Acc@3, Suffix@2 = <<"Part"/utf8, (erlang:integer_to_binary(Idx@2))/binary>>, {Hoisted@3, State@14} = hoist_schema_ref( S_ref@2, Name_prefix, Suffix@2, {hoisted_all_of_part, Name_prefix, Idx@2}, State@13 ), State@15 = case Hoisted@3 of {reference, _, Name} -> case gleam_stdlib:map_get( erlang:element(2, State@14), Name ) of {ok, {inline, Obj@1}} -> {hoist_state, gleam@dict:insert( erlang:element(2, State@14), Name, {inline, oaspec@openapi@schema:set_internal( Obj@1 )} ), erlang:element(3, State@14), erlang:element(4, State@14)}; _ -> State@14 end; _ -> State@14 end, {[Hoisted@3 | Schemas_acc@2], State@15} end ), {{all_of_schema, Metadata@2, lists:reverse(Hoisted_schemas_rev@2)}, State@16}; _ -> {Schema_obj, State} end. -file("src/oaspec/openapi/hoist.gleam", 204). ?DOC( " Hoist a single SchemaRef unconditionally if it is inline.\n" " Used for oneOf/anyOf variants where codegen requires all variants to be $ref.\n" ). -spec hoist_schema_ref_always( oaspec@openapi@schema:schema_ref(), binary(), binary(), oaspec@openapi@schema:origin_kind(), hoist_state() ) -> {oaspec@openapi@schema:schema_ref(), hoist_state()}. hoist_schema_ref_always(Schema_ref, Name_prefix, Name_suffix, Origin, State) -> case Schema_ref of {reference, _, _} -> {Schema_ref, State}; {inline, Schema_obj} -> Base_name = <<(oaspec@util@naming:to_pascal_case(Name_prefix))/binary, (oaspec@util@naming:to_pascal_case(Name_suffix))/binary>>, {Hoisted_obj, State@1} = hoist_within_schema( Schema_obj, Base_name, State ), Hoisted_obj@1 = oaspec@openapi@schema:set_provenance( Hoisted_obj, Origin ), {Unique_name, State@2} = make_unique_name(Base_name, State@1), State@3 = {hoist_state, gleam@dict:insert( erlang:element(2, State@2), Unique_name, {inline, Hoisted_obj@1} ), erlang:element(3, State@2), erlang:element(4, State@2)}, Ref = <<"#/components/schemas/"/utf8, Unique_name/binary>>, {oaspec@openapi@schema:make_reference(Ref), State@3} end. -file("src/oaspec/openapi/hoist.gleam", 386). ?DOC(" Hoist schemas within component schemas.\n"). -spec hoist_component_schemas( gleam@dict:dict(binary(), oaspec@openapi@schema:schema_ref()), hoist_state() ) -> {gleam@dict:dict(binary(), oaspec@openapi@schema:schema_ref()), hoist_state()}. hoist_component_schemas(Schemas, State) -> _pipe = maps:to_list(Schemas), gleam@list:fold( _pipe, {maps:new(), State}, fun(Acc, Entry) -> {Result, State@1} = Acc, {Name, Schema_ref} = Entry, case Schema_ref of {inline, Schema_obj} -> {Hoisted_obj, State@2} = hoist_within_schema( Schema_obj, Name, State@1 ), {gleam@dict:insert(Result, Name, {inline, Hoisted_obj}), State@2}; {reference, _, _} -> {gleam@dict:insert(Result, Name, Schema_ref), State@1} end end ). -file("src/oaspec/openapi/hoist.gleam", 513). ?DOC(" Hoist complex schemas within operation parameters.\n"). -spec hoist_parameters( list(oaspec@openapi@spec:ref_or(oaspec@openapi@spec:parameter(OFC))), binary(), hoist_state() ) -> {list(oaspec@openapi@spec:ref_or(oaspec@openapi@spec:parameter(OFC))), hoist_state()}. hoist_parameters(Params, Op_id, State) -> gleam@list:fold( Params, {[], State}, fun(Acc, Ref_or_param) -> {Params_acc, State@1} = Acc, case Ref_or_param of {ref, _} -> {lists:append(Params_acc, [Ref_or_param]), State@1}; {value, Param} -> case erlang:element(6, Param) of {parameter_schema, Schema_ref} -> Suffix = <<"Param"/utf8, (oaspec@util@naming:to_pascal_case( erlang:element(2, Param) ))/binary>>, {Hoisted, State@2} = hoist_schema_ref( Schema_ref, Op_id, Suffix, {hoisted_parameter, Op_id, erlang:element(2, Param)}, State@1 ), New_param = {parameter, erlang:element(2, Param), erlang:element(3, Param), erlang:element(4, Param), erlang:element(5, Param), {parameter_schema, Hoisted}, erlang:element(7, Param), erlang:element(8, Param), erlang:element(9, Param), erlang:element(10, Param), erlang:element(11, Param)}, {lists:append(Params_acc, [{value, New_param}]), State@2}; {parameter_content, _} -> {lists:append(Params_acc, [Ref_or_param]), State@1} end end end ). -file("src/oaspec/openapi/hoist.gleam", 549). ?DOC(" Hoist schemas within a RequestBody.\n"). -spec hoist_request_body( oaspec@openapi@spec:request_body(OFJ), binary(), hoist_state() ) -> {oaspec@openapi@spec:request_body(OFJ), hoist_state()}. hoist_request_body(Rb, Op_id, State) -> {New_content, State@3} = begin _pipe = maps:to_list(erlang:element(3, Rb)), gleam@list:fold( _pipe, {maps:new(), State}, fun(Acc, Entry) -> {Result, State@1} = Acc, {Media_type_name, Media_type} = Entry, case erlang:element(2, Media_type) of {some, Schema_ref} -> {Hoisted, State@2} = hoist_schema_ref( Schema_ref, Op_id, <<"Request"/utf8>>, {hoisted_request_body, Op_id}, State@1 ), Mt = {media_type, {some, Hoisted}, erlang:element(3, Media_type), erlang:element(4, Media_type), erlang:element(5, Media_type)}, {gleam@dict:insert(Result, Media_type_name, Mt), State@2}; none -> {gleam@dict:insert(Result, Media_type_name, Media_type), State@1} end end ) end, {{request_body, erlang:element(2, Rb), New_content, erlang:element(4, Rb)}, State@3}. -file("src/oaspec/openapi/hoist.gleam", 579). ?DOC(" Hoist schemas within response definitions.\n"). -spec hoist_responses( gleam@dict:dict(oaspec@util@http:http_status_code(), oaspec@openapi@spec:ref_or(oaspec@openapi@spec:response(OFM))), binary(), hoist_state() ) -> {gleam@dict:dict(oaspec@util@http:http_status_code(), oaspec@openapi@spec:ref_or(oaspec@openapi@spec:response(OFM))), hoist_state()}. hoist_responses(Responses, Op_id, State) -> _pipe = maps:to_list(Responses), gleam@list:fold( _pipe, {maps:new(), State}, fun(Acc, Entry) -> {Result, State@1} = Acc, {Status_code, Ref_or_response} = Entry, case Ref_or_response of {ref, _} -> {gleam@dict:insert(Result, Status_code, Ref_or_response), State@1}; {value, Response} -> {New_content, State@4} = begin _pipe@1 = maps:to_list(erlang:element(3, Response)), gleam@list:fold( _pipe@1, {maps:new(), State@1}, fun(Ct_acc, Ct_entry) -> {Ct_result, State@2} = Ct_acc, {Media_type_name, Media_type} = Ct_entry, case erlang:element(2, Media_type) of {some, Schema_ref} -> Suffix = <<"Response"/utf8, (oaspec@util@http:status_code_suffix( Status_code ))/binary>>, {Hoisted, State@3} = hoist_schema_ref( Schema_ref, Op_id, Suffix, {hoisted_response, Op_id, oaspec@util@http:status_code_suffix( Status_code )}, State@2 ), Mt = {media_type, {some, Hoisted}, erlang:element(3, Media_type), erlang:element(4, Media_type), erlang:element(5, Media_type)}, {gleam@dict:insert( Ct_result, Media_type_name, Mt ), State@3}; none -> {gleam@dict:insert( Ct_result, Media_type_name, Media_type ), State@2} end end ) end, Hoisted_response = {response, erlang:element(2, Response), New_content, erlang:element(4, Response), erlang:element(5, Response)}, {gleam@dict:insert( Result, Status_code, {value, Hoisted_response} ), State@4} end end ). -file("src/oaspec/openapi/hoist.gleam", 485). ?DOC(" Hoist schemas within a single Operation.\n"). -spec hoist_operation( oaspec@openapi@spec:operation(OEZ), binary(), hoist_state() ) -> {oaspec@openapi@spec:operation(OEZ), hoist_state()}. hoist_operation(Operation, Op_id, State) -> {Parameters, State@1} = hoist_parameters( erlang:element(6, Operation), Op_id, State ), {Request_body, State@3} = case erlang:element(7, Operation) of none -> {none, State@1}; {some, {ref, R}} -> {{some, {ref, R}}, State@1}; {some, {value, Rb}} -> {Hoisted_rb, State@2} = hoist_request_body(Rb, Op_id, State@1), {{some, {value, Hoisted_rb}}, State@2} end, {Responses, State@4} = hoist_responses( erlang:element(8, Operation), Op_id, State@3 ), Result = {operation, erlang:element(2, Operation), erlang:element(3, Operation), erlang:element(4, Operation), erlang:element(5, Operation), Parameters, Request_body, Responses, erlang:element(9, Operation), erlang:element(10, Operation), erlang:element(11, Operation), erlang:element(12, Operation), erlang:element(13, Operation)}, {Result, State@4}. -file("src/oaspec/openapi/hoist.gleam", 460). ?DOC(" Hoist schemas in an optional operation.\n"). -spec hoist_maybe_operation( gleam@option:option(oaspec@openapi@spec:operation(OEU)), binary(), binary(), hoist_state() ) -> {gleam@option:option(oaspec@openapi@spec:operation(OEU)), hoist_state()}. hoist_maybe_operation(Maybe_op, Method, Path, State) -> case Maybe_op of none -> {none, State}; {some, Operation} -> Op_id = case erlang:element(2, Operation) of {some, Id} -> Id; none -> <<<>/binary, (begin _pipe = gleam@string:replace( Path, <<"/"/utf8>>, <<"_"/utf8>> ), _pipe@1 = gleam@string:replace( _pipe, <<"{"/utf8>>, <<""/utf8>> ), gleam@string:replace( _pipe@1, <<"}"/utf8>>, <<""/utf8>> ) end)/binary>> end, {Hoisted_op, State@1} = hoist_operation(Operation, Op_id, State), {{some, Hoisted_op}, State@1} end. -file("src/oaspec/openapi/hoist.gleam", 424). ?DOC(" Hoist schemas within a single PathItem.\n"). -spec hoist_path_item( oaspec@openapi@spec:path_item(OER), binary(), hoist_state() ) -> {oaspec@openapi@spec:path_item(OER), hoist_state()}. hoist_path_item(Path_item, Path, State) -> {Get, State@1} = hoist_maybe_operation( erlang:element(4, Path_item), <<"get"/utf8>>, Path, State ), {Post, State@2} = hoist_maybe_operation( erlang:element(5, Path_item), <<"post"/utf8>>, Path, State@1 ), {Put, State@3} = hoist_maybe_operation( erlang:element(6, Path_item), <<"put"/utf8>>, Path, State@2 ), {Delete, State@4} = hoist_maybe_operation( erlang:element(7, Path_item), <<"delete"/utf8>>, Path, State@3 ), {Patch, State@5} = hoist_maybe_operation( erlang:element(8, Path_item), <<"patch"/utf8>>, Path, State@4 ), {Head, State@6} = hoist_maybe_operation( erlang:element(9, Path_item), <<"head"/utf8>>, Path, State@5 ), {Options, State@7} = hoist_maybe_operation( erlang:element(10, Path_item), <<"options"/utf8>>, Path, State@6 ), {Trace, State@8} = hoist_maybe_operation( erlang:element(11, Path_item), <<"trace"/utf8>>, Path, State@7 ), Result = {path_item, erlang:element(2, Path_item), erlang:element(3, Path_item), Get, Post, Put, Delete, Patch, Head, Options, Trace, erlang:element(12, Path_item), erlang:element(13, Path_item)}, {Result, State@8}. -file("src/oaspec/openapi/hoist.gleam", 405). ?DOC(" Walk all paths and operations, hoisting inline schemas.\n"). -spec hoist_paths( gleam@dict:dict(binary(), oaspec@openapi@spec:ref_or(oaspec@openapi@spec:path_item(OEI))), hoist_state() ) -> {gleam@dict:dict(binary(), oaspec@openapi@spec:ref_or(oaspec@openapi@spec:path_item(OEI))), hoist_state()}. hoist_paths(Paths, State) -> _pipe = maps:to_list(Paths), gleam@list:fold( _pipe, {maps:new(), State}, fun(Acc, Entry) -> {Result, State@1} = Acc, {Path, Ref_or_path_item} = Entry, case Ref_or_path_item of {ref, _} -> {gleam@dict:insert(Result, Path, Ref_or_path_item), State@1}; {value, Path_item} -> {Hoisted_item, State@2} = hoist_path_item( Path_item, Path, State@1 ), {gleam@dict:insert(Result, Path, {value, Hoisted_item}), State@2} end end ). -file("src/oaspec/openapi/hoist.gleam", 35). ?DOC( " Hoist inline complex schemas into components.schemas, replacing them with $ref.\n" " This is a pre-processing pass that runs after parsing and before validation.\n" " The function is idempotent: running it twice produces the same result.\n" ). -spec hoist(oaspec@openapi@spec:open_api_spec(OEB)) -> oaspec@openapi@spec:open_api_spec(OEB). hoist(Spec) -> Existing_schemas = case erlang:element(5, Spec) of {some, Components} -> erlang:element(2, Components); none -> maps:new() end, Existing_names = begin _pipe = maps:keys(Existing_schemas), _pipe@1 = gleam@list:map(_pipe, fun(K) -> {K, nil} end), maps:from_list(_pipe@1) end, Existing_type_names = begin _pipe@2 = maps:keys(Existing_schemas), _pipe@3 = gleam@list:map( _pipe@2, fun(K@1) -> {oaspec@util@naming:schema_to_type_name(K@1), nil} end ), maps:from_list(_pipe@3) end, State = {hoist_state, maps:new(), Existing_names, Existing_type_names}, {Hoisted_component_schemas, State@1} = hoist_component_schemas( Existing_schemas, State ), {Hoisted_paths, State@2} = hoist_paths(erlang:element(4, Spec), State@1), Final_schemas = maps:merge( Hoisted_component_schemas, erlang:element(2, State@2) ), Components@1 = case erlang:element(5, Spec) of {some, C} -> {some, {components, Final_schemas, erlang:element(3, C), erlang:element(4, C), erlang:element(5, C), erlang:element(6, C), erlang:element(7, C), erlang:element(8, C), erlang:element(9, C), erlang:element(10, C)}}; none -> case gleam@dict:is_empty(erlang:element(2, State@2)) of true -> none; false -> {some, {components, Final_schemas, maps:new(), maps:new(), maps:new(), maps:new(), maps:new(), maps:new(), maps:new(), maps:new()}} end end, {open_api_spec, erlang:element(2, Spec), erlang:element(3, Spec), Hoisted_paths, Components@1, erlang:element(6, Spec), erlang:element(7, Spec), erlang:element(8, Spec), erlang:element(9, Spec), erlang:element(10, Spec), erlang:element(11, Spec)}.