Current section

Files

Jump to
dream_http_client src dream_http_client@recorder.erl
Raw

src/dream_http_client@recorder.erl

-module(dream_http_client@recorder).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream_http_client/recorder.gleam").
-export([start/2, add_recording/2, is_record_mode/1, find_recording/2, get_recordings/1, stop/1]).
-export_type([recorder/0, mode/0, recorder_state/0, recorder_message/0, recorder_response/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(
" Recorder process and state management\n"
"\n"
" Manages HTTP request/response recordings using a process to store state.\n"
" Supports recording, playback, and passthrough modes.\n"
).
-opaque recorder() :: {recorder,
gleam@erlang@process:subject(recorder_message())}.
-type mode() :: {record, binary()} | {playback, binary()} | passthrough.
-type recorder_state() :: {recorder_state,
mode(),
binary(),
dream_http_client@matching:matching_config(),
gleam@dict:dict(binary(), dream_http_client@recording:recording())}.
-type recorder_message() :: {add_recording,
dream_http_client@recording:recording()} |
{find_recording,
dream_http_client@recording:recorded_request(),
gleam@erlang@process:subject(recorder_response())} |
{get_recordings, gleam@erlang@process:subject(recorder_response())} |
{check_mode, gleam@erlang@process:subject(recorder_response())} |
{stop, gleam@erlang@process:subject(recorder_response())}.
-type recorder_response() :: {found_recording,
gleam@option:option(dream_http_client@recording:recording())} |
{got_recordings, list(dream_http_client@recording:recording())} |
{mode_is_record, boolean()} |
{stopped, {ok, nil} | {error, binary()}}.
-file("src/dream_http_client/recorder.gleam", 90).
-spec wrap_recorder_subject(
gleam@otp@actor:started(gleam@erlang@process:subject(recorder_message()))
) -> recorder().
wrap_recorder_subject(Started) ->
{recorder, erlang:element(3, Started)}.
-file("src/dream_http_client/recorder.gleam", 96).
-spec convert_actor_error(gleam@otp@actor:start_error()) -> binary().
convert_actor_error(Error) ->
<<"Failed to start recorder: "/utf8, (gleam@string:inspect(Error))/binary>>.
-file("src/dream_http_client/recorder.gleam", 100).
-spec get_directory(mode()) -> binary().
get_directory(Mode) ->
case Mode of
{record, Dir} ->
Dir;
{playback, Dir@1} ->
Dir@1;
passthrough ->
<<""/utf8>>
end.
-file("src/dream_http_client/recorder.gleam", 108).
-spec build_recordings_map(
list(dream_http_client@recording:recording()),
dream_http_client@matching:matching_config()
) -> gleam@dict:dict(binary(), dream_http_client@recording:recording()).
build_recordings_map(Recordings, Config) ->
gleam@list:fold(
Recordings,
maps:new(),
fun(Acc, Rec) ->
Signature = dream_http_client@matching:build_signature(
erlang:element(2, Rec),
Config
),
gleam@dict:insert(Acc, Signature, Rec)
end
).
-file("src/dream_http_client/recorder.gleam", 118).
-spec handle_recorder_message(recorder_state(), recorder_message()) -> gleam@otp@actor:next(recorder_state(), recorder_message()).
handle_recorder_message(State, Message) ->
case Message of
{add_recording, Rec} ->
Signature = dream_http_client@matching:build_signature(
erlang:element(2, Rec),
erlang:element(4, State)
),
New_recordings = gleam@dict:insert(
erlang:element(5, State),
Signature,
Rec
),
New_state = {recorder_state,
erlang:element(2, State),
erlang:element(3, State),
erlang:element(4, State),
New_recordings},
gleam@otp@actor:continue(New_state);
{find_recording, Request, Reply_to} ->
Signature@1 = dream_http_client@matching:build_signature(
Request,
erlang:element(4, State)
),
case gleam_stdlib:map_get(erlang:element(5, State), Signature@1) of
{ok, Rec@1} ->
gleam@erlang@process:send(
Reply_to,
{found_recording, {some, Rec@1}}
),
gleam@otp@actor:continue(State);
{error, _} ->
gleam@erlang@process:send(Reply_to, {found_recording, none}),
gleam@otp@actor:continue(State)
end;
{get_recordings, Reply_to@1} ->
All_recordings = maps:values(erlang:element(5, State)),
gleam@erlang@process:send(
Reply_to@1,
{got_recordings, All_recordings}
),
gleam@otp@actor:continue(State);
{check_mode, Reply_to@2} ->
Is_record = case erlang:element(2, State) of
{record, _} ->
true;
_ ->
false
end,
gleam@erlang@process:send(Reply_to@2, {mode_is_record, Is_record}),
gleam@otp@actor:continue(State);
{stop, Reply_to@3} ->
case erlang:element(2, State) of
{record, Dir} ->
All_recordings@1 = maps:values(erlang:element(5, State)),
case dream_http_client@storage:save_recordings(
Dir,
All_recordings@1
) of
{ok, _} ->
gleam@erlang@process:send(
Reply_to@3,
{stopped, {ok, nil}}
);
{error, Reason} ->
gleam@erlang@process:send(
Reply_to@3,
{stopped, {error, Reason}}
)
end;
_ ->
gleam@erlang@process:send(Reply_to@3, {stopped, {ok, nil}})
end,
gleam@otp@actor:stop()
end.
-file("src/dream_http_client/recorder.gleam", 43).
?DOC(
" Start a new recorder in the specified mode\n"
"\n"
" Creates a process to manage recorder state internally.\n"
" Multiple requests can share the same recorder by passing the same handle.\n"
).
-spec start(mode(), dream_http_client@matching:matching_config()) -> {ok,
recorder()} |
{error, binary()}.
start(Mode, Matching_config) ->
Directory = get_directory(Mode),
Initial_state = {recorder_state,
Mode,
Directory,
Matching_config,
maps:new()},
case Mode of
{playback, Dir} ->
case dream_http_client@storage:load_recordings(Dir) of
{ok, Loaded} ->
Recordings_map = build_recordings_map(
Loaded,
Matching_config
),
State_with_recordings = {recorder_state,
Mode,
Dir,
Matching_config,
Recordings_map},
_pipe = gleam@otp@actor:new(State_with_recordings),
_pipe@1 = gleam@otp@actor:on_message(
_pipe,
fun handle_recorder_message/2
),
_pipe@2 = gleam@otp@actor:start(_pipe@1),
_pipe@3 = gleam@result:map(
_pipe@2,
fun wrap_recorder_subject/1
),
gleam@result:map_error(_pipe@3, fun convert_actor_error/1);
{error, Load_error} ->
{error,
<<"Failed to load recordings in playback mode: "/utf8,
Load_error/binary>>}
end;
_ ->
_pipe@4 = gleam@otp@actor:new(Initial_state),
_pipe@5 = gleam@otp@actor:on_message(
_pipe@4,
fun handle_recorder_message/2
),
_pipe@6 = gleam@otp@actor:start(_pipe@5),
_pipe@7 = gleam@result:map(_pipe@6, fun wrap_recorder_subject/1),
gleam@result:map_error(_pipe@7, fun convert_actor_error/1)
end.
-file("src/dream_http_client/recorder.gleam", 207).
?DOC(
" Add a recording to the recorder\n"
"\n"
" Only works in Record mode. In other modes, this is a no-op.\n"
).
-spec add_recording(recorder(), dream_http_client@recording:recording()) -> nil.
add_recording(Recorder, Rec) ->
{recorder, Subject} = Recorder,
gleam@erlang@process:send(Subject, {add_recording, Rec}).
-file("src/dream_http_client/recorder.gleam", 215).
?DOC(
" Check if recorder is in Record mode\n"
"\n"
" Returns true if the recorder is in Record mode, false otherwise.\n"
).
-spec is_record_mode(recorder()) -> boolean().
is_record_mode(Recorder) ->
{recorder, Subject} = Recorder,
Reply_subject = gleam@erlang@process:new_subject(),
gleam@erlang@process:send(Subject, {check_mode, Reply_subject}),
Selector = begin
_pipe = gleam_erlang_ffi:new_selector(),
gleam@erlang@process:select_map(
_pipe,
Reply_subject,
fun(Msg) -> Msg end
)
end,
case gleam_erlang_ffi:select(Selector, 1000) of
{ok, {mode_is_record, Is_record}} ->
Is_record;
{ok, _} ->
false;
{error, _} ->
false
end.
-file("src/dream_http_client/recorder.gleam", 243).
?DOC(
" Find a matching recording for a request\n"
"\n"
" Returns the matching recording if found, or None if not found.\n"
" Only works in Playback mode. In other modes, returns None.\n"
).
-spec find_recording(recorder(), dream_http_client@recording:recorded_request()) -> gleam@option:option(dream_http_client@recording:recording()).
find_recording(Recorder, Request) ->
{recorder, Subject} = Recorder,
Reply_subject = gleam@erlang@process:new_subject(),
gleam@erlang@process:send(Subject, {find_recording, Request, Reply_subject}),
Selector = begin
_pipe = gleam_erlang_ffi:new_selector(),
gleam@erlang@process:select_map(
_pipe,
Reply_subject,
fun(Msg) -> Msg end
)
end,
case gleam_erlang_ffi:select(Selector, 1000) of
{ok, {found_recording, Rec_opt}} ->
Rec_opt;
{ok, _} ->
none;
{error, _} ->
none
end.
-file("src/dream_http_client/recorder.gleam", 273).
?DOC(
" Get all recordings from the recorder\n"
"\n"
" Returns all recordings currently stored in the recorder.\n"
).
-spec get_recordings(recorder()) -> list(dream_http_client@recording:recording()).
get_recordings(Recorder) ->
{recorder, Subject} = Recorder,
Reply_subject = gleam@erlang@process:new_subject(),
gleam@erlang@process:send(Subject, {get_recordings, Reply_subject}),
Selector = begin
_pipe = gleam_erlang_ffi:new_selector(),
gleam@erlang@process:select_map(
_pipe,
Reply_subject,
fun(Msg) -> Msg end
)
end,
case gleam_erlang_ffi:select(Selector, 1000) of
{ok, {got_recordings, Recordings}} ->
Recordings;
{ok, _} ->
[];
{error, _} ->
[]
end.
-file("src/dream_http_client/recorder.gleam", 302).
?DOC(
" Stop the recorder and save recordings\n"
"\n"
" In Record mode, saves all recordings to disk before stopping.\n"
" In other modes, just stops the process.\n"
" Returns an error if saving fails.\n"
).
-spec stop(recorder()) -> {ok, nil} | {error, binary()}.
stop(Recorder) ->
{recorder, Subject} = Recorder,
Reply_subject = gleam@erlang@process:new_subject(),
gleam@erlang@process:send(Subject, {stop, Reply_subject}),
Selector = begin
_pipe = gleam_erlang_ffi:new_selector(),
gleam@erlang@process:select_map(
_pipe,
Reply_subject,
fun(Msg) -> Msg end
)
end,
case gleam_erlang_ffi:select(Selector, 5000) of
{ok, {stopped, Result}} ->
Result;
{ok, Unexpected_message} ->
{error,
<<"Unexpected response from recorder: "/utf8,
(gleam@string:inspect(Unexpected_message))/binary>>};
{error, _} ->
{error, <<"Recorder did not respond within 5 seconds"/utf8>>}
end.