-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", 91). -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", 97). -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", 101). -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", 109). -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", 119). -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, Recording_value} -> gleam@erlang@process:send( Reply_to, {found_recording, {some, Recording_value}} ), gleam@otp@actor:continue(State); {error, Not_found} -> _ = Not_found, 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", 44). ?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", 210). ?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", 250). -spec identity_recorder_response(recorder_response()) -> recorder_response(). identity_recorder_response(Response) -> Response. -file("src/dream_http_client/recorder.gleam", 218). ?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 identity_recorder_response/1 ) end, case gleam_erlang_ffi:select(Selector, 1000) of {ok, {mode_is_record, Is_record}} -> Is_record; {ok, Unexpected_message} -> gleam_stdlib:println_error( <<"Recorder returned unexpected response to CheckMode: "/utf8, (gleam@string:inspect(Unexpected_message))/binary>> ), false; {error, Timeout_error} -> gleam_stdlib:println_error( <<"Recorder did not respond to CheckMode within 1 second: "/utf8, (gleam@string:inspect(Timeout_error))/binary>> ), false end. -file("src/dream_http_client/recorder.gleam", 258). ?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 identity_recorder_response/1 ) end, case gleam_erlang_ffi:select(Selector, 1000) of {ok, {found_recording, Rec_opt}} -> Rec_opt; {ok, Unexpected_message} -> gleam_stdlib:println_error( <<"Recorder returned unexpected response to FindRecording: "/utf8, (gleam@string:inspect(Unexpected_message))/binary>> ), none; {error, Timeout_error} -> gleam_stdlib:println_error( <<"Recorder did not respond to FindRecording within 1 second: "/utf8, (gleam@string:inspect(Timeout_error))/binary>> ), none end. -file("src/dream_http_client/recorder.gleam", 296). ?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 identity_recorder_response/1 ) end, case gleam_erlang_ffi:select(Selector, 1000) of {ok, {got_recordings, Recordings}} -> Recordings; {ok, Unexpected_message} -> gleam_stdlib:println_error( <<"Recorder returned unexpected response to GetRecordings: "/utf8, (gleam@string:inspect(Unexpected_message))/binary>> ), []; {error, Timeout_error} -> gleam_stdlib:println_error( <<"Recorder did not respond to GetRecordings within 1 second: "/utf8, (gleam@string:inspect(Timeout_error))/binary>> ), [] end. -file("src/dream_http_client/recorder.gleam", 333). ?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 identity_recorder_response/1 ) 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, Timeout_error} -> {error, <<"Recorder did not respond within 5 seconds: "/utf8, (gleam@string:inspect(Timeout_error))/binary>>} end.