Current section
Files
Jump to
Current section
Files
src/mochi@response.erl
-module(mochi@response).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/mochi/response.gleam").
-export([success/1, failure/1, partial/2, deferred_patch_to_dynamic/1, incremental_envelope_to_dynamic/2, with_extensions/2, with_extension/3, with_tracing/3, to_dynamic/1, serialize/1, to_json/1, to_json_pretty/1, execution_error_to_graphql_error/1, from_execution_result/1, from_execution_result_incremental/1, has_errors/1, has_data/1, error_count/1, is_success/1, is_partial/1, format/1]).
-export_type([incremental_response/0, deferred_patch_response/0, graph_q_l_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.
-type incremental_response() :: {incremental_response,
graph_q_l_response(),
list(deferred_patch_response())}.
-type deferred_patch_response() :: {deferred_patch_response,
list(binary()),
gleam@option:option(gleam@dynamic:dynamic_()),
gleam@option:option(list(mochi@error:graph_q_l_error())),
gleam@option:option(binary()),
boolean()}.
-type graph_q_l_response() :: {graph_q_l_response,
gleam@option:option(gleam@dynamic:dynamic_()),
gleam@option:option(list(mochi@error:graph_q_l_error())),
gleam@option:option(gleam@dict:dict(binary(), gleam@dynamic:dynamic_())),
gleam@option:option(boolean())}.
-file("src/mochi/response.gleam", 61).
?DOC(" Create a successful response with data\n").
-spec success(gleam@dynamic:dynamic_()) -> graph_q_l_response().
success(Data) ->
{graph_q_l_response, {some, Data}, none, none, none}.
-file("src/mochi/response.gleam", 71).
?DOC(" Create an error-only response\n").
-spec failure(list(mochi@error:graph_q_l_error())) -> graph_q_l_response().
failure(Errors) ->
{graph_q_l_response, none, {some, Errors}, none, none}.
-file("src/mochi/response.gleam", 81).
?DOC(" Create a partial response with data and errors\n").
-spec partial(gleam@dynamic:dynamic_(), list(mochi@error:graph_q_l_error())) -> graph_q_l_response().
partial(Data, Errors) ->
{graph_q_l_response, {some, Data}, {some, Errors}, none, none}.
-file("src/mochi/response.gleam", 131).
?DOC(" Serialize an incremental response patch to Dynamic\n").
-spec deferred_patch_to_dynamic(deferred_patch_response()) -> gleam@dynamic:dynamic_().
deferred_patch_to_dynamic(Patch) ->
Parts = [{<<"path"/utf8>>, gleam_stdlib:identity(erlang:element(2, Patch))},
{<<"data"/utf8>>, case erlang:element(3, Patch) of
{some, D} ->
D;
none ->
gleam_stdlib:identity(nil)
end}],
Parts@1 = case erlang:element(4, Patch) of
{some, Errors} ->
[{<<"errors"/utf8>>, mochi@error:errors_to_dynamic(Errors)} | Parts];
none ->
Parts
end,
Parts@2 = case erlang:element(5, Patch) of
{some, Label} ->
[{<<"label"/utf8>>, gleam_stdlib:identity(Label)} | Parts@1];
none ->
Parts@1
end,
gleam_stdlib:identity(maps:from_list(Parts@2)).
-file("src/mochi/response.gleam", 152).
?DOC(
" Serialize an incremental delivery envelope to Dynamic\n"
" Shape: {\"incremental\": [...patches], \"hasNext\": bool}\n"
).
-spec incremental_envelope_to_dynamic(
list(deferred_patch_response()),
boolean()
) -> gleam@dynamic:dynamic_().
incremental_envelope_to_dynamic(Patches, Has_next) ->
gleam_stdlib:identity(
maps:from_list(
[{<<"incremental"/utf8>>,
gleam_stdlib:identity(
gleam@list:map(Patches, fun deferred_patch_to_dynamic/1)
)},
{<<"hasNext"/utf8>>, gleam_stdlib:identity(Has_next)}]
)
).
-file("src/mochi/response.gleam", 168).
?DOC(" Add extensions to a response\n").
-spec with_extensions(
graph_q_l_response(),
gleam@dict:dict(binary(), gleam@dynamic:dynamic_())
) -> graph_q_l_response().
with_extensions(Response, Extensions) ->
{graph_q_l_response,
erlang:element(2, Response),
erlang:element(3, Response),
{some, Extensions},
erlang:element(5, Response)}.
-file("src/mochi/response.gleam", 176).
?DOC(" Add a single extension value\n").
-spec with_extension(graph_q_l_response(), binary(), gleam@dynamic:dynamic_()) -> graph_q_l_response().
with_extension(Response, Key, Value) ->
Extensions = case erlang:element(4, Response) of
{some, Ext} ->
gleam@dict:insert(Ext, Key, Value);
none ->
maps:from_list([{Key, Value}])
end,
{graph_q_l_response,
erlang:element(2, Response),
erlang:element(3, Response),
{some, Extensions},
erlang:element(5, Response)}.
-file("src/mochi/response.gleam", 189).
?DOC(" Add tracing data to response extensions\n").
-spec with_tracing(graph_q_l_response(), integer(), integer()) -> graph_q_l_response().
with_tracing(Response, Start_time, End_time) ->
Tracing = gleam_stdlib:identity(
maps:from_list(
[{<<"version"/utf8>>, gleam_stdlib:identity(1)},
{<<"startTime"/utf8>>, gleam_stdlib:identity(Start_time)},
{<<"endTime"/utf8>>, gleam_stdlib:identity(End_time)},
{<<"duration"/utf8>>,
gleam_stdlib:identity(End_time - Start_time)}]
)
),
with_extension(Response, <<"tracing"/utf8>>, Tracing).
-file("src/mochi/response.gleam", 211).
?DOC(" Convert a GraphQLResponse to a Dynamic representation for JSON serialization\n").
-spec to_dynamic(graph_q_l_response()) -> gleam@dynamic:dynamic_().
to_dynamic(Response) ->
Parts = [],
Parts@1 = [{<<"data"/utf8>>, case erlang:element(2, Response) of
{some, D} ->
D;
none ->
gleam_stdlib:identity(nil)
end} | Parts],
Parts@2 = case erlang:element(3, Response) of
{some, Errors} ->
[{<<"errors"/utf8>>, mochi@error:errors_to_dynamic(Errors)} |
Parts@1];
none ->
Parts@1
end,
Parts@3 = case erlang:element(4, Response) of
{some, Ext} ->
[{<<"extensions"/utf8>>, gleam_stdlib:identity(Ext)} | Parts@2];
none ->
Parts@2
end,
Parts@4 = case erlang:element(5, Response) of
{some, V} ->
[{<<"hasNext"/utf8>>, gleam_stdlib:identity(V)} | Parts@3];
none ->
Parts@3
end,
gleam_stdlib:identity(maps:from_list(Parts@4)).
-file("src/mochi/response.gleam", 246).
?DOC(
" Serialize response to JSON string (requires external JSON encoder)\n"
" This returns the Dynamic representation - use with a JSON library\n"
).
-spec serialize(graph_q_l_response()) -> gleam@dynamic:dynamic_().
serialize(Response) ->
to_dynamic(Response).
-file("src/mochi/response.gleam", 272).
-spec encode_error_fallback(mochi@output:encode_error(), boolean()) -> binary().
encode_error_fallback(Err, Pretty) ->
Message = <<"Internal: "/utf8, (mochi@json:describe_error(Err))/binary>>,
Fallback = begin
_pipe@1 = failure(
[begin
_pipe = mochi@error:new(Message),
mochi@error:with_code(
_pipe,
<<"INTERNAL_SERVER_ERROR"/utf8>>
)
end]
),
to_dynamic(_pipe@1)
end,
Encoded = case Pretty of
true ->
mochi@json:encode_pretty(Fallback, 2);
false ->
mochi@json:encode(Fallback)
end,
case Encoded of
{ok, S} ->
S;
{error, _} ->
<<"{\"data\":null,\"errors\":[{\"message\":\"Internal: response encoding failed\"}]}"/utf8>>
end.
-file("src/mochi/response.gleam", 256).
?DOC(
" Convert a GraphQLResponse to a JSON string.\n"
"\n"
" If the response data contains a runtime shape that can't be encoded as\n"
" JSON (e.g. a resolver returned a tuple or function), fall back to a\n"
" spec-compliant error response describing the failure rather than\n"
" silently emitting `null` for the offending value.\n"
).
-spec to_json(graph_q_l_response()) -> binary().
to_json(Response) ->
case mochi@json:encode(to_dynamic(Response)) of
{ok, S} ->
S;
{error, Err} ->
encode_error_fallback(Err, false)
end.
-file("src/mochi/response.gleam", 265).
?DOC(
" Convert a GraphQLResponse to a pretty-printed JSON string. Same fallback\n"
" behaviour as [`to_json`](#to_json) on encoding failure.\n"
).
-spec to_json_pretty(graph_q_l_response()) -> binary().
to_json_pretty(Response) ->
case mochi@json:encode_pretty(to_dynamic(Response), 2) of
{ok, S} ->
S;
{error, Err} ->
encode_error_fallback(Err, true)
end.
-file("src/mochi/response.gleam", 297).
?DOC(
" Convert an ExecutionError to a GraphQLError\n"
" Add source location to a GraphQLError if one is present\n"
).
-spec maybe_with_location(
mochi@error:graph_q_l_error(),
gleam@option:option({integer(), integer()})
) -> mochi@error:graph_q_l_error().
maybe_with_location(Err, Location) ->
case Location of
none ->
Err;
{some, {Line, Col}} ->
mochi@error:with_locations(Err, [{location, Line, Col}])
end.
-file("src/mochi/response.gleam", 315).
-spec execution_error_to_graphql_error(mochi@executor:execution_error()) -> mochi@error:graph_q_l_error().
execution_error_to_graphql_error(Err) ->
case Err of
{validation_error, Message, Path, Location} ->
_pipe = mochi@error:new_at(Message, Path),
_pipe@1 = mochi@error:with_category(
_pipe,
validation_error_category
),
maybe_with_location(_pipe@1, Location);
{resolver_error, Message@1, Path@1, Location@1} ->
_pipe@2 = mochi@error:new_at(Message@1, Path@1),
_pipe@3 = mochi@error:with_category(
_pipe@2,
resolver_error_category
),
maybe_with_location(_pipe@3, Location@1);
{type_error, Message@2, Path@2, Location@2} ->
_pipe@4 = mochi@error:new_at(Message@2, Path@2),
_pipe@5 = mochi@error:with_category(_pipe@4, type_error_category),
maybe_with_location(_pipe@5, Location@2);
{null_value_error, Message@3, Path@3, Location@3} ->
_pipe@6 = mochi@error:new_at(Message@3, Path@3),
_pipe@7 = mochi@error:with_code(
_pipe@6,
<<"NULL_VALUE_ERROR"/utf8>>
),
_pipe@8 = mochi@error:with_category(
_pipe@7,
resolver_error_category
),
maybe_with_location(_pipe@8, Location@3);
{rich_resolver_error, Gql_err, Err_path, Location@4} ->
_pipe@9 = {graph_q_l_error,
erlang:element(2, Gql_err),
erlang:element(3, Gql_err),
{some,
gleam@list:map(
Err_path,
fun(Field@0) -> {field_segment, Field@0} end
)},
erlang:element(5, Gql_err)},
maybe_with_location(_pipe@9, Location@4)
end.
-file("src/mochi/response.gleam", 308).
-spec map_errors(list(mochi@executor:execution_error())) -> gleam@option:option(list(mochi@error:graph_q_l_error())).
map_errors(Errors) ->
case Errors of
[] ->
none;
Errs ->
{some, gleam@list:map(Errs, fun execution_error_to_graphql_error/1)}
end.
-file("src/mochi/response.gleam", 91).
?DOC(" Create a response from an ExecutionResult\n").
-spec from_execution_result(mochi@executor:execution_result()) -> graph_q_l_response().
from_execution_result(Result) ->
{graph_q_l_response,
erlang:element(2, Result),
map_errors(erlang:element(3, Result)),
none,
none}.
-file("src/mochi/response.gleam", 101).
?DOC(" Build an incremental response from an ExecutionResult that contains deferred patches\n").
-spec from_execution_result_incremental(mochi@executor:execution_result()) -> incremental_response().
from_execution_result_incremental(Result) ->
Has_deferred = not gleam@list:is_empty(erlang:element(4, Result)),
Last_idx = erlang:length(erlang:element(4, Result)) - 1,
Initial = {graph_q_l_response,
erlang:element(2, Result),
map_errors(erlang:element(3, Result)),
none,
case Has_deferred of
true ->
{some, true};
false ->
none
end},
Patches = begin
_pipe = erlang:element(4, Result),
gleam@list:index_map(
_pipe,
fun(Patch, Idx) ->
{deferred_patch_response,
erlang:element(2, Patch),
erlang:element(3, Patch),
map_errors(erlang:element(4, Patch)),
erlang:element(5, Patch),
Idx < Last_idx}
end
)
end,
{incremental_response, Initial, Patches}.
-file("src/mochi/response.gleam", 353).
?DOC(" Check if response has errors\n").
-spec has_errors(graph_q_l_response()) -> boolean().
has_errors(Response) ->
case erlang:element(3, Response) of
{some, Errors} ->
Errors /= [];
none ->
false
end.
-file("src/mochi/response.gleam", 361).
?DOC(" Check if response has data\n").
-spec has_data(graph_q_l_response()) -> boolean().
has_data(Response) ->
case erlang:element(2, Response) of
{some, _} ->
true;
none ->
false
end.
-file("src/mochi/response.gleam", 369).
?DOC(" Get error count\n").
-spec error_count(graph_q_l_response()) -> integer().
error_count(Response) ->
case erlang:element(3, Response) of
{some, Errors} ->
erlang:length(Errors);
none ->
0
end.
-file("src/mochi/response.gleam", 377).
?DOC(" Check if response is successful (has data, no errors)\n").
-spec is_success(graph_q_l_response()) -> boolean().
is_success(Response) ->
has_data(Response) andalso not has_errors(Response).
-file("src/mochi/response.gleam", 382).
?DOC(" Check if response is partial (has data and errors)\n").
-spec is_partial(graph_q_l_response()) -> boolean().
is_partial(Response) ->
has_data(Response) andalso has_errors(Response).
-file("src/mochi/response.gleam", 391).
?DOC(" Format response for debugging/logging\n").
-spec format(graph_q_l_response()) -> binary().
format(Response) ->
Data_str = case erlang:element(2, Response) of
{some, _} ->
<<"data: <present>"/utf8>>;
none ->
<<"data: null"/utf8>>
end,
Errors_str = case erlang:element(3, Response) of
{some, Errors} ->
Count = erlang:length(Errors),
case Count of
1 ->
<<"errors: [1 error]"/utf8>>;
N ->
<<<<"errors: ["/utf8, (erlang:integer_to_binary(N))/binary>>/binary,
" errors]"/utf8>>
end;
none ->
<<"errors: none"/utf8>>
end,
Ext_str = case erlang:element(4, Response) of
{some, _} ->
<<"extensions: <present>"/utf8>>;
none ->
<<"extensions: none"/utf8>>
end,
<<<<<<<<<<<<"GraphQLResponse { "/utf8, Data_str/binary>>/binary, ", "/utf8>>/binary,
Errors_str/binary>>/binary,
", "/utf8>>/binary,
Ext_str/binary>>/binary,
" }"/utf8>>.