-module(http_server_mock@internal@server_impl).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/http_server_mock/internal/server_impl.gleam").
-export([start_server/1, stop_server/1, add_stub/2, remove_stub/2, clear_stubs/1, get_stubs/1, get_requests/1, clear_requests/1]).
-export_type([server_state/0, server_message/0, handle/0]).
-type server_state() :: {server_state,
list(http_server_mock@types:stub()),
list(http_server_mock@types:recorded_request()),
gleam@dict:dict(binary(), binary())}.
-type server_message() :: {add_stub,
http_server_mock@types:stub(),
gleam@erlang@process:subject(binary())} |
{remove_stub, binary(), gleam@erlang@process:subject(nil)} |
{clear_stubs, gleam@erlang@process:subject(nil)} |
{get_stubs, gleam@erlang@process:subject(binary())} |
{match_request,
http_server_mock@types:recorded_request(),
gleam@erlang@process:subject(gleam@option:option(http_server_mock@types:response_definition()))} |
{get_requests, gleam@erlang@process:subject(binary())} |
{clear_requests, gleam@erlang@process:subject(nil)} |
shutdown.
-type handle() :: {handle,
gleam@erlang@process:subject(server_message()),
gleam@erlang@process:pid_()}.
-file("src/http_server_mock/internal/server_impl.gleam", 356).
-spec build_response(http_server_mock@types:response_definition()) -> gleam@http@response:response(mist:response_data()).
build_response(Response_def) ->
Body_tree = case erlang:element(4, Response_def) of
no_body ->
gleam@bytes_tree:new();
{string_body, Text} ->
gleam_stdlib:wrap_list(Text);
{raw_json_body, Json_text} ->
gleam_stdlib:wrap_list(Json_text);
{bytes_body, Bytes} ->
gleam@bytes_tree:from_bit_array(Bytes)
end,
Base_response = begin
_pipe = gleam@http@response:new(erlang:element(2, Response_def)),
gleam@http@response:set_body(_pipe, {bytes, Body_tree})
end,
gleam@list:fold(
erlang:element(3, Response_def),
Base_response,
fun(Current_response, Header_pair) ->
{Key, Value} = Header_pair,
gleam@http@response:set_header(Current_response, Key, Value)
end
).
-file("src/http_server_mock/internal/server_impl.gleam", 379).
-spec json_response(binary(), integer()) -> gleam@http@response:response(mist:response_data()).
json_response(Body, Status) ->
_pipe = gleam@http@response:new(Status),
_pipe@1 = gleam@http@response:set_header(
_pipe,
<<"content-type"/utf8>>,
<<"application/json"/utf8>>
),
gleam@http@response:set_body(_pipe@1, {bytes, gleam_stdlib:wrap_list(Body)}).
-file("src/http_server_mock/internal/server_impl.gleam", 433).
-spec now_ms() -> integer().
now_ms() ->
erlang:system_time(erlang:binary_to_atom(<<"millisecond"/utf8>>)).
-file("src/http_server_mock/internal/server_impl.gleam", 428).
-spec new_id() -> binary().
new_id() ->
<<"req_"/utf8, (erlang:integer_to_binary(erlang:unique_integer()))/binary>>.
-file("src/http_server_mock/internal/server_impl.gleam", 239).
-spec read_body_string(
gleam@http@request:request(mist@internal@http:connection())
) -> binary().
read_body_string(Mist_request) ->
case mist:read_body(Mist_request, 4194304) of
{ok, Request_with_body} ->
_pipe = erlang:element(4, Request_with_body),
_pipe@1 = gleam@bit_array:to_string(_pipe),
gleam@result:unwrap(_pipe@1, <<""/utf8>>);
{error, _} ->
<<""/utf8>>
end.
-file("src/http_server_mock/internal/server_impl.gleam", 250).
-spec handle_stub(
gleam@erlang@process:subject(server_message()),
gleam@http@request:request(mist@internal@http:connection())
) -> gleam@http@response:response(mist:response_data()).
handle_stub(Subject, Mist_request) ->
Body_string = read_body_string(Mist_request),
Headers_dict = gleam@list:fold(
erlang:element(3, Mist_request),
maps:new(),
fun(Header_dict, Header_pair) ->
{Key, Value} = Header_pair,
gleam@dict:insert(Header_dict, string:lowercase(Key), Value)
end
),
Recorded_request = {recorded_request,
new_id(),
erlang:element(2, Mist_request),
erlang:element(8, Mist_request),
erlang:element(9, Mist_request),
Headers_dict,
Body_string,
now_ms(),
none},
case gleam@erlang@process:call(
Subject,
5000,
fun(Reply_subject) ->
{match_request, Recorded_request, Reply_subject}
end
) of
none ->
json_response(
<<<<"{\"status\":404,\"message\":\"No stub matched\",\"path\":\""/utf8,
(erlang:element(8, Mist_request))/binary>>/binary,
"\"}"/utf8>>,
404
);
{some, Response_def} ->
case erlang:element(5, Response_def) of
{some, Milliseconds} ->
timer:sleep(Milliseconds);
none ->
nil
end,
build_response(Response_def)
end.
-file("src/http_server_mock/internal/server_impl.gleam", 297).
-spec handle_admin(
gleam@erlang@process:subject(server_message()),
gleam@http@request:request(mist@internal@http:connection())
) -> gleam@http@response:response(mist:response_data()).
handle_admin(Subject, Mist_request) ->
Body_string = read_body_string(Mist_request),
Path = erlang:element(8, Mist_request),
Method = erlang:element(2, Mist_request),
case {Method, Path} of
{get, <<"/__admin/health"/utf8>>} ->
json_response(<<"{\"status\":\"ok\"}"/utf8>>, 200);
{get, <<"/__admin/stubs"/utf8>>} ->
json_response(
gleam@erlang@process:call(
Subject,
5000,
fun(Reply_subject) -> {get_stubs, Reply_subject} end
),
200
);
{post, <<"/__admin/stubs"/utf8>>} ->
case http_server_mock@internal@json_codec:decode_stub(Body_string) of
{error, Error_message} ->
json_response(
<<<<"{\"error\":\""/utf8, Error_message/binary>>/binary,
"\"}"/utf8>>,
400
);
{ok, Stub} ->
Stub_id = gleam@erlang@process:call(
Subject,
5000,
fun(Reply_subject@1) ->
{add_stub, Stub, Reply_subject@1}
end
),
json_response(
<<<<"{\"id\":\""/utf8, Stub_id/binary>>/binary,
"\"}"/utf8>>,
201
)
end;
{delete, <<"/__admin/stubs"/utf8>>} ->
gleam@erlang@process:call(
Subject,
5000,
fun(Reply_subject@2) -> {clear_stubs, Reply_subject@2} end
),
json_response(<<"{\"status\":\"ok\"}"/utf8>>, 200);
{get, <<"/__admin/requests"/utf8>>} ->
json_response(
gleam@erlang@process:call(
Subject,
5000,
fun(Reply_subject@3) -> {get_requests, Reply_subject@3} end
),
200
);
{delete, <<"/__admin/requests"/utf8>>} ->
gleam@erlang@process:call(
Subject,
5000,
fun(Reply_subject@4) -> {clear_requests, Reply_subject@4} end
),
json_response(<<"{\"status\":\"ok\"}"/utf8>>, 200);
{_, _} ->
json_response(<<"{\"error\":\"Not found\"}"/utf8>>, 404)
end.
-file("src/http_server_mock/internal/server_impl.gleam", 228).
-spec handle_http(
gleam@erlang@process:subject(server_message()),
gleam@http@request:request(mist@internal@http:connection())
) -> gleam@http@response:response(mist:response_data()).
handle_http(Subject, Mist_request) ->
case gleam_stdlib:string_starts_with(
erlang:element(8, Mist_request),
<<"/__admin"/utf8>>
) of
true ->
handle_admin(Subject, Mist_request);
false ->
handle_stub(Subject, Mist_request)
end.
-file("src/http_server_mock/internal/server_impl.gleam", 404).
-spec advance_scenario(
gleam@dict:dict(binary(), binary()),
http_server_mock@types:stub()
) -> gleam@dict:dict(binary(), binary()).
advance_scenario(Scenarios, Stub) ->
case erlang:element(6, Stub) of
none ->
Scenarios;
{some, Scenario_state} ->
case erlang:element(4, Scenario_state) of
none ->
Scenarios;
{some, New_state} ->
gleam@dict:insert(
Scenarios,
erlang:element(2, Scenario_state),
New_state
)
end
end.
-file("src/http_server_mock/internal/server_impl.gleam", 389).
-spec insert_stub(
list(http_server_mock@types:stub()),
http_server_mock@types:stub()
) -> list(http_server_mock@types:stub()).
insert_stub(Stubs, New_stub) ->
Without_existing = gleam@list:filter(
Stubs,
fun(Stub) -> erlang:element(2, Stub) /= erlang:element(2, New_stub) end
),
gleam@list:sort(
[New_stub | Without_existing],
fun(Left, Right) ->
case erlang:element(3, Left) < erlang:element(3, Right) of
true ->
lt;
false ->
case erlang:element(3, Left) > erlang:element(3, Right) of
true ->
gt;
false ->
eq
end
end
end
).
-file("src/http_server_mock/internal/server_impl.gleam", 149).
-spec handle_message(server_state(), server_message()) -> gleam@otp@actor:next(server_state(), server_message()).
handle_message(State, Message) ->
case Message of
shutdown ->
gleam@otp@actor:stop();
{add_stub, Stub, Reply_subject} ->
gleam@erlang@process:send(Reply_subject, erlang:element(2, Stub)),
gleam@otp@actor:continue(
{server_state,
insert_stub(erlang:element(2, State), Stub),
erlang:element(3, State),
erlang:element(4, State)}
);
{remove_stub, Id, Reply_subject@1} ->
gleam@erlang@process:send(Reply_subject@1, nil),
gleam@otp@actor:continue(
{server_state,
gleam@list:filter(
erlang:element(2, State),
fun(Stub@1) -> erlang:element(2, Stub@1) /= Id end
),
erlang:element(3, State),
erlang:element(4, State)}
);
{clear_stubs, Reply_subject@2} ->
gleam@erlang@process:send(Reply_subject@2, nil),
gleam@otp@actor:continue(
{server_state,
[],
erlang:element(3, State),
erlang:element(4, State)}
);
{get_stubs, Reply_subject@3} ->
gleam@erlang@process:send(
Reply_subject@3,
http_server_mock@internal@json_codec:encode_stubs(
erlang:element(2, State)
)
),
gleam@otp@actor:continue(State);
{match_request, Recorded_request, Reply_subject@4} ->
case http_server_mock@internal@router:find_match(
erlang:element(2, State),
erlang:element(4, State),
Recorded_request
) of
none ->
Recorded = {recorded_request,
erlang:element(2, Recorded_request),
erlang:element(3, Recorded_request),
erlang:element(4, Recorded_request),
erlang:element(5, Recorded_request),
erlang:element(6, Recorded_request),
erlang:element(7, Recorded_request),
erlang:element(8, Recorded_request),
none},
gleam@erlang@process:send(Reply_subject@4, none),
gleam@otp@actor:continue(
{server_state,
erlang:element(2, State),
[Recorded | erlang:element(3, State)],
erlang:element(4, State)}
);
{some, {Stub@2, Response_def}} ->
Recorded@1 = {recorded_request,
erlang:element(2, Recorded_request),
erlang:element(3, Recorded_request),
erlang:element(4, Recorded_request),
erlang:element(5, Recorded_request),
erlang:element(6, Recorded_request),
erlang:element(7, Recorded_request),
erlang:element(8, Recorded_request),
{some, erlang:element(2, Stub@2)}},
Updated_scenarios = advance_scenario(
erlang:element(4, State),
Stub@2
),
gleam@erlang@process:send(
Reply_subject@4,
{some, Response_def}
),
gleam@otp@actor:continue(
{server_state,
erlang:element(2, State),
[Recorded@1 | erlang:element(3, State)],
Updated_scenarios}
)
end;
{get_requests, Reply_subject@5} ->
gleam@erlang@process:send(
Reply_subject@5,
http_server_mock@internal@json_codec:encode_recorded_requests(
erlang:element(3, State)
)
),
gleam@otp@actor:continue(State);
{clear_requests, Reply_subject@6} ->
gleam@erlang@process:send(Reply_subject@6, nil),
gleam@otp@actor:continue(
{server_state,
erlang:element(2, State),
[],
erlang:element(4, State)}
)
end.
-file("src/http_server_mock/internal/server_impl.gleam", 51).
-spec start_server(integer()) -> {ok, {integer(), gleam@dynamic:dynamic_()}} |
{error, binary()}.
start_server(Port) ->
Initial_state = {server_state, [], [], maps:new()},
gleam@result:'try'(
begin
_pipe = gleam@otp@actor:new(Initial_state),
_pipe@1 = gleam@otp@actor:on_message(_pipe, fun handle_message/2),
_pipe@2 = gleam@otp@actor:start(_pipe@1),
gleam@result:map_error(
_pipe@2,
fun(_) -> <<"Failed to start state actor"/utf8>> end
)
end,
fun(Started) ->
Subject = erlang:element(3, Started),
Port_channel = gleam@erlang@process:new_subject(),
Port_selector = begin
_pipe@3 = gleam_erlang_ffi:new_selector(),
gleam@erlang@process:select(_pipe@3, Port_channel)
end,
gleam@result:'try'(
begin
_pipe@4 = mist:new(
fun(Mist_request) ->
handle_http(Subject, Mist_request)
end
),
_pipe@5 = mist:port(_pipe@4, Port),
_pipe@6 = mist:bind(_pipe@5, <<"0.0.0.0"/utf8>>),
_pipe@7 = mist:after_start(
_pipe@6,
fun(Actual_port, _, _) ->
gleam@erlang@process:send(Port_channel, Actual_port)
end
),
_pipe@8 = mist:start(_pipe@7),
gleam@result:map_error(
_pipe@8,
fun(_) -> <<"Failed to start HTTP server"/utf8>> end
)
end,
fun(Mist_started) ->
Actual_port@1 = case gleam_erlang_ffi:select(
Port_selector,
5000
) of
{ok, Assigned_port} ->
Assigned_port;
{error, nil} ->
Port
end,
Server_handle = {handle,
Subject,
erlang:element(2, Mist_started)},
{ok, {Actual_port@1, gleam_stdlib:identity(Server_handle)}}
end
)
end
).
-file("src/http_server_mock/internal/server_impl.gleam", 87).
-spec stop_server(gleam@dynamic:dynamic_()) -> nil.
stop_server(Handle) ->
Server_handle = gleam_stdlib:identity(Handle),
gleam@erlang@process:send(erlang:element(2, Server_handle), shutdown),
gleam@erlang@process:send_exit(erlang:element(3, Server_handle)).
-file("src/http_server_mock/internal/server_impl.gleam", 94).
-spec add_stub(gleam@dynamic:dynamic_(), binary()) -> {ok, binary()} |
{error, binary()}.
add_stub(Handle, Stub_json) ->
Server_handle = gleam_stdlib:identity(Handle),
case http_server_mock@internal@json_codec:decode_stub(Stub_json) of
{error, Error_message} ->
{error, <<"Invalid stub JSON: "/utf8, Error_message/binary>>};
{ok, Stub} ->
Stub_id = gleam@erlang@process:call(
erlang:element(2, Server_handle),
5000,
fun(Reply_subject) -> {add_stub, Stub, Reply_subject} end
),
{ok, Stub_id}
end.
-file("src/http_server_mock/internal/server_impl.gleam", 109).
-spec remove_stub(gleam@dynamic:dynamic_(), binary()) -> nil.
remove_stub(Handle, Id) ->
Server_handle = gleam_stdlib:identity(Handle),
gleam@erlang@process:call(
erlang:element(2, Server_handle),
5000,
fun(Reply_subject) -> {remove_stub, Id, Reply_subject} end
).
-file("src/http_server_mock/internal/server_impl.gleam", 117).
-spec clear_stubs(gleam@dynamic:dynamic_()) -> nil.
clear_stubs(Handle) ->
Server_handle = gleam_stdlib:identity(Handle),
gleam@erlang@process:call(
erlang:element(2, Server_handle),
5000,
fun(Reply_subject) -> {clear_stubs, Reply_subject} end
).
-file("src/http_server_mock/internal/server_impl.gleam", 125).
-spec get_stubs(gleam@dynamic:dynamic_()) -> binary().
get_stubs(Handle) ->
Server_handle = gleam_stdlib:identity(Handle),
gleam@erlang@process:call(
erlang:element(2, Server_handle),
5000,
fun(Reply_subject) -> {get_stubs, Reply_subject} end
).
-file("src/http_server_mock/internal/server_impl.gleam", 133).
-spec get_requests(gleam@dynamic:dynamic_()) -> binary().
get_requests(Handle) ->
Server_handle = gleam_stdlib:identity(Handle),
gleam@erlang@process:call(
erlang:element(2, Server_handle),
5000,
fun(Reply_subject) -> {get_requests, Reply_subject} end
).
-file("src/http_server_mock/internal/server_impl.gleam", 141).
-spec clear_requests(gleam@dynamic:dynamic_()) -> nil.
clear_requests(Handle) ->
Server_handle = gleam_stdlib:identity(Handle),
gleam@erlang@process:call(
erlang:element(2, Server_handle),
5000,
fun(Reply_subject) -> {clear_requests, Reply_subject} end
).