Skip to main content

src/http_server_mock@stub_builder.erl

-module(http_server_mock@stub_builder).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/http_server_mock/stub_builder.gleam").
-export([new/0, matching/2, responding_with/2, with_id/2, with_priority/2, in_scenario/2, when_state_is/2, then_transition_to/2, build/1]).
-export_type([with_matcher/0, without_matcher/0, with_response/0, without_response/0, with_scenario/0, without_scenario/0, stub_builder/3]).

-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(
    " Builder for `Stub` — pairs a request matcher with a response definition.\n"
    "\n"
    " Start with `new()`, set a matcher via `matching()` and a response via\n"
    " `responding_with()`, then call `build()` to produce a `Stub` you can\n"
    " register with the server.\n"
    "\n"
    " Phantom types enforce that both `matching` and `responding_with` have been\n"
    " called: passing a partially-configured builder to `build` is a compile error.\n"
    "\n"
    " For stateful sequences, call `in_scenario` first, then optionally\n"
    " `when_state_is` and `then_transition_to` — calling the latter two without\n"
    " `in_scenario` is a compile error enforced by the `WithScenario` phantom type.\n"
    "\n"
    " ```gleam\n"
    " import gleam/http\n"
    " import http_server_mock/matcher\n"
    " import http_server_mock/response\n"
    " import http_server_mock/stub_builder\n"
    "\n"
    " let stub =\n"
    "   stub_builder.new()\n"
    "   |> stub_builder.matching(matcher.new() |> matcher.method(http.Post) |> matcher.path(\"/users\"))\n"
    "   |> stub_builder.responding_with(response.created())\n"
    "   |> stub_builder.build()\n"
    " ```\n"
).

-type with_matcher() :: any().

-type without_matcher() :: any().

-type with_response() :: any().

-type without_response() :: any().

-type with_scenario() :: any().

-type without_scenario() :: any().

-opaque stub_builder(FPG, FPH, FPI) :: {stub_builder,
        gleam@option:option(http_server_mock@types:request_matcher()),
        gleam@option:option(http_server_mock@types:response_definition()),
        gleam@option:option(binary()),
        integer(),
        gleam@option:option(http_server_mock@types:scenario_state())} |
    {gleam_phantom, FPG, FPH, FPI}.

-file("src/http_server_mock/stub_builder.gleam", 74).
?DOC(" Creates an empty stub builder with no matcher, response, or scenario set.\n").
-spec new() -> stub_builder(without_matcher(), without_response(), without_scenario()).
new() ->
    {stub_builder, none, none, none, 5, none}.

-file("src/http_server_mock/stub_builder.gleam", 85).
?DOC(" Sets the request matcher, transitioning the builder to `WithMatcher`.\n").
-spec matching(
    stub_builder(any(), FPN, FPO),
    http_server_mock@types:request_matcher()
) -> stub_builder(with_matcher(), FPN, FPO).
matching(Builder, Request_matcher) ->
    {stub_builder,
        {some, Request_matcher},
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        erlang:element(6, Builder)}.

-file("src/http_server_mock/stub_builder.gleam", 93).
?DOC(" Sets the response definition, transitioning the builder to `WithResponse`.\n").
-spec responding_with(
    stub_builder(FPV, any(), FPX),
    http_server_mock@types:response_definition()
) -> stub_builder(FPV, with_response(), FPX).
responding_with(Builder, Response_def) ->
    {stub_builder,
        erlang:element(2, Builder),
        {some, Response_def},
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        erlang:element(6, Builder)}.

-file("src/http_server_mock/stub_builder.gleam", 104).
?DOC(
    " Assigns a custom ID to this stub.\n"
    "\n"
    " IDs are used with `remove_stub` to unregister a specific stub. If not set,\n"
    " a unique ID is generated automatically.\n"
).
-spec with_id(stub_builder(FQE, FQF, FQG), binary()) -> stub_builder(FQE, FQF, FQG).
with_id(Builder, Id) ->
    {stub_builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        {some, Id},
        erlang:element(5, Builder),
        erlang:element(6, Builder)}.

-file("src/http_server_mock/stub_builder.gleam", 114).
?DOC(
    " Sets the priority for this stub.\n"
    "\n"
    " Lower values win when multiple stubs match a request. Defaults to `5`.\n"
).
-spec with_priority(stub_builder(FQN, FQO, FQP), integer()) -> stub_builder(FQN, FQO, FQP).
with_priority(Builder, Priority) ->
    {stub_builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        Priority,
        erlang:element(6, Builder)}.

-file("src/http_server_mock/stub_builder.gleam", 126).
?DOC(
    " Places this stub inside the named scenario, transitioning the builder to `WithScenario`.\n"
    "\n"
    " Scenarios allow a sequence of stubs to fire in order: the first time the\n"
    " matcher fires it transitions state; subsequent calls match the next stub.\n"
    " Must be called before `when_state_is` or `then_transition_to`.\n"
).
-spec in_scenario(stub_builder(FQW, FQX, any()), binary()) -> stub_builder(FQW, FQX, with_scenario()).
in_scenario(Builder, Name) ->
    Scenario = case erlang:element(6, Builder) of
        none ->
            {scenario_state, Name, none, none};

        {some, Existing} ->
            {scenario_state,
                Name,
                erlang:element(3, Existing),
                erlang:element(4, Existing)}
    end,
    {stub_builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        {some, Scenario}}.

-file("src/http_server_mock/stub_builder.gleam", 140).
?DOC(
    " Makes this stub only fire when the scenario is in the given state.\n"
    "\n"
    " Requires `in_scenario` to have been called first — enforced at compile time.\n"
).
-spec when_state_is(stub_builder(FRF, FRG, with_scenario()), binary()) -> stub_builder(FRF, FRG, with_scenario()).
when_state_is(Builder, State) ->
    Existing@1 = case erlang:element(6, Builder) of
        {some, Existing} -> Existing;
        _assert_fail ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                        file => <<?FILEPATH/utf8>>,
                        module => <<"http_server_mock/stub_builder"/utf8>>,
                        function => <<"when_state_is"/utf8>>,
                        line => 144,
                        value => _assert_fail,
                        start => 5427,
                        'end' => 5471,
                        pattern_start => 5438,
                        pattern_end => 5452})
    end,
    {stub_builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        {some,
            {scenario_state,
                erlang:element(2, Existing@1),
                {some, State},
                erlang:element(4, Existing@1)}}}.

-file("src/http_server_mock/stub_builder.gleam", 154).
?DOC(
    " Transitions the scenario to the given state after this stub fires.\n"
    "\n"
    " Requires `in_scenario` to have been called first — enforced at compile time.\n"
).
-spec then_transition_to(stub_builder(FRN, FRO, with_scenario()), binary()) -> stub_builder(FRN, FRO, with_scenario()).
then_transition_to(Builder, New_state) ->
    Existing@1 = case erlang:element(6, Builder) of
        {some, Existing} -> Existing;
        _assert_fail ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                        file => <<?FILEPATH/utf8>>,
                        module => <<"http_server_mock/stub_builder"/utf8>>,
                        function => <<"then_transition_to"/utf8>>,
                        line => 158,
                        value => _assert_fail,
                        start => 5926,
                        'end' => 5970,
                        pattern_start => 5937,
                        pattern_end => 5951})
    end,
    {stub_builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        {some,
            {scenario_state,
                erlang:element(2, Existing@1),
                erlang:element(3, Existing@1),
                {some, New_state}}}}.

-file("src/http_server_mock/stub_builder.gleam", 191).
-spec generate_id() -> binary().
generate_id() ->
    <<"stub_"/utf8, (erlang:integer_to_binary(erlang:unique_integer()))/binary>>.

-file("src/http_server_mock/stub_builder.gleam", 171).
?DOC(
    " Builds the concrete `Stub` from this builder.\n"
    "\n"
    " Only callable when both `matching` and `responding_with` have been called —\n"
    " the phantom types enforce this at compile time.\n"
    "\n"
    " Pass the result to `http_server_mock.with_stub` or `http_server_mock.add_stub`.\n"
).
-spec build(stub_builder(with_matcher(), with_response(), any())) -> http_server_mock@types:stub().
build(Builder) ->
    Matcher@1 = case erlang:element(2, Builder) of
        {some, Matcher} -> Matcher;
        _assert_fail ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                        file => <<?FILEPATH/utf8>>,
                        module => <<"http_server_mock/stub_builder"/utf8>>,
                        function => <<"build"/utf8>>,
                        line => 174,
                        value => _assert_fail,
                        start => 6454,
                        'end' => 6496,
                        pattern_start => 6465,
                        pattern_end => 6478})
    end,
    Response@1 = case erlang:element(3, Builder) of
        {some, Response} -> Response;
        _assert_fail@1 ->
            erlang:error(#{gleam_error => let_assert,
                        message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
                        file => <<?FILEPATH/utf8>>,
                        module => <<"http_server_mock/stub_builder"/utf8>>,
                        function => <<"build"/utf8>>,
                        line => 175,
                        value => _assert_fail@1,
                        start => 6499,
                        'end' => 6543,
                        pattern_start => 6510,
                        pattern_end => 6524})
    end,
    Id@1 = case erlang:element(4, Builder) of
        {some, Id} ->
            Id;

        none ->
            generate_id()
    end,
    {stub,
        Id@1,
        erlang:element(5, Builder),
        Matcher@1,
        Response@1,
        erlang:element(6, Builder)}.