-module(atproto_openapi@refs). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/atproto_openapi/refs.gleam"). -export([describe/1, canonicalize/2, component_key/1, schema_pointer/2, find_collisions/1]). -export_type([ref/0, ref_error/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( " Lexicon ref -> OpenAPI component-key resolution. Every `PropRef`/\n" " `ItemRef`/`BodySchemaRef`/union member string in the AST is one of\n" " three spellings (`#localName`, `nsid#name`, bare `nsid`); this module\n" " reduces all three to the canonical `Ref(nsid, name)` pair (bare `nsid`\n" " canonicalizes to `Ref(nsid, \"main\")`) and derives the `components/\n" " schemas` key from it. OpenAPI component keys must match\n" " `^[a-zA-Z0-9._-]+$`, so the lexicon's `#` separator cannot appear in\n" " one; `_` is used instead, which is safe because NSID segments and def\n" " names never contain `_`. Two distinct canonical refs landing on the\n" " same key is a collision, reported via `find_collisions` rather than\n" " silently merged, mirroring how `atproto_sdl`/`atproto_mlf` report NSID\n" " collisions. See `docs/lexicon-openapi.md` for the full scheme.\n" ). -type ref() :: {ref, binary(), binary()}. -type ref_error() :: empty_ref | {multiple_fragments, binary()} | {empty_local_name, binary()} | {key_collision, binary(), ref(), ref()}. -file("src/atproto_openapi/refs.gleam", 51). -spec ref_to_string(ref()) -> binary(). ref_to_string(Ref) -> <<<<(erlang:element(2, Ref))/binary, "#"/utf8>>/binary, (erlang:element(3, Ref))/binary>>. -file("src/atproto_openapi/refs.gleam", 35). -spec describe(ref_error()) -> binary(). describe(Error) -> case Error of empty_ref -> <<"empty ref"/utf8>>; {multiple_fragments, Raw} -> <<"malformed ref (more than one '#'): "/utf8, Raw/binary>>; {empty_local_name, Raw@1} -> <<"malformed ref (empty name after '#'): "/utf8, Raw@1/binary>>; {key_collision, Key, First, Second} -> <<<<<<<<<<<<"component key collision: "/utf8, (ref_to_string(First))/binary>>/binary, " and "/utf8>>/binary, (ref_to_string(Second))/binary>>/binary, " both map to \""/utf8>>/binary, Key/binary>>/binary, "\""/utf8>> end. -file("src/atproto_openapi/refs.gleam", 58). ?DOC( " Resolve a raw ref string against the NSID of the doc it appears in.\n" " `#foo` resolves to `Ref(doc_id, \"foo\")`; `nsid#foo` to `Ref(nsid,\n" " \"foo\")`; bare `nsid` to `Ref(nsid, \"main\")`.\n" ). -spec canonicalize(binary(), binary()) -> {ok, ref()} | {error, ref_error()}. canonicalize(Raw, Doc_id) -> case Raw of <<""/utf8>> -> {error, empty_ref}; _ -> case gleam@string:split(Raw, <<"#"/utf8>>) of [Nsid] -> {ok, {ref, Nsid, <<"main"/utf8>>}}; [Nsid@1, Name] -> case Name of <<""/utf8>> -> {error, {empty_local_name, Raw}}; _ -> case Nsid@1 of <<""/utf8>> -> {ok, {ref, Doc_id, Name}}; _ -> {ok, {ref, Nsid@1, Name}} end end; _ -> {error, {multiple_fragments, Raw}} end end. -file("src/atproto_openapi/refs.gleam", 80). ?DOC( " The `components/schemas` key for a canonical ref: `nsid` when `name`\n" " is `\"main\"`, else `nsid <> \"_\" <> name`.\n" ). -spec component_key(ref()) -> binary(). component_key(Ref) -> case erlang:element(3, Ref) of <<"main"/utf8>> -> erlang:element(2, Ref); Name -> <<<<(erlang:element(2, Ref))/binary, "_"/utf8>>/binary, Name/binary>> end. -file("src/atproto_openapi/refs.gleam", 89). ?DOC( " The `$ref` JSON pointer for a raw ref string, resolved against the\n" " containing doc's id: `#/components/schemas/`.\n" ). -spec schema_pointer(binary(), binary()) -> {ok, binary()} | {error, ref_error()}. schema_pointer(Raw, Doc_id) -> gleam@result:'try'( canonicalize(Raw, Doc_id), fun(Ref) -> {ok, <<"#/components/schemas/"/utf8, (component_key(Ref))/binary>>} end ). -file("src/atproto_openapi/refs.gleam", 111). -spec group_by_key(list(ref())) -> list({binary(), list(ref())}). group_by_key(Refs) -> gleam@list:fold( Refs, [], fun(Acc, Ref) -> Key = component_key(Ref), case gleam@list:key_find(Acc, Key) of {ok, Existing} -> gleam@list:key_set(Acc, Key, lists:append(Existing, [Ref])); {error, _} -> gleam@list:key_set(Acc, Key, [Ref]) end end ). -file("src/atproto_openapi/refs.gleam", 98). ?DOC( " Report every case where two distinct canonical refs land on the same\n" " component key, rather than letting the second silently overwrite the\n" " first in the emitted `components/schemas` map. Empty when `refs` has\n" " no collisions.\n" ). -spec find_collisions(list(ref())) -> list(ref_error()). find_collisions(Refs) -> _pipe = Refs, _pipe@1 = gleam@list:unique(_pipe), _pipe@2 = group_by_key(_pipe@1), gleam@list:flat_map( _pipe@2, fun(Group) -> {Key, Members} = Group, case Members of [First | Rest] -> gleam@list:map( Rest, fun(_capture) -> {key_collision, Key, First, _capture} end ); [] -> [] end end ).