Skip to main content

src/oaisp@internal@package_interface.erl

-module(oaisp@internal@package_interface).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/oaisp/internal/package_interface.gleam").
-export([decode_string/1, parse_format_lines/1, resolve_type/3, knows_module/2, malformed_format_lines/3]).
-export_type([error/0, resolved_type/0, field/0, field_type/0, format_directive/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(false).

-type error() :: {could_not_parse, gleam@json:decode_error()} |
    {module_not_found, binary()} |
    {type_not_found, binary(), binary()}.

-type resolved_type() :: {record_type,
        list(field()),
        gleam@option:option(binary())} |
    {enum_type, list(binary()), gleam@option:option(binary())} |
    {unmodelled, gleam@option:option(binary())}.

-type field() :: {field, binary(), field_type()}.

-type field_type() :: string_type |
    int_type |
    float_type |
    bool_type |
    nil_type |
    {list_type, field_type()} |
    {option_type, field_type()} |
    {dict_type, field_type()} |
    {tuple_type, list(field_type())} |
    {formatted_string_type, binary()} |
    timestamp_type |
    {ref_type, binary(), binary()} |
    any_type.

-type format_directive() :: {format_directive, binary(), binary()} |
    {malformed_format, binary()}.

-file("src/oaisp/internal/package_interface.gleam", 87).
?DOC(false).
-spec decode_string(binary()) -> {ok, gleam@package_interface:package()} |
    {error, error()}.
decode_string(Input) ->
    _pipe = gleam@json:parse(Input, gleam@package_interface:decoder()),
    gleam@result:map_error(
        _pipe,
        fun(Field@0) -> {could_not_parse, Field@0} end
    ).

-file("src/oaisp/internal/package_interface.gleam", 93).
?DOC(false).
-spec lookup_type(gleam@package_interface:package(), binary(), binary()) -> {ok,
        gleam@package_interface:type_definition()} |
    {error, error()}.
lookup_type(Package, Module, Name) ->
    gleam@result:'try'(
        begin
            _pipe = gleam_stdlib:map_get(erlang:element(5, Package), Module),
            gleam@result:replace_error(_pipe, {module_not_found, Module})
        end,
        fun(Found_module) ->
            _pipe@1 = gleam_stdlib:map_get(
                erlang:element(4, Found_module),
                Name
            ),
            gleam@result:replace_error(_pipe@1, {type_not_found, Module, Name})
        end
    ).

-file("src/oaisp/internal/package_interface.gleam", 159).
?DOC(false).
-spec classify_union(
    list(gleam@package_interface:type_constructor()),
    gleam@option:option(binary())
) -> resolved_type().
classify_union(Constructors, Documentation) ->
    case gleam@list:all(Constructors, fun(C) -> erlang:element(4, C) =:= [] end) of
        true ->
            {enum_type,
                gleam@list:map(
                    Constructors,
                    fun(C@1) -> erlang:element(3, C@1) end
                ),
                Documentation};

        false ->
            {unmodelled, Documentation}
    end.

-file("src/oaisp/internal/package_interface.gleam", 186).
?DOC(false).
-spec apply_format(field_type(), binary()) -> field_type().
apply_format(Base, Format) ->
    case Base of
        string_type ->
            {formatted_string_type, Format};

        {option_type, string_type} ->
            {option_type, {formatted_string_type, Format}};

        Other ->
            Other
    end.

-file("src/oaisp/internal/package_interface.gleam", 225).
?DOC(false).
-spec nth_type(list(gleam@package_interface:type()), integer()) -> field_type().
nth_type(Parameters, Index) ->
    case gleam@list:drop(Parameters, Index) of
        [Type_ | _] ->
            field_type(Type_);

        [] ->
            any_type
    end.

-file("src/oaisp/internal/package_interface.gleam", 204).
?DOC(false).
-spec named_type(
    binary(),
    binary(),
    binary(),
    list(gleam@package_interface:type())
) -> field_type().
named_type(Name, Package, Module, Parameters) ->
    case {Package, Module, Name} of
        {<<""/utf8>>, <<"gleam"/utf8>>, <<"String"/utf8>>} ->
            string_type;

        {<<""/utf8>>, <<"gleam"/utf8>>, <<"Int"/utf8>>} ->
            int_type;

        {<<""/utf8>>, <<"gleam"/utf8>>, <<"Float"/utf8>>} ->
            float_type;

        {<<""/utf8>>, <<"gleam"/utf8>>, <<"Bool"/utf8>>} ->
            bool_type;

        {<<""/utf8>>, <<"gleam"/utf8>>, <<"Nil"/utf8>>} ->
            nil_type;

        {<<""/utf8>>, <<"gleam"/utf8>>, <<"List"/utf8>>} ->
            {list_type, nth_type(Parameters, 0)};

        {<<"gleam_stdlib"/utf8>>, <<"gleam/option"/utf8>>, <<"Option"/utf8>>} ->
            {option_type, nth_type(Parameters, 0)};

        {<<"gleam_stdlib"/utf8>>, <<"gleam/dict"/utf8>>, <<"Dict"/utf8>>} ->
            {dict_type, nth_type(Parameters, 1)};

        {<<"gleam_time"/utf8>>,
            <<"gleam/time/timestamp"/utf8>>,
            <<"Timestamp"/utf8>>} ->
            timestamp_type;

        {_, _, _} ->
            {ref_type, Module, Name}
    end.

-file("src/oaisp/internal/package_interface.gleam", 194).
?DOC(false).
-spec field_type(gleam@package_interface:type()) -> field_type().
field_type(Type_) ->
    case Type_ of
        {named, Name, Package, Module, Parameters} ->
            named_type(Name, Package, Module, Parameters);

        {tuple, Elements} ->
            {tuple_type, gleam@list:map(Elements, fun field_type/1)};

        {variable, _} ->
            any_type;

        {fn, _, _} ->
            any_type
    end.

-file("src/oaisp/internal/package_interface.gleam", 169).
?DOC(false).
-spec field_from_param(
    gleam@package_interface:parameter(),
    gleam@dict:dict(binary(), binary())
) -> field().
field_from_param(Parameter, Formats) ->
    Label@1 = case erlang:element(2, Parameter) of
        {some, Label} -> Label;
        _assert_fail ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"record fields are checked to be labelled before this point"/utf8>>,
                        file => <<?FILEPATH/utf8>>,
                        module => <<"oaisp/internal/package_interface"/utf8>>,
                        function => <<"field_from_param"/utf8>>,
                        line => 173,
                        value => _assert_fail,
                        start => 6675,
                        'end' => 6715,
                        pattern_start => 6686,
                        pattern_end => 6697})
    end,
    Base = field_type(erlang:element(3, Parameter)),
    Type_ = case gleam_stdlib:map_get(Formats, Label@1) of
        {ok, Format} ->
            apply_format(Base, Format);

        {error, nil} ->
            Base
    end,
    {field, Label@1, Type_}.

-file("src/oaisp/internal/package_interface.gleam", 140).
?DOC(false).
-spec classify_product(
    gleam@package_interface:type_constructor(),
    gleam@option:option(binary()),
    gleam@dict:dict(binary(), binary())
) -> resolved_type().
classify_product(Constructor, Documentation, Formats) ->
    case gleam@list:all(
        erlang:element(4, Constructor),
        fun(P) -> gleam@option:is_some(erlang:element(2, P)) end
    ) of
        true ->
            {record_type,
                gleam@list:map(
                    erlang:element(4, Constructor),
                    fun(Parameter) -> field_from_param(Parameter, Formats) end
                ),
                Documentation};

        false ->
            {unmodelled, Documentation}
    end.

-file("src/oaisp/internal/package_interface.gleam", 300).
?DOC(false).
-spec parse_format_line(binary()) -> format_directive().
parse_format_line(Line) ->
    Body = begin
        _pipe = Line,
        _pipe@1 = gleam@string:trim_start(_pipe),
        _pipe@2 = gleam@string:drop_start(
            _pipe@1,
            string:length(<<"@format"/utf8>>)
        ),
        gleam@string:trim(_pipe@2)
    end,
    case gleam@string:split_once(Body, <<":"/utf8>>) of
        {ok, {Field, Format}} ->
            Field@1 = gleam@string:trim(Field),
            Format@1 = gleam@string:trim(Format),
            case ((Field@1 =:= <<""/utf8>>) orelse (Format@1 =:= <<""/utf8>>))
            orelse gleam_stdlib:contains_string(Field@1, <<" "/utf8>>) of
                true ->
                    {malformed_format, gleam@string:trim(Line)};

                false ->
                    {format_directive, Field@1, Format@1}
            end;

        {error, nil} ->
            {malformed_format, gleam@string:trim(Line)}
    end.

-file("src/oaisp/internal/package_interface.gleam", 282).
?DOC(false).
-spec is_format_line(binary()) -> boolean().
is_format_line(Line) ->
    Rest = begin
        _pipe = Line,
        _pipe@1 = gleam@string:trim_start(_pipe),
        gleam@string:split_once(_pipe@1, <<"@format"/utf8>>)
    end,
    case Rest of
        {ok, {<<""/utf8>>, After}} ->
            ((After =:= <<""/utf8>>) orelse gleam_stdlib:string_starts_with(
                After,
                <<" "/utf8>>
            ))
            orelse gleam_stdlib:string_starts_with(After, <<"\t"/utf8>>);

        _ ->
            false
    end.

-file("src/oaisp/internal/package_interface.gleam", 276).
?DOC(false).
-spec parse_format_lines(list(binary())) -> list(format_directive()).
parse_format_lines(Lines) ->
    _pipe = Lines,
    _pipe@1 = gleam@list:filter(_pipe, fun is_format_line/1),
    gleam@list:map(_pipe@1, fun parse_format_line/1).

-file("src/oaisp/internal/package_interface.gleam", 322).
?DOC(false).
-spec format_map(list(format_directive())) -> gleam@dict:dict(binary(), binary()).
format_map(Directives) ->
    _pipe = Directives,
    _pipe@1 = gleam@list:filter_map(_pipe, fun(Directive) -> case Directive of
                {format_directive, Field, Format} ->
                    {ok, {Field, Format}};

                {malformed_format, _} ->
                    {error, nil}
            end end),
    maps:from_list(_pipe@1).

-file("src/oaisp/internal/package_interface.gleam", 256).
?DOC(false).
-spec clean_doc(list(binary())) -> gleam@option:option(binary()).
clean_doc(Lines) ->
    Cleaned = begin
        _pipe = Lines,
        _pipe@1 = gleam@list:filter(
            _pipe,
            fun(Line) -> not is_format_line(Line) end
        ),
        _pipe@2 = gleam@string:join(_pipe@1, <<"\n"/utf8>>),
        gleam@string:trim(_pipe@2)
    end,
    case Cleaned of
        <<""/utf8>> ->
            none;

        _ ->
            {some, Cleaned}
    end.

-file("src/oaisp/internal/package_interface.gleam", 358).
?DOC(false).
-spec strip_one_leading_space(binary()) -> binary().
strip_one_leading_space(Line) ->
    case gleam_stdlib:string_starts_with(Line, <<" "/utf8>>) of
        true ->
            gleam@string:drop_start(Line, 1);

        false ->
            Line
    end.

-file("src/oaisp/internal/package_interface.gleam", 244).
?DOC(false).
-spec raw_doc_lines(gleam@option:option(binary())) -> list(binary()).
raw_doc_lines(Documentation) ->
    case Documentation of
        none ->
            [];

        {some, Text} ->
            _pipe = Text,
            _pipe@1 = gleam@string:split(_pipe, <<"\n"/utf8>>),
            gleam@list:map(_pipe@1, fun strip_one_leading_space/1)
    end.

-file("src/oaisp/internal/package_interface.gleam", 125).
?DOC(false).
-spec classify(gleam@package_interface:type_definition()) -> resolved_type().
classify(Definition) ->
    Raw = raw_doc_lines(erlang:element(2, Definition)),
    Documentation = clean_doc(Raw),
    Formats = format_map(parse_format_lines(Raw)),
    case erlang:element(5, Definition) of
        [] ->
            {unmodelled, Documentation};

        [Single] ->
            classify_product(Single, Documentation, Formats);

        Many ->
            classify_union(Many, Documentation)
    end.

-file("src/oaisp/internal/package_interface.gleam", 108).
?DOC(false).
-spec resolve_type(gleam@package_interface:package(), binary(), binary()) -> {ok,
        resolved_type()} |
    {error, error()}.
resolve_type(Package, Module, Name) ->
    gleam@result:map(
        lookup_type(Package, Module, Name),
        fun(Definition) -> classify(Definition) end
    ).

-file("src/oaisp/internal/package_interface.gleam", 121).
?DOC(false).
-spec knows_module(gleam@package_interface:package(), binary()) -> boolean().
knows_module(Package, Module) ->
    gleam@dict:has_key(erlang:element(5, Package), Module).

-file("src/oaisp/internal/package_interface.gleam", 338).
?DOC(false).
-spec malformed_format_lines(
    gleam@package_interface:package(),
    binary(),
    binary()
) -> list(binary()).
malformed_format_lines(Package, Module, Name) ->
    case lookup_type(Package, Module, Name) of
        {error, _} ->
            [];

        {ok, Definition} ->
            _pipe = erlang:element(2, Definition),
            _pipe@1 = raw_doc_lines(_pipe),
            _pipe@2 = parse_format_lines(_pipe@1),
            gleam@list:filter_map(_pipe@2, fun(Directive) -> case Directive of
                        {malformed_format, Line} ->
                            {ok, Line};

                        {format_directive, _, _} ->
                            {error, nil}
                    end end)
    end.