-module(oaspec@openapi@dedup). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/oaspec/openapi/dedup.gleam"). -export([dedup_property_names/1, dedup_param_field_names/1, dedup_enum_variants/1, dedup/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. -file("src/oaspec/openapi/dedup.gleam", 161). ?DOC( " Pick the first `base_` suffix that collides neither with another\n" " literal input name nor with a name this call has already minted.\n" ). -spec next_unique_name( binary(), gleam@dict:dict(binary(), boolean()), gleam@dict:dict(binary(), boolean()), integer() ) -> binary(). next_unique_name(Base, Input_names, Claimed, Suffix) -> Candidate = <<<>/binary, (erlang:integer_to_binary(Suffix))/binary>>, case gleam@dict:has_key(Input_names, Candidate) orelse gleam@dict:has_key( Claimed, Candidate ) of true -> next_unique_name(Base, Input_names, Claimed, Suffix + 1); false -> Candidate end. -file("src/oaspec/openapi/dedup.gleam", 142). ?DOC( " Deduplicate a list of strings by appending \"_2\", \"_3\", etc. for duplicates.\n" " The chosen suffix skips any name that already appears elsewhere in the\n" " input (so a later literal `foo_2` keeps its label and an earlier\n" " duplicate `foo` advances to `foo_3`) and any suffix this call has\n" " already handed out, so the output has no collisions in either\n" " direction.\n" ). -spec deduplicate_strings(list(binary())) -> list(binary()). deduplicate_strings(Names) -> Input_names = gleam@list:fold( Names, maps:new(), fun(Acc, Name) -> gleam@dict:insert(Acc, Name, true) end ), {Result_rev, _} = gleam@list:fold( Names, {[], maps:new()}, fun(Acc@1, Name@1) -> {Result, Claimed} = Acc@1, case gleam@dict:has_key(Claimed, Name@1) of false -> {[Name@1 | Result], gleam@dict:insert(Claimed, Name@1, true)}; true -> Unique_name = next_unique_name( Name@1, Input_names, Claimed, 2 ), {[Unique_name | Result], gleam@dict:insert(Claimed, Unique_name, true)} end end ), lists:reverse(Result_rev). -file("src/oaspec/openapi/dedup.gleam", 101). ?DOC( " Given a list of original property names (JSON wire names), return a list\n" " of deduped snake_case Gleam field names. The returned list is parallel\n" " to the input: result[i] is the Gleam name for input[i].\n" ). -spec dedup_property_names(list(binary())) -> list(binary()). dedup_property_names(Prop_names) -> Snake_names = gleam@list:map( Prop_names, fun oaspec@util@naming:to_snake_case/1 ), deduplicate_strings(Snake_names). -file("src/oaspec/openapi/dedup.gleam", 117). ?DOC( " Given the parameters of a single operation, return a parallel list of\n" " deduped snake_case Gleam field names. Parameters whose wire names map to\n" " the same snake_case field (e.g. `id` in path AND `id` in query) get the\n" " same `_2`/`_3` suffix treatment used for property names. The reserved\n" " label `body` is taken first so a parameter literally named `body` is\n" " renamed instead of clashing with the request type's body field.\n" "\n" " The function is order-sensitive: the first occurrence keeps its base\n" " snake_case form, later occurrences get the suffix. Pass the parameters\n" " in the same order the spec lists them so type emission, server dispatch,\n" " and client builder agree on the final field name.\n" ). -spec dedup_param_field_names(list(oaspec@openapi@spec:parameter(any()))) -> list(binary()). dedup_param_field_names(Params) -> Snake_names = gleam@list:map( Params, fun(P) -> oaspec@util@naming:to_snake_case(erlang:element(2, P)) end ), With_body_reserved = [<<"body"/utf8>> | Snake_names], case deduplicate_strings(With_body_reserved) of [_ | Rest] -> Rest; _ -> [] end. -file("src/oaspec/openapi/dedup.gleam", 131). ?DOC( " Given a list of original enum values (JSON wire values), return a list\n" " of deduped PascalCase Gleam variant suffixes. The returned list is\n" " parallel to the input.\n" ). -spec dedup_enum_variants(list(binary())) -> list(binary()). dedup_enum_variants(Enum_values) -> Pascal_names = gleam@list:map( Enum_values, fun oaspec@util@naming:to_pascal_case/1 ), deduplicate_strings(Pascal_names). -file("src/oaspec/openapi/dedup.gleam", 53). -spec dedup_schema_object(oaspec@openapi@schema:schema_object()) -> oaspec@openapi@schema:schema_object(). dedup_schema_object(Schema_obj) -> case Schema_obj of {object_schema, _, Properties, _, Additional_properties, _, _} = Obj -> New_props = begin _pipe = maps:to_list(Properties), _pipe@1 = gleam@list:map( _pipe, fun(Entry) -> {Name, Prop_ref} = Entry, {Name, dedup_schema_ref(Prop_ref)} end ), maps:from_list(_pipe@1) end, {object_schema, erlang:element(2, Obj), New_props, erlang:element(4, Obj), case Additional_properties of {typed, Ap} -> {typed, dedup_schema_ref(Ap)}; forbidden -> forbidden; untyped -> untyped; unspecified -> unspecified end, erlang:element(6, Obj), erlang:element(7, Obj)}; {one_of_schema, Metadata, Schemas, Discriminator} -> {one_of_schema, Metadata, gleam@list:map(Schemas, fun dedup_schema_ref/1), Discriminator}; {any_of_schema, Metadata@1, Schemas@1, Discriminator@1} -> {any_of_schema, Metadata@1, gleam@list:map(Schemas@1, fun dedup_schema_ref/1), Discriminator@1}; _ -> Schema_obj end. -file("src/oaspec/openapi/dedup.gleam", 46). -spec dedup_schema_ref(oaspec@openapi@schema:schema_ref()) -> oaspec@openapi@schema:schema_ref(). dedup_schema_ref(Schema_ref) -> case Schema_ref of {reference, _, _} -> Schema_ref; {inline, Schema_obj} -> {inline, dedup_schema_object(Schema_obj)} end. -file("src/oaspec/openapi/dedup.gleam", 27). ?DOC(" Recurse into nested schemas within components (e.g. oneOf children).\n"). -spec dedup_schemas(oaspec@openapi@spec:open_api_spec(IKC)) -> oaspec@openapi@spec:open_api_spec(IKC). dedup_schemas(Spec) -> case erlang:element(5, Spec) of none -> Spec; {some, Components} -> New_schemas = begin _pipe = maps:to_list(erlang:element(2, Components)), _pipe@1 = gleam@list:map( _pipe, fun(Entry) -> {Name, Schema_ref} = Entry, {Name, dedup_schema_ref(Schema_ref)} end ), maps:from_list(_pipe@1) end, {open_api_spec, erlang:element(2, Spec), erlang:element(3, Spec), erlang:element(4, Spec), {some, {components, New_schemas, erlang:element(3, Components), erlang:element(4, Components), erlang:element(5, Components), erlang:element(6, Components), erlang:element(7, Components), erlang:element(8, Components), erlang:element(9, Components), erlang:element(10, Components), erlang:element(11, Components)}}, erlang:element(6, Spec), erlang:element(7, Spec), erlang:element(8, Spec), erlang:element(9, Spec), erlang:element(10, Spec), erlang:element(11, Spec)} end. -file("src/oaspec/openapi/dedup.gleam", 21). ?DOC( " Deduplicate names within schemas to avoid collisions in generated code.\n" " This is a pre-processing pass that runs after hoisting and before validation.\n" "\n" " Scope is intentionally limited: operationId / function-name uniqueness is\n" " enforced by `oaspec/codegen/validate.gleam` with a hard error, not by a\n" " silent rename, because renaming mutates the generated public API surface\n" " without telling the user (see issue #237). Property name and enum variant\n" " deduplication is done at codegen time via dedup_property_names/1 and\n" " dedup_enum_variants/1 to preserve JSON wire names.\n" ). -spec dedup(oaspec@openapi@spec:open_api_spec(IJZ)) -> oaspec@openapi@spec:open_api_spec(IJZ). dedup(Spec) -> _pipe = Spec, dedup_schemas(_pipe).