-module(dream_http_client@storage). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/dream_http_client/storage.gleam"). -export([load_recordings/1, save_recordings/2, save_recording_immediately/2]). -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( " File I/O for recordings\n" "\n" " Handles loading and saving recording files to/from the filesystem.\n" ). -file("src/dream_http_client/storage.gleam", 162). -spec build_file_path(binary()) -> binary(). build_file_path(Directory) -> Normalized_directory = case gleam_stdlib:string_ends_with( Directory, <<"/"/utf8>> ) of true -> Directory; false -> <> end, <>. -file("src/dream_http_client/storage.gleam", 43). ?DOC( " Load recordings from a JSON file\n" "\n" " Reads and decodes recordings from `{directory}/recordings.json`. This function\n" " is used internally by the recorder when starting in Playback mode, but can also\n" " be called directly to inspect or manipulate recordings.\n" "\n" " ## Parameters\n" "\n" " - `directory`: The directory containing the `recordings.json` file\n" "\n" " ## Returns\n" "\n" " - `Ok(List(Recording))`: Successfully loaded recordings (empty list if file doesn't exist)\n" " - `Error(String)`: Error message if file exists but cannot be read or decoded\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " // Load recordings for inspection\n" " case storage.load_recordings(\"mocks/api\") {\n" " Ok(recordings) -> {\n" " io.println(\"Loaded \" <> int.to_string(list.length(recordings)) <> \" recordings\")\n" " }\n" " Error(reason) -> io.println_error(\"Failed to load: \" <> reason)\n" " }\n" " ```\n" "\n" " ## Notes\n" "\n" " - Returns an empty list (not an error) if the file doesn't exist\n" " - This is the expected behavior for Playback mode when no recordings have been created yet\n" " - File format must match the versioned JSON structure (see `recording.RecordingFile`)\n" ). -spec load_recordings(binary()) -> {ok, list(dream_http_client@recording:recording())} | {error, binary()}. load_recordings(Directory) -> File_path = build_file_path(Directory), case simplifile:read(File_path) of {ok, Content} -> case dream_http_client@recording:decode_recording_file(Content) of {ok, File} -> {ok, erlang:element(3, File)}; {error, Reason} -> {error, <<"Failed to decode recording file: "/utf8, Reason/binary>>} end; {error, enoent} -> {ok, []}; {error, Read_error} -> {error, <<"Failed to read recording file: "/utf8, (gleam@string:inspect(Read_error))/binary>>} end. -file("src/dream_http_client/storage.gleam", 116). ?DOC( " Save recordings to a JSON file\n" "\n" " Writes all recordings to `{directory}/recordings.json` in the versioned JSON format.\n" " Creates the directory if it doesn't exist. This function overwrites any existing\n" " recordings file.\n" "\n" " ## Parameters\n" "\n" " - `directory`: The directory where `recordings.json` will be written\n" " - `recordings`: List of recordings to save\n" "\n" " ## Returns\n" "\n" " - `Ok(Nil)`: Successfully saved all recordings\n" " - `Error(String)`: Error message if directory creation or file write fails\n" "\n" " ## Examples\n" "\n" " ```gleam\n" " let recordings = [\n" " create_test_recording(),\n" " create_another_recording(),\n" " ]\n" "\n" " case storage.save_recordings(\"mocks/api\", recordings) {\n" " Ok(_) -> io.println(\"Saved recordings successfully\")\n" " Error(reason) -> io.println_error(\"Failed to save: \" <> reason)\n" " }\n" " ```\n" "\n" " ## Notes\n" "\n" " - Directory is created automatically if it doesn't exist\n" " - Existing `recordings.json` file is overwritten (use `load_recordings` first to merge)\n" " - File format includes version field for future compatibility\n" ). -spec save_recordings(binary(), list(dream_http_client@recording:recording())) -> {ok, nil} | {error, binary()}. save_recordings(Directory, Recordings) -> File_path = build_file_path(Directory), _pipe = case simplifile:create_directory_all(Directory) of {ok, nil} -> {ok, nil}; {error, eexist} -> {ok, nil}; {error, Directory_error} -> {error, <<<<<<"Failed to create directory "/utf8, Directory/binary>>/binary, ": "/utf8>>/binary, (gleam@string:inspect(Directory_error))/binary>>} end, gleam@result:'try'( _pipe, fun(_) -> Recording_file = {recording_file, <<"1.0"/utf8>>, Recordings}, Json_value = dream_http_client@recording:encode_recording_file( Recording_file ), Json_string = gleam@json:to_string(Json_value), case simplifile:write(File_path, Json_string) of {ok, nil} -> {ok, nil}; {error, Write_error} -> {error, <<<<<<"Failed to write recording file "/utf8, File_path/binary>>/binary, ": "/utf8>>/binary, (gleam@string:inspect(Write_error))/binary>>} end end ). -file("src/dream_http_client/storage.gleam", 73). ?DOC( " Save a single recording immediately by appending to existing recordings\n" "\n" " Loads existing recordings, prepends the new recording, and saves all.\n" " This uses a read-modify-write approach that prioritizes reliability over performance.\n" "\n" " **Performance Tradeoff:** This function performs O(n) file I/O operations where n is\n" " the number of existing recordings. For typical use cases (recording once, playback often),\n" " this is acceptable. If you need high-performance recording with deferred saves or delta\n" " files, please create an issue at https://github.com/TrustBound/dream/issues.\n" ). -spec save_recording_immediately( binary(), dream_http_client@recording:recording() ) -> {ok, nil} | {error, binary()}. save_recording_immediately(Directory, Recording) -> gleam@result:'try'( load_recordings(Directory), fun(Existing) -> save_recordings(Directory, [Recording | Existing]) end ).