-module(http_server_mock@matcher). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/http_server_mock/matcher.gleam"). -export([new/0, method/2, path/2, path_matching/2, path_contains/2, query_param/3, query_param_matching/3, header/3, header_matching/3, body_equal_to/2, body_containing/2, body_json/2, body_matcher/2, apply_string_matcher/2, matches/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( " Builder for `RequestMatcher` — the rules that decide whether an incoming\n" " request should be handled by a given stub.\n" "\n" " Start with `new()` and pipe through the constraint functions you need.\n" " Constraints that are not set match anything, so a `new()` with no\n" " constraints will match every request.\n" "\n" " ```gleam\n" " let m =\n" " matcher.new()\n" " |> matcher.method(http.Post)\n" " |> matcher.path(\"/orders\")\n" " |> matcher.header(\"x-api-key\", \"secret\")\n" " |> matcher.body_json(\"{\\\"amount\\\":100}\")\n" " ```\n" ). -file("src/http_server_mock/matcher.gleam", 29). ?DOC(" Returns a new `RequestMatcher` with no constraints — matches every request.\n"). -spec new() -> http_server_mock@types:request_matcher(). new() -> {request_matcher, none, none, [], [], any_body}. -file("src/http_server_mock/matcher.gleam", 40). ?DOC(" Constrains the matcher to only match requests with the given HTTP method.\n"). -spec method(http_server_mock@types:request_matcher(), gleam@http:method()) -> http_server_mock@types:request_matcher(). method(Request_matcher, Method) -> {request_matcher, {some, Method}, erlang:element(3, Request_matcher), erlang:element(4, Request_matcher), erlang:element(5, Request_matcher), erlang:element(6, Request_matcher)}. -file("src/http_server_mock/matcher.gleam", 51). ?DOC( " Constrains the matcher to only match requests whose path is exactly equal\n" " to `path`.\n" "\n" " Use `path_matching` or `path_contains` for partial matches.\n" ). -spec path(http_server_mock@types:request_matcher(), binary()) -> http_server_mock@types:request_matcher(). path(Request_matcher, Path) -> {request_matcher, erlang:element(2, Request_matcher), {some, {exact, Path}}, erlang:element(4, Request_matcher), erlang:element(5, Request_matcher), erlang:element(6, Request_matcher)}. -file("src/http_server_mock/matcher.gleam", 60). ?DOC( " Constrains the matcher to only match requests whose path satisfies the\n" " given `StringMatcher`.\n" "\n" " Use this when you need `Contains`, `Prefix`, or `Suffix` path matching\n" " instead of an exact match.\n" ). -spec path_matching( http_server_mock@types:request_matcher(), http_server_mock@types:string_matcher() ) -> http_server_mock@types:request_matcher(). path_matching(Request_matcher, String_matcher) -> {request_matcher, erlang:element(2, Request_matcher), {some, String_matcher}, erlang:element(4, Request_matcher), erlang:element(5, Request_matcher), erlang:element(6, Request_matcher)}. -file("src/http_server_mock/matcher.gleam", 69). ?DOC( " Constrains the matcher to only match requests whose path contains\n" " `fragment` as a substring.\n" ). -spec path_contains(http_server_mock@types:request_matcher(), binary()) -> http_server_mock@types:request_matcher(). path_contains(Request_matcher, Fragment) -> {request_matcher, erlang:element(2, Request_matcher), {some, {contains, Fragment}}, erlang:element(4, Request_matcher), erlang:element(5, Request_matcher), erlang:element(6, Request_matcher)}. -file("src/http_server_mock/matcher.gleam", 80). ?DOC( " Constrains the matcher to only match requests that have the query parameter\n" " `key` set to exactly `value`.\n" "\n" " Can be called multiple times to require several query parameters.\n" ). -spec query_param(http_server_mock@types:request_matcher(), binary(), binary()) -> http_server_mock@types:request_matcher(). query_param(Request_matcher, Key, Value) -> {request_matcher, erlang:element(2, Request_matcher), erlang:element(3, Request_matcher), [{Key, {exact, Value}} | erlang:element(4, Request_matcher)], erlang:element(5, Request_matcher), erlang:element(6, Request_matcher)}. -file("src/http_server_mock/matcher.gleam", 95). ?DOC( " Constrains the matcher to only match requests that have the query parameter\n" " `key` satisfying the given `StringMatcher`.\n" "\n" " Can be called multiple times to require several query parameters.\n" ). -spec query_param_matching( http_server_mock@types:request_matcher(), binary(), http_server_mock@types:string_matcher() ) -> http_server_mock@types:request_matcher(). query_param_matching(Request_matcher, Key, String_matcher) -> {request_matcher, erlang:element(2, Request_matcher), erlang:element(3, Request_matcher), [{Key, String_matcher} | erlang:element(4, Request_matcher)], erlang:element(5, Request_matcher), erlang:element(6, Request_matcher)}. -file("src/http_server_mock/matcher.gleam", 110). ?DOC( " Constrains the matcher to only match requests that have the header `key`\n" " set to exactly `value`. Header names are compared case-insensitively.\n" "\n" " Can be called multiple times to require several headers.\n" ). -spec header(http_server_mock@types:request_matcher(), binary(), binary()) -> http_server_mock@types:request_matcher(). header(Request_matcher, Key, Value) -> {request_matcher, erlang:element(2, Request_matcher), erlang:element(3, Request_matcher), erlang:element(4, Request_matcher), [{string:lowercase(Key), {exact, Value}} | erlang:element(5, Request_matcher)], erlang:element(6, Request_matcher)}. -file("src/http_server_mock/matcher.gleam", 126). ?DOC( " Constrains the matcher to only match requests that have the header `key`\n" " satisfying the given `StringMatcher`. Header names are compared\n" " case-insensitively.\n" "\n" " Can be called multiple times to require several headers.\n" ). -spec header_matching( http_server_mock@types:request_matcher(), binary(), http_server_mock@types:string_matcher() ) -> http_server_mock@types:request_matcher(). header_matching(Request_matcher, Key, String_matcher) -> {request_matcher, erlang:element(2, Request_matcher), erlang:element(3, Request_matcher), erlang:element(4, Request_matcher), [{string:lowercase(Key), String_matcher} | erlang:element(5, Request_matcher)], erlang:element(6, Request_matcher)}. -file("src/http_server_mock/matcher.gleam", 139). ?DOC( " Constrains the matcher to only match requests whose body is exactly equal\n" " to `body`.\n" ). -spec body_equal_to(http_server_mock@types:request_matcher(), binary()) -> http_server_mock@types:request_matcher(). body_equal_to(Request_matcher, Body) -> {request_matcher, erlang:element(2, Request_matcher), erlang:element(3, Request_matcher), erlang:element(4, Request_matcher), erlang:element(5, Request_matcher), {exact_body, Body}}. -file("src/http_server_mock/matcher.gleam", 148). ?DOC( " Constrains the matcher to only match requests whose body contains\n" " `fragment` as a substring.\n" ). -spec body_containing(http_server_mock@types:request_matcher(), binary()) -> http_server_mock@types:request_matcher(). body_containing(Request_matcher, Fragment) -> {request_matcher, erlang:element(2, Request_matcher), erlang:element(3, Request_matcher), erlang:element(4, Request_matcher), erlang:element(5, Request_matcher), {contains_body, Fragment}}. -file("src/http_server_mock/matcher.gleam", 158). ?DOC( " Constrains the matcher to only match requests whose body is semantically\n" " equal to `json` when both are parsed as JSON (whitespace and key order are\n" " ignored).\n" ). -spec body_json(http_server_mock@types:request_matcher(), binary()) -> http_server_mock@types:request_matcher(). body_json(Request_matcher, Json) -> {request_matcher, erlang:element(2, Request_matcher), erlang:element(3, Request_matcher), erlang:element(4, Request_matcher), erlang:element(5, Request_matcher), {json_body, Json}}. -file("src/http_server_mock/matcher.gleam", 169). ?DOC( " Constrains the matcher using a custom `BodyMatcher`.\n" "\n" " Use this when none of the convenience functions (`body_equal_to`,\n" " `body_containing`, `body_json`) cover your use case.\n" ). -spec body_matcher( http_server_mock@types:request_matcher(), http_server_mock@types:body_matcher() ) -> http_server_mock@types:request_matcher(). body_matcher(Request_matcher, Body_matcher) -> {request_matcher, erlang:element(2, Request_matcher), erlang:element(3, Request_matcher), erlang:element(4, Request_matcher), erlang:element(5, Request_matcher), Body_matcher}. -file("src/http_server_mock/matcher.gleam", 282). -spec normalize_json(binary()) -> binary(). normalize_json(Json_string) -> _pipe = Json_string, _pipe@1 = gleam@string:replace(_pipe, <<" "/utf8>>, <<""/utf8>>), _pipe@2 = gleam@string:replace(_pipe@1, <<"\n"/utf8>>, <<""/utf8>>), _pipe@3 = gleam@string:replace(_pipe@2, <<"\t"/utf8>>, <<""/utf8>>), gleam@string:replace(_pipe@3, <<"\r"/utf8>>, <<""/utf8>>). -file("src/http_server_mock/matcher.gleam", 241). -spec body_matches(http_server_mock@types:body_matcher(), binary()) -> boolean(). body_matches(Expected, Actual) -> case Expected of any_body -> true; {exact_body, Body} -> Actual =:= Body; {contains_body, Fragment} -> gleam_stdlib:contains_string(Actual, Fragment); {json_body, Expected_json} -> normalize_json(Actual) =:= normalize_json(Expected_json) end. -file("src/http_server_mock/matcher.gleam", 254). ?DOC( " Applies a `StringMatcher` to `value`, returning `True` if it matches.\n" "\n" " Exposed for use in custom filtering logic over `recorded_requests`.\n" ). -spec apply_string_matcher(http_server_mock@types:string_matcher(), binary()) -> boolean(). apply_string_matcher(String_matcher, Value) -> case String_matcher of {exact, Expected} -> Value =:= Expected; {contains, Fragment} -> gleam_stdlib:contains_string(Value, Fragment); {prefix, Prefix} -> gleam_stdlib:string_starts_with(Value, Prefix); {suffix, Suffix} -> gleam_stdlib:string_ends_with(Value, Suffix); any_string -> true end. -file("src/http_server_mock/matcher.gleam", 228). -spec headers_match( list({binary(), http_server_mock@types:string_matcher()}), gleam@dict:dict(binary(), binary()) ) -> boolean(). headers_match(Expected, Actual) -> gleam@list:all( Expected, fun(Header_pair) -> {Key, String_matcher} = Header_pair, case gleam_stdlib:map_get(Actual, string:lowercase(Key)) of {ok, Value} -> apply_string_matcher(String_matcher, Value); {error, nil} -> String_matcher =:= any_string end end ). -file("src/http_server_mock/matcher.gleam", 267). -spec parse_query(gleam@option:option(binary())) -> list({binary(), binary()}). parse_query(Query_string) -> case Query_string of none -> []; {some, Query} -> _pipe = Query, _pipe@1 = gleam@string:split(_pipe, <<"&"/utf8>>), gleam@list:filter_map( _pipe@1, fun(Part) -> case gleam@string:split_once(Part, <<"="/utf8>>) of {ok, {Key, Value}} -> {ok, {Key, Value}}; {error, nil} -> {ok, {Part, <<""/utf8>>}} end end ) end. -file("src/http_server_mock/matcher.gleam", 209). -spec query_params_match( list({binary(), http_server_mock@types:string_matcher()}), gleam@option:option(binary()) ) -> boolean(). query_params_match(Expected, Query_string) -> case Expected of [] -> true; _ -> Params = parse_query(Query_string), gleam@list:all( Expected, fun(Query_param_pair) -> {Key, String_matcher} = Query_param_pair, case gleam@list:key_find(Params, Key) of {ok, Value} -> apply_string_matcher(String_matcher, Value); {error, nil} -> String_matcher =:= any_string end end ) end. -file("src/http_server_mock/matcher.gleam", 199). -spec path_matches( gleam@option:option(http_server_mock@types:string_matcher()), binary() ) -> boolean(). path_matches(Expected, Actual) -> case Expected of none -> true; {some, String_matcher} -> apply_string_matcher(String_matcher, Actual) end. -file("src/http_server_mock/matcher.gleam", 192). -spec method_matches( gleam@option:option(gleam@http:method()), gleam@http:method() ) -> boolean(). method_matches(Expected, Actual) -> case Expected of none -> true; {some, Method} -> Method =:= Actual end. -file("src/http_server_mock/matcher.gleam", 181). ?DOC( " Returns `True` if `recorded_request` satisfies all constraints on\n" " `request_matcher`.\n" "\n" " This is the same matching logic the server uses internally. You can call it\n" " directly when filtering `recorded_requests` for custom assertions.\n" ). -spec matches( http_server_mock@types:request_matcher(), http_server_mock@types:recorded_request() ) -> boolean(). matches(Request_matcher, Recorded_request) -> (((method_matches( erlang:element(2, Request_matcher), erlang:element(3, Recorded_request) ) andalso path_matches( erlang:element(3, Request_matcher), erlang:element(4, Recorded_request) )) andalso query_params_match( erlang:element(4, Request_matcher), erlang:element(5, Recorded_request) )) andalso headers_match( erlang:element(5, Request_matcher), erlang:element(6, Recorded_request) )) andalso body_matches( erlang:element(6, Request_matcher), erlang:element(7, Recorded_request) ).