### Elixir Example: Setup, Stubbing, and Assertion for Chuck Norris API Source: https://github.com/tank-bohr/bookish_spork/blob/master/README.md This Elixir code demonstrates a complete test case using ExUnit and bookish_spork. It sets up the server, stubs a request for a random joke, asserts the joke content returned by the API function, and verifies the captured request URI. It utilizes Elixir syntax for modules, functions, and assertions. ```elixir defmodule ChuckNorrisApiTest do use ExUnit.Case doctest ChuckNorrisApi setup do {:ok, _} = :bookish_spork.start_server() on_exit(fn -> :bookish_spork.stop_server() end) end test "retrieves a random joke" do :bookish_spork.stub_request([200, %{}, "{ \"value\": \"Chuck norris tried to crank that soulja boy but it wouldn\'t crank up\" }"]) assert ChuckNorrisApi.random == "Chuck norris tried to crank that soulja boy but it wouldn't crank up" {:ok, request} = :bookish_spork.capture_request() assert request.uri === "/jokes/random" end end ``` -------------------------------- ### Start Handler (Elixir) Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_sup.md Starts a handler with the provided supervisor, socket, TLS extensions, and connection ID. The return type is generic 'any()'. ```Elixir start_handler(Sup, Socket, TlsExt, ConnectionId) -> any() ``` -------------------------------- ### Start Acceptor Supervisor (Elixir) Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_sup.md Starts an acceptor supervisor for a given server, transport, and listen socket. It returns an {ok, pid()} tuple upon successful startup. ```Elixir start_acceptor_sup(Server, Transport, ListenSocket) -> {ok, pid()} --- Server = pid() Transport = gen_tcp | bookish_spork_ssl ListenSocket = term() ``` -------------------------------- ### Start Handler Supervisor (Elixir) Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_sup.md Starts a handler supervisor associated with a server and transport. The return type is generic 'any()'. ```Elixir start_handler_sup(Server, Transport) -> any() ``` -------------------------------- ### Start Bookish Spork Server Source: https://github.com/tank-bohr/bookish_spork/blob/master/README.md This code demonstrates how to start the bookish_spork server within your test suite. The server can be started on a default port or a specified port using bookish_spork:start_server/1. ```erlang bookish_spork:start_server(). ``` ```erlang bookish_spork:start_server(3000). ``` -------------------------------- ### Elixir: Stub and Capture HTTP Requests with Bookish Spork Source: https://context7.com/tank-bohr/bookish_spork/llms.txt This Elixir code demonstrates how to use bookish_spork within ExUnit tests. It shows how to start and stop the bookish_spork server, stub API responses for success and error scenarios, capture outgoing HTTP requests, and assert details of the captured requests like method, URI, and headers. It also includes an example of dynamic response stubbing based on request content. ```elixir defmodule MyApiClientTest do use ExUnit.Case setup do {:ok, _pid} = :bookish_spork.start_server() on_exit(fn -> :bookish_spork.stop_server() end) :ok end test "fetches user data from API" do # Stub the API response :bookish_spork.stub_request([ 200, %{"Content-Type" => "application/json"}, Jason.encode!(%{ id: 123, name: "Alice", email: "alice@example.com" }) ]) # Call your API client function {:ok, user} = MyApiClient.get_user(123) # Verify the response assert user.id == 123 assert user.name == "Alice" # Capture and verify the request {:ok, request} = :bookish_spork.capture_request() assert :bookish_spork_request.method(request) == :get assert :bookish_spork_request.uri(request) == "/users/123" # Check headers headers = :bookish_spork_request.headers(request) assert Map.has_key?(headers, "authorization") end test "handles API errors gracefully" do # Stub an error response :bookish_spork.stub_request([ 500, %{"Content-Type" => "application/json"}, Jason.encode!(%{error: "Internal Server Error"}) ]) # Verify error handling {:error, reason} = MyApiClient.get_user(456) assert reason == :server_error {:ok, request} = :bookish_spork.capture_request() assert :bookish_spork_request.uri(request) == "/users/456" end test "dynamic responses based on request" do # Stub with function for dynamic behavior :bookish_spork.stub_request(fn request -> uri = :bookish_spork_request.uri(request) case uri do "/api/public" -> [200, %{}, "Public data"] "/api/private" -> auth = :bookish_spork_request.header(request, "authorization") if auth == "Bearer valid-token" do [200, %{}, "Private data"] else [401, %{}, "Unauthorized"] end end end, 10) # Test with valid token {:ok, _} = MyApiClient.get_private_data("valid-token") # Test without token {:error, :unauthorized} = MyApiClient.get_private_data(nil) end end ``` -------------------------------- ### Start HTTP server Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork.md Starts an HTTP server on a specified port. Returns {ok, pid} on success or {error, Error} on failure. Accepts optional proplists for configuration. ```erlang start_server(Options :: proplists:proplist()) -> {ok, pid()} | {error, Error::term()} ``` -------------------------------- ### Start Server with Options in Elixir Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_server.md The 'start/1' function initializes the server with provided options, which must be a proplist. It returns an 'ok' tuple with the server's PID or an 'error' tuple. ```elixir start(Options::proplists:proplist()) -> {ok, pid()} | {error, Error::term()} ``` -------------------------------- ### Start blocking queue process Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_blocking_queue.md The 'start_link/0' function is used to start a new process for the blocking queue. It is typically called during system initialization or when a new queue is needed. The return type is 'any()', indicating flexibility in the return value. ```erlang start_link() -> any() ``` -------------------------------- ### Start Listening - Elixir Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_transport.md Initiates listening for incoming connections on a specified port. It requires a callback module to handle connection establishment and optionally takes port options. ```elixir listen(Module::callback_module(), Port::inet:port_number()) -> listen() ``` -------------------------------- ### Erlang: Setup and Teardown for Test Groups Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/README.md This snippet shows how to start and stop the bookish_spork server for test groups in Erlang. It ensures the server is running before tests and properly shut down afterwards. This is essential for isolating test environments. ```erlang init_per_group(_GroupName, Config) -> {ok, _} = bookish_spork:start_server(), Config. end_per_group(_GroupName, _Config) -> ok = bookish_spork:stop_server(). ``` -------------------------------- ### Start Bookish Spork Test Server (Erlang) Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/README.md This code demonstrates how to start the bookish_spork test server in your Erlang tests. The server can be started without a link, making it suitable for use in `init_per_group` or `init_per_suite` callbacks. The default port is 32002, but a custom port can be specified using `bookish_spork:start_server/1`. ```erlang bookish_spork:start_server(). ``` -------------------------------- ### Server Management API Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork.md Functions for starting, stopping, and managing the HTTP server. ```APIDOC ## POST /server/start ### Description Starts the HTTP server. Can be called with no arguments to start on the default port 32002, or with options. ### Method POST ### Endpoint /server/start ### Parameters #### Query Parameters - **port** (integer) - Optional - The port number to start the server on. - **options** (proplist) - Optional - A property list for additional server options. ### Request Example ```json { "port": 8080, "options": [{"ssl", true}, {"certfile", "/path/to/cert.pem"}] } ``` ### Response #### Success Response (200) - **pid** (pid) - The process ID of the started server. #### Response Example ```json { "pid": "<0.123.0>" } ``` ## DELETE /server/stop ### Description Stops the HTTP server. ### Method DELETE ### Endpoint /server/stop ### Response #### Success Response (200) - **status** (string) - Indicates the server has stopped successfully. #### Response Example ```json { "status": "ok" } ``` ``` -------------------------------- ### Start Link Function for bookish_spork_handler in Erlang Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_handler.md The start_link/2 function initiates a new process for the bookish_spork_handler. It takes the server PID and transport as arguments and returns an OK tuple with the process ID. This function is crucial for starting and linking the server process within the Erlang runtime. ```erlang start_link(Server, Transport) -> {ok, pid()} %% @spec Server :: pid() %% @spec Transport :: bookish_spork_transport:t() ``` -------------------------------- ### Get HTTP Method Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_request.md Retrieves the HTTP verb (method) of a request in lowercase. Examples include 'get', 'post', 'put', etc. The input is the request data type 't()'. The output is an atom representing the HTTP method. ```Erlang method(Request::t()) -> atom() ``` -------------------------------- ### Start Supervisor Process - Erlang Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_acceptor_sup.md The start_link/2 function is used to initiate the supervisor process for the bookish_spork_acceptor_sup module. It takes the server PID and a listening socket as arguments and returns an {ok, pid()} tuple upon successful startup. This function is typically called during application initialization. ```erlang start_link(Server, ListenSocket) -> {ok, pid()} ``` -------------------------------- ### Bookish Spork Acceptor Supervisor API Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_acceptor_sup.md This section details the functions available for interacting with the bookish_spork_acceptor_sup module, primarily focusing on starting the supervisor. ```APIDOC ## start_link/2 ### Description Starts the acceptor supervisor process. ### Method - (N/A - Function Call) ### Endpoint - (N/A - Function Call) ### Parameters #### Path Parameters - None #### Query Parameters - None #### Request Body - None ### Request Example - (N/A - Function Call) ### Response #### Success Response (ok) - **pid()** - The PID of the started supervisor process. #### Response Example ``` {ok, <0.123.456>} ``` ``` -------------------------------- ### Elixir: Setup/Teardown and Basic Request Stubbing Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/README.md This Elixir example demonstrates setting up and tearing down the bookish_spork server within an ExUnit test case. It also shows how to stub a simple JSON response for a random joke request and asserts the result of calling the API function. ```elixir defmodule ChuckNorrisApiTest do use ExUnit.Case doctest ChuckNorrisApi setup do {:ok, _} = :bookish_spork.start_server() on_exit(fn -> :bookish_spork.stop_server() end) end test "retrieves a random joke" do :bookish_spork.stub_request([200, %{}, "{ "value": "Chuck norris tried to crank that soulja boy but it wouldn't crank up" }"]) assert ChuckNorrisApi.random == "Chuck norris tried to crank that soulja boy but it wouldn't crank up" {:ok, request} = :bookish_spork.capture_request() assert request.uri === "/jokes/random" end end ``` -------------------------------- ### Install chuck_ex Elixir Package Source: https://github.com/tank-bohr/bookish_spork/blob/master/examples/chuck_ex/README.md This code snippet shows how to add the 'chuck_ex' Elixir package to your project's dependencies. It assumes you are using the 'mix' build tool and need to modify the 'deps' function in your 'mix.exs' file. Ensure you are using a compatible version of the package. ```elixir def deps do [ {:chuck_ex, "~> 0.1.0"} ] end ``` -------------------------------- ### Get Response from Server in Elixir Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_server.md The 'response/1' function takes a server PID and returns either an 'ok' tuple with a response or an 'error' tuple indicating no response. This function is used by 'bookish_spork_acceptor'. ```elixir response(Server::pid()) -> {ok, response()} | {error, no_response} ``` -------------------------------- ### Set Expectation for a Single Request in Erlang Source: https://github.com/tank-bohr/bookish_spork/blob/master/README.md This example demonstrates how to stub a specific HTTP request in Erlang. It sets up a mock response with a 200 status code, empty headers, and a JSON body. This is used to control the behavior of the application under test during test execution. ```erlang init_per_testcase(random_test, Config) -> bookish_spork:stub_request([200, #{ } <<"{\"value\": \"Chuck Norris' favourite word: chunk.\"}">>]), Config. ``` -------------------------------- ### Get SSL Information - Elixir Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_transport.md Retrieves detailed information about an SSL transport connection. This function returns a property list containing various SSL-related parameters. ```elixir ssl_info(Transport::t()) -> proplists:proplist() ``` -------------------------------- ### Testing HTTPS/SSL Requests in Erlang Source: https://context7.com/tank-bohr/bookish_spork/llms.txt This Erlang code demonstrates how to test HTTPS requests. It starts a server in SSL mode, stubs a request, makes an HTTPS request using the `httpc` module, and then captures and inspects the request. The code also retrieves and asserts on SSL/TLS information, including TLS extensions, which is crucial for verifying secure communication. ```erlang % Testing HTTPS endpoints init_per_testcase(https_test, Config) -> {ok, _} = bookish_spork:start_server([ssl]), Config. test_https_request(_Config) -> bookish_spork:stub_request([ 200, #{<<"Content-Type">> => <<"application/json">>}, <<"{\"secure\": true}">> ]), % Make HTTPS request (note: use appropriate SSL options in real tests) {ok, {{"HTTP/1.1", 200, "OK"}, _Headers, Body}} = httpc:request( get, {"https://localhost:32002/secure/endpoint", []}, [{ssl, [{verify, verify_none}]}], [] ), % Capture and inspect the request {ok, Request} = bookish_spork:capture_request(), ?assertEqual(get, bookish_spork_request:method(Request)), % Access SSL/TLS information SslInfo = bookish_spork_request:ssl_info(Request), ?assertNotEqual(nil, SslInfo), % Check TLS extensions if needed TlsExt = bookish_spork_request:tls_ext(Request), ?assertNotEqual(nil, TlsExt). ``` -------------------------------- ### Stub Requests Dynamically with a Function in Erlang Source: https://github.com/tank-bohr/bookish_spork/blob/master/README.md This Erlang example demonstrates stubbing requests using a function. The function receives the request object and returns the appropriate response based on the request's URI. This is useful when the response depends on the incoming request details. ```erlang bookish_spork:stub_request(fun(Request) -> case bookish_spork_request:uri(Request) of "/bookish/spork" -> [200, #{} , <<"Hello">>]; "/admin/sporks" -> [403, #{} , <<"It is not possible here">>] end end) ``` -------------------------------- ### Get SSL Extension Information - Elixir Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_transport.md Retrieves SSL protocol extension information for an SSL transport. This function is specific to SSL connections and provides details about negotiated extensions. ```elixir ssl_ext(Transport::t()) -> ssl:protocol_extensions() ``` -------------------------------- ### Erlang: Stub Requests Based on Request Conditions Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/README.md This Erlang example shows how to stub requests dynamically using a function. The provided function receives the request object and returns the appropriate response based on conditions, such as the request URI. This is useful when the order or details of requests cannot be predetermined. ```erlang bookish_spork:stub_request(fun(Request) -> case bookish_spork_request:uri(Request) of "/bookish/spork" -> [200, #{},)<"Hello">>]; "/admin/sporks" -> [403, #{},)<"It is not possible here">>] end end) ``` -------------------------------- ### Stub Requests with a Function and Fixed Response Count in Erlang Source: https://github.com/tank-bohr/bookish_spork/blob/master/README.md This Erlang example combines stubbing with a function and specifying a fixed number of times the response should be used. The provided function determines the response based on the request, and the `_Times` argument limits how many times this dynamic response logic will be applied. ```erlang bookish_spork:stub_request(fun(Req) -> Body = bookish_spork_request:body(Req), [200, #{}, Body] end, _Times = 150) ``` -------------------------------- ### Erlang: Stub Requests with a Function and Response Count Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/README.md This Erlang example combines stubbing requests with a function and a repetition count. The function dynamically generates responses for a specified number of requests, allowing for complex scenarios where responses depend on the request but need to be repeated. ```erlang bookish_spork:stub_request(fun(Req) -> Body = bookish_spork_request:body(Req), [200, #{<"X-Respond-With">> => <"echo">>}, Body] end, _Times = 150) ``` -------------------------------- ### Get Socket from Transport - Elixir Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_transport.md Retrieves the underlying socket (gen_tcp or ssl) from the transport abstraction. This can be useful for performing socket-specific operations. ```elixir socket(Transport::t()) -> socket() ``` -------------------------------- ### Get All Headers Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_request.md Retrieves all HTTP headers from a request as a map. Header names are normalized and lowercased. The input is the request data type 't()'. The output is a map where keys are header names and values are header contents. ```Erlang headers(Request::t()) -> map() ``` -------------------------------- ### Get Connection ID - Elixir Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_transport.md Retrieves the unique identifier for a given transport connection. This can be useful for logging or tracking specific connections. ```elixir connection_id(Transport::t()) -> binary() ``` -------------------------------- ### Get Raw Headers Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_request.md Retrieves the raw HTTP headers from a request, preserving their original order and case. The input is the request data type 't()'. The output is a proplist, providing a raw view of the headers. ```Erlang raw_headers(Request::t()) -> proplists:proplist() ``` -------------------------------- ### Erlang: Assert Testee Function Result and Side Effects Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/README.md This Erlang example shows how to make assertions after a testee function has been called. It verifies the return value of `testee:make_request()` and also captures and asserts attributes of an outgoing request made by the system under test. ```erlang random_test(_Config) -> ?assertEqual()<"Chuck Norris' favourite word: chunk.">>, testee:make_request()), {ok, Request} = bookish_spork:capture_request(), ?assertEqual("/jokes/random", bookish_spork_request:uri(Request)). ``` -------------------------------- ### Get Request Body Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_request.md Retrieves the raw body of an HTTP request. The input is a request data type 't()'. The output is a binary representation of the request body. ```Erlang body(Request::t()) -> binary() ``` -------------------------------- ### Get Request URI Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_request.md Extracts the URI (Uniform Resource Identifier) of a request, including the query string. The input is the request data type 't()'. The output is a binary representing the URI, or nil if not available. ```Erlang uri(Request::t()) -> binary() | nil ``` -------------------------------- ### Dynamic Response Stubs with Functions in Erlang Source: https://context7.com/tank-bohr/bookish_spork/llms.txt Stub HTTP requests with a function that dynamically generates responses based on the incoming request's properties such as URI, method, headers, or body. This allows for complex conditional logic in testing. The example shows stubbing based on URI and then on method and body, using `bookish_spork:stub_request/2` with an anonymous function. ```erlang % Conditional responses based on request URI test_dynamic_stub(_Config) -> bookish_spork:stub_request(fun(Request) -> case bookish_spork_request:uri(Request) of <<"/api/users">> -> [200, #{}, <<"{\"users\": []}">>]; <<"/api/admin">> -> [403, #{}, <<"{\"error\": \"Forbidden\"}">>]; <<"/api/health">> -> [200, #{}, <<"OK">>]; _ -> [404, #{}, <<"{\"error\": \"Not Found\"}">>] end end, _Times = 100), % Test different endpoints {ok, {_, _, UsersBody}} = httpc:request("http://localhost:32002/api/users"), ?assertMatch(<<"{\"users\":", _/binary>>, iolist_to_binary(UsersBody)), {ok, {{"HTTP/1.1", 403, _}, _, _}} = httpc:request("http://localhost:32002/api/admin"), {ok, {{"HTTP/1.1", 404, _}, _, _}} = httpc:request("http://localhost:32002/api/unknown"), % Dynamic response based on request method and body bookish_spork:stub_request(fun(Request) -> Method = bookish_spork_request:method(Request), Body = bookish_spork_request:body(Request), case Method of post -> % Echo the request body back [200, #{<<"X-Echo">> => <<"true">>}, Body]; get -> [200, #{}, <<"GET received">>]; delete -> [204, #{}, <<>>] end end, _Times = 50). ``` -------------------------------- ### Get HTTP Version Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_request.md Retrieves the HTTP protocol version used in the request. This is typically returned as a tuple, e.g., {1, 1}. The input is the request data type 't()'. The output is a tuple representing the version, or nil if not available. ```Erlang version(Request::t()) -> tuple() | nil ``` -------------------------------- ### Get Specific Header Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_request.md Retrieves a specific HTTP header from a request. The input includes the request data type 't()' and the header name (string or binary). The output is the header value as a binary, or nil if the header is not found. ```Erlang header(Request::t(), HeaderName::string() | binary()) -> binary() | nil ``` -------------------------------- ### Stub Same Response Multiple Times in Erlang Source: https://context7.com/tank-bohr/bookish_spork/llms.txt Stub a single HTTP response to be returned for multiple requests. This is useful for testing loops or repeated API calls. The `bookish_spork:stub_request/2` function is used with a specified number of times the response should be returned. The example demonstrates making 10 requests and verifying that each receives the same 'Pong' response. ```erlang % Stub same response for multiple requests test_repeated_stub(_Config) -> % Return the same response 10 times bookish_spork:stub_request([ 200, #{<<"Content-Type">> => <<"text/plain">>}, <<"Pong">> ], _Times = 10), % Make 10 requests, all get same response Results = [begin {ok, {_, _, Body}} = httpc:request("http://localhost:32002/ping"), string:trim(Body) end || _ <- lists:seq(1, 10)], % All should be "Pong" ?assertEqual(lists:duplicate(10, "Pong"), Results). ``` -------------------------------- ### Bookish Spork Handler API Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_handler.md This section details the functions available in the bookish_spork_handler module. It primarily focuses on the `start_link/2` function used for initiating a server process. ```APIDOC ## Bookish Spork Handler Module ### Description This module defines the behavior for the bookish spork handler, which integrates with the `gen_server` behavior. It exposes a function to start a new server process. ### Functions #### `start_link/2` ##### Description Initiates a new server process for the bookish spork handler. ##### Method `start_link` (Function Call) ##### Parameters ###### Path Parameters * None ###### Query Parameters * None ###### Request Body * None ##### Request Example `start_link(Server, Transport)` ##### Response ###### Success Response (ok) - `pid()`: The process identifier of the newly started server. ###### Response Example `{ok, <0.123.456>}` ###### Type Definitions - `Server`: `pid()` - `Transport`: `bookish_spork_transport:t()` ``` -------------------------------- ### bookish_spork_ssl Functions Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_ssl.md This section details the various functions available within the bookish_spork_ssl module for managing SSL connections, including accepting connections, closing sockets, retrieving connection information, listening for incoming connections, receiving data, sending data, setting options, and shutting down connections. ```APIDOC ## bookish_spork_ssl Functions This module provides functions for managing SSL/TLS connections. ### accept/1 **Description:** Accepts an incoming SSL connection. **Function Signature:** `accept(ListenSocket) -> any()` ### close/1 **Description:** Closes an SSL socket. **Function Signature:** `close(Socket) -> any()` ### connection_information/1 **Description:** Retrieves information about an SSL connection. **Function Signature:** `connection_information(Socket) -> any()` ### listen/2 **Description:** Starts listening for incoming SSL connections on a given port. **Function Signature:** `listen(Port, Options) -> any()` ### recv/2 **Description:** Receives data from an SSL socket. **Function Signature:** `recv(Socket, Length) -> any()` ### send/2 **Description:** Sends data over an SSL socket. **Function Signature:** `send(Socket, Data) -> any()` ### setopts/2 **Description:** Sets options for an SSL socket. **Function Signature:** `setopts(Socket, Options) -> any()` ### shutdown/2 **Description:** Shuts down an SSL connection. **Function Signature:** `shutdown(Socket, How) -> any()` ``` -------------------------------- ### Respond with Data in Elixir Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_server.md The 'respond_with/2' function takes a response and a non-negative integer for times, returning 'ok'. This function is likely used to send a processed response back to a client or another part of the system. ```elixir respond_with(Response::response(), Times::non_neg_integer()) -> ok ``` -------------------------------- ### Get Content-Length Header Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_request.md Extracts the Content-Length header value from an HTTP request. The input is a request data type 't()'. The output is an integer representing the value of the Content-Length header. ```Erlang content_length(Request::t()) -> integer() ``` -------------------------------- ### Inspecting Raw Headers in Erlang Source: https://context7.com/tank-bohr/bookish_spork/llms.txt This Erlang code demonstrates how to access and verify raw HTTP headers, including their original casing and order. It stubs a request, makes a request with specific header casing, captures the request, retrieves normalized and raw headers, and asserts on the presence and value of a specific raw header. This is especially useful when testing the behavior of HTTP clients that need to preserve header formatting. ```erlang % Testing header case sensitivity and order test_raw_headers(_Config) -> bookish_spork:stub_request([200, #{}, <<"OK">>]), % Make request with specific header casing {ok, _} = httpc:request(get, { "http://localhost:32002/api/resource", [ {"X-Custom-Header", "value1"}, {"Accept", "application/json"}, {"X-Another-Header", "value2"} ] }, [], []), {ok, Request} = bookish_spork:capture_request(), % Get normalized headers (lowercase keys) Headers = bookish_spork_request:headers(Request), ?assertMatch(#{<<"x-custom-header">> := _}, Headers), % Get raw headers (original case and order preserved) RawHeaders = bookish_spork_request:raw_headers(Request), % RawHeaders is a proplist like [{<<"X-Custom-Header">>, <<"value1">>}, ...] ?assert(lists:keymember(<<"X-Custom-Header">>, 1, RawHeaders)), % Verify specific raw header {<<"X-Custom-Header">>, Value} = lists:keyfind(<<"X-Custom-Header">>, 1, RawHeaders), ?assertEqual(<<"value1">>, Value). ``` -------------------------------- ### Capture Multiple HTTP Requests in Erlang Source: https://context7.com/tank-bohr/bookish_spork/llms.txt This Erlang code demonstrates how to capture and verify multiple HTTP requests made during a test. It stubs requests, makes several HTTP calls, captures all requests, and then asserts the number of requests and verifies their URIs and method/body for specific requests. This is useful for verifying that multiple calls are made as expected. ```erlang % Capture all requests made during a test test_capture_all_requests(_Config) -> % Stub to accept any request bookish_spork:stub_request([200, #{}, <<"OK">>], _Times = 5), % Make multiple different requests httpc:request("http://localhost:32002/endpoint1"), httpc:request("http://localhost:32002/endpoint2"), httpc:request(post, { "http://localhost:32002/endpoint3", [], "application/json", <<"{\"data\": \"test\"}">> }, [], []), httpc:request("http://localhost:32002/endpoint4?param=value"), % Capture all requests Requests = bookish_spork:capture_requests(), % Verify we got 4 requests ?assertEqual(4, length(Requests)), % Extract URIs from all requests URIs = [bookish_spork_request:uri(Req) || Req <- Requests], ?assertEqual([ <<"/endpoint1">>, <<"/endpoint2">>, <<"/endpoint3">>, <<"/endpoint4?param=value">> ], URIs), % Verify one specific request [_, _, PostRequest, _] = Requests, ?assertEqual(post, bookish_spork_request:method(PostRequest)), ?assertEqual(<<"{\"data\": \"test\"}">>, bookish_spork_request:body(PostRequest)). ``` -------------------------------- ### Stub Request (2 arguments) Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork.md Stubs a request with a specified response and the number of times it should be called. ```APIDOC ## stub_request/2 ### Description Stubs a request with a given response or a function to generate a response, and specifies the number of times the request should be stubbed. ### Method Not Applicable (Function Call) ### Endpoint Not Applicable (Function Call) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```elixir bookish_spork:stub_request(Response::stub_request_fun() | bookish_spork_response:response(), Times::non_neg_integer()) -> ok ``` `Response` can be: * A function: `fun((bookish_request:t()) -> bookish_spork_response:response()) * A response data structure: `bookish_spork_response:response()` #### Example Usage ```elixir bookish_spork:stub_request(fun(Request) -> case bookish_spork_request:uri(Request) of "/bookish/spork" -> [200, [], "Hello"]; "/admin/sporks" -> [403, [], "It is not possible here"] end end, 20) ``` ### Response #### Success Response (ok) Returns `ok` upon successful stubbing. #### Response Example `ok` ``` -------------------------------- ### Store Request in Elixir Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_server.md The 'store_request/2' function takes a server PID and a request, returning 'ok'. This function is used by 'bookish_spork_acceptor' to persist incoming requests. ```elixir store_request(Server::pid(), Request::request()) -> ok ``` -------------------------------- ### Stub Request (1 argument) Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork.md Stubs a request and returns a response. This is a shorthand for stub_request(Response, 1). ```APIDOC ## stub_request/1 ### Description Stubs a request and returns a response. This is a shorthand for `stub_request(Response, 1)`. ### Method Not Applicable (Function Call) ### Endpoint Not Applicable (Function Call) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```elixir bookish_spork:stub_request(Response::stub_request_fun() | bookish_spork_response:response()) -> ok ``` ### Response #### Success Response (ok) Returns `ok` upon successful stubbing. #### Response Example `ok` ``` -------------------------------- ### Stop Server in Elixir Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_server.md The 'stop/0' function is used to gracefully shut down the server, returning 'ok' upon successful termination. This is a simple function with no arguments. ```elixir stop() -> ok ``` -------------------------------- ### Response Data Types and Construction Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_response.md This section details the various data types used for responses and the `new/1` function for constructing response data structures. ```APIDOC ## Data Types ### `response()` Represents a response, which can be one of the following: * An HTTP status code (integer). * A tuple `{non_neg_integer(), map(), binary()}` representing {Status, Headers, Body}. * A tuple `{non_neg_integer(), list(), binary()}` representing {Status, Headers, Body}. * A nonempty list representing [Status, Headers, Body]. * A `t()` type, indicating the response is already a structured response object. ### `t()` An abstract data type representing a structured response object. ## Function Details ### `new/1` **Description:** Constructs a response data structure. **Method:** N/A (Function within a module) **Endpoint:** N/A **Parameters:** #### Path Parameters None #### Query Parameters None #### Request Body * **Response** (`response()`) - Required - The response data to be constructed. Can be an HTTP status code, a response tuple `{Status, Headers, Body}`, a response list `[Status, Headers, Body]`, or an existing `t()` type. **Request Example:** ```json { "example": "bookish_spork_response:new([200, #{}, <<\"Hello\">>])" } ``` **Response:** #### Success Response (200) * **`t()`** (`t()`) - The constructed response data structure. #### Response Example ```json { "example": "#Response{...}" } ``` ``` -------------------------------- ### Accept Connection - Elixir Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_transport.md Accepts an incoming connection on a given listen socket. This function is part of the transport behavior and is used to establish new client connections. ```elixir accept(Listen::listen()) -> t() ``` -------------------------------- ### TCP Socket Operations Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_tcp.md This section details the various functions available for interacting with TCP sockets, such as establishing connections, sending and receiving data, and managing socket options. ```APIDOC ## TCP Socket Operations This module provides functions for network socket programming. ### `accept/1` **Description**: Accepts an incoming connection on a listening socket. **Function Signature**: `accept(ListenSocket) -> any()` ### `close/1` **Description**: Closes an existing socket connection. **Function Signature**: `close(Socket) -> any()` ### `connection_information/1` **Description**: Retrieves information about a socket connection. **Function Signature**: `connection_information(Socket) -> any()` ### `listen/2` **Description**: Starts listening for incoming connections on a specified port with given options. **Function Signature**: `listen(Port, Options) -> any()` ### `recv/2` **Description**: Receives data from a socket connection. **Function Signature**: `recv(Socket, Length) -> any()` ### `send/2` **Description**: Sends data over a socket connection. **Function Signature**: `send(Socket, Data) -> any()` ### `setopts/2` **Description**: Sets options for a socket. **Function Signature**: `setopts(Socket, Options) -> any()` ### `shutdown/2` **Description**: Shuts down a socket connection in a specified manner. **Function Signature**: `shutdown(Socket, How) -> any() ``` -------------------------------- ### Capture all requests Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork.md Retrieves all requests that have been sent to the server, with an optional timeout. Returns a list of requests. ```erlang capture_requests(Timeout) -> [Request] %% Request = bookish_spork_request:t() %% Timeout = non_neg_integer() ``` -------------------------------- ### Formatting Response to String Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_response.md This section covers the `write_str/2` function used for formatting a response object into a binary string. ```APIDOC ### `write_str/2` **Description:** Formats a response data structure into a binary string. **Method:** N/A (Function within a module) **Endpoint:** N/A **Parameters:** #### Path Parameters None #### Query Parameters None #### Request Body * **Response** (`t()`) - Required - The response data structure to format. * **Now** (`calendar:datetime()`) - Required - The current datetime, likely used for timestamping or logging within the response formatting. **Request Example:** ```json { "example": "bookish_spork_response:write_str(ResponseObject, calendar:local_time())" } ``` **Response:** #### Success Response (200) * **binary()** - The formatted response as a binary string. #### Response Example ```json { "example": "<<"Formatted Response String">>" } ``` ``` -------------------------------- ### Stub Request with Function or Response (Erlang) Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork.md Defines a stub request that can be matched by a function or a specific response structure. This is useful for creating mock API endpoints. It takes a response definition and an optional number of times the stub should be used. ```erlang stub_request(Response::stub_request_fun() | bookish_spork_response:response()) -> ok ``` ```erlang stub_request(Response::stub_request_fun() | bookish_spork_response:response(), Times::non_neg_integer()) -> ok ``` ```erlang bookish_spork:stub_request(fun(Request) -> case bookish_spork_request:uri(Request) of "/bookish/spork" -> [200, [], <<"Hello">>]; "/admin/sporks" -> [403, [], <<"It is not possible here">>] end end, _Times = 20) ``` -------------------------------- ### Construct Response Data Structure (Erlang) Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_response.md The `new/1` function constructs a response data structure of type `t()`. It accepts various input formats for the response, including HTTP status codes, tuples, lists, or records. Headers can be provided as a map or a proplist. The function returns the constructed response data structure. ```erlang new(Response::response()) -> t() %% Example: %% bookish_spork_response:new([200, #{}, <<"Hello">>]) ``` -------------------------------- ### Stub a request with default response Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork.md Stubs a request with a default response (204 No Content) and predefined headers and an empty body. This is a convenience function equivalent to a more verbose call. ```erlang stub_request() -> ok ``` -------------------------------- ### Stop Supervisor (Elixir) Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_sup.md Stops a given supervisor. The return type is generic 'any()'. ```Elixir stop(Sup) -> any() ``` -------------------------------- ### Write Response to Binary (Erlang) Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_response.md The `write_str/2` function serializes a response data structure (`t()`) into a binary format. It takes the response structure and a datetime value as input and returns the binary representation of the response. The specific format of the binary output is determined by the internal implementation of the function. ```erlang write_str(Response::t(), Now::calendar:datetime()) -> binary() ``` -------------------------------- ### Bookish Spork Acceptor API Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_acceptor.md This section details the available functions within the bookish_spork_acceptor module. ```APIDOC ## Bookish Spork Acceptor Module API ### Description This module provides functions for generating child specifications and starting the acceptor process. ### Functions #### child_spec/1 ##### Description Generates a child specification for the acceptor process. ##### Method N/A (Function within a module) ##### Endpoint N/A ##### Parameters * **Args** (any()) - Description for Args parameter. #### start_link/2 ##### Description Starts a new instance of the acceptor server. ##### Method N/A (Function within a module) ##### Endpoint N/A ##### Parameters * **Server** (any()) - Description for Server parameter. * **ListenSocket** (any()) - Description for ListenSocket parameter. ### Request Example N/A (Module functions) ### Response #### Success Response (200) * **Result** (any()) - The result of the function call. #### Response Example N/A (Module functions) ``` -------------------------------- ### Reason Phrase Formatting Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork_format.md Formats an integer status code into its corresponding HTTP reason phrase. ```APIDOC ## reason_phrase/1 ### Description Formats an integer status code into its corresponding HTTP reason phrase. ### Method N/A (This is a function, not an API endpoint) ### Endpoint N/A ### Parameters #### Path Parameters N/A #### Query Parameters N/A #### Request Body N/A ### Request Example N/A ### Response #### Success Response (200) - **binary** (binary) - The HTTP reason phrase string. #### Response Example ``` "OK" ``` ``` -------------------------------- ### Request Capture API Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork.md Functions for capturing and retrieving requests sent to the server. ```APIDOC ## GET /requests/capture ### Description Retrieves a single request that was sent to the server. If multiple requests are pending, it returns the oldest one. Optionally, a timeout can be specified. ### Method GET ### Endpoint /requests/capture ### Parameters #### Query Parameters - **timeout** (non_neg_integer) - Optional - The maximum time in milliseconds to wait for a request. ### Request Example ```json { "timeout": 5000 } ``` ### Response #### Success Response (200) - **request** (object) - An object representing the captured request, with details like method, URL, headers, and body. #### Response Example ```json { "request": { "method": "POST", "url": "/api/users", "headers": { "Content-Type": "application/json" }, "body": "{\"name\": \"John Doe\"}" } } ``` ## GET /requests/capture/all ### Description Retrieves all requests that have been sent to the server since the server started or the last time this function was called. Optionally, a timeout can be specified to wait for at least one request. ### Method GET ### Endpoint /requests/capture/all ### Parameters #### Query Parameters - **timeout** (non_neg_integer) - Optional - The maximum time in milliseconds to wait for at least one request. ### Request Example ```json { "timeout": 10000 } ``` ### Response #### Success Response (200) - **requests** (array) - An array of objects, where each object represents a captured request. #### Response Example ```json { "requests": [ { "method": "GET", "url": "/api/items", "headers": {}, "body": "" }, { "method": "PUT", "url": "/api/items/123", "headers": { "Content-Type": "application/json" }, "body": "{\"price\": 100}" } ] } ``` ``` -------------------------------- ### Request Stubbing API Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/bookish_spork.md Functions for stubbing requests to simulate specific responses. ```APIDOC ## POST /stub/request ### Description Stubs a request to return a specific response. This function can be called with default values or with custom status codes, headers, and bodies. ### Method POST ### Endpoint /stub/request ### Parameters #### Request Body - **status_code** (integer) - Optional - The HTTP status code for the stubbed response. Defaults to 204. - **headers** (object) - Optional - A key-value map of headers for the stubbed response. Defaults to `{"Server": "BookishSpork/0.0.1", "Date": "Sat, 28 Apr 2018 05:51:50 GMT"}`. - **body** (string) - Optional - The response body for the stubbed request. Defaults to an empty string. - **response_fun** (function) - Optional - A function that generates the response dynamically. If provided, it overrides `status_code`, `headers`, and `body`. ### Request Example (Default) ```json { } ``` ### Request Example (Custom) ```json { "status_code": 404, "headers": { "Content-Type": "application/json" }, "body": "{\"message\": \"Not Found\"}" } ``` ### Request Example (with Function) ```json { "response_fun": "fun(Request -> {ok, 200, #{}, "Hello"})" } ``` ### Response #### Success Response (200) - **status** (string) - Indicates the request has been successfully stubbed. #### Response Example ```json { "status": "ok" } ``` ``` -------------------------------- ### Erlang: Stub Multiple Sequential Requests Source: https://github.com/tank-bohr/bookish_spork/blob/master/doc/README.md This Erlang snippet illustrates how to stub multiple outgoing requests when the order is known. Each `bookish_spork:stub_request/1` call defines the response for a subsequent request, processed in the order they are defined. ```erlang bookish_spork:stub_request([200, #{}, )<"{"value": "The first response"}">>]), bookish_spork:stub_request([200, #{}, )<"{"value": "The second response"}">>]). ```