Current section
Files
Jump to
Current section
Files
src/gleamql.erl
-module(gleamql).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleamql.gleam").
-export([new/1, host/2, path/2, header/3, json_content_type/1, scheme/2, send/3]).
-export_type([request/1, error/1, graph_q_l_error/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(
" Query a GraphQL server with `gleamql`.\n"
"\n"
" This library provides a type-safe way to build GraphQL queries and mutations\n"
" while ensuring the query structure and response decoder stay synchronized.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import gleamql\n"
" import gleamql/field\n"
" import gleamql/operation\n"
" import gleam/json\n"
" import gleam/hackney\n"
"\n"
" pub type Country {\n"
" Country(name: String)\n"
" }\n"
"\n"
" pub fn main() {\n"
" let country_op = \n"
" operation.query(\"CountryQuery\")\n"
" |> operation.variable(\"code\", \"ID!\")\n"
" |> operation.field(\n"
" field.object(\"country\", fn() {\n"
" use name <- field.field(field.string(\"name\"))\n"
" field.build(Country(name:))\n"
" })\n"
" |> field.arg(\"code\", \"code\")\n"
" )\n"
"\n"
" let assert Ok(Some(Country(name: \"United Kingdom\"))) =\n"
" gleamql.new(country_op)\n"
" |> gleamql.host(\"countries.trevorblades.com\")\n"
" |> gleamql.path(\"/graphql\")\n"
" |> gleamql.json_content_type()\n"
" |> gleamql.send(hackney.send, [\n"
" #(\"code\", json.string(\"GB\"))\n"
" ])\n"
" }\n"
" ```\n"
"\n"
).
-type request(HXI) :: {request,
gleam@http@request:request(binary()),
gleamql@operation:operation(HXI)}.
-type error(HXJ) :: {network_error, HXJ} |
{http_error, integer(), binary()} |
{graph_q_l_errors, list(graph_q_l_error())} |
{invalid_json, gleam@json:decode_error(), binary()} |
{decode_error, list(gleam@dynamic@decode:decode_error()), binary()}.
-type graph_q_l_error() :: {graph_q_l_error,
binary(),
gleam@option:option(list(gleam@dynamic:dynamic_())),
gleam@option:option(gleam@dynamic:dynamic_())}.
-file("src/gleamql.gleam", 117).
?DOC(
" Construct a GleamQL Request with an operation.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" gleamql.new(country_operation)\n"
" |> gleamql.host(\"api.example.com\")\n"
" |> gleamql.path(\"/graphql\")\n"
" |> gleamql.json_content_type()\n"
" |> gleamql.send(hackney.send, [#(\"code\", json.string(\"GB\"))])\n"
" ```\n"
).
-spec new(gleamql@operation:operation(HXK)) -> request(HXK).
new(Op) ->
{request,
begin
_pipe = gleam@http@request:new(),
gleam@http@request:set_method(_pipe, post)
end,
Op}.
-file("src/gleamql.gleam", 132).
?DOC(
" Set the host of the request.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" gleamql.host(req, \"api.example.com\")\n"
" ```\n"
).
-spec host(request(HXN), binary()) -> request(HXN).
host(Req, Host) ->
{request,
begin
_pipe = erlang:element(2, Req),
gleam@http@request:set_host(_pipe, Host)
end,
erlang:element(3, Req)}.
-file("src/gleamql.gleam", 144).
?DOC(
" Set the path of the request.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" gleamql.path(req, \"/graphql\")\n"
" ```\n"
).
-spec path(request(HXQ), binary()) -> request(HXQ).
path(Req, Path) ->
{request,
begin
_pipe = erlang:element(2, Req),
gleam@http@request:set_path(_pipe, Path)
end,
erlang:element(3, Req)}.
-file("src/gleamql.gleam", 158).
?DOC(
" Set a header on the request.\n"
"\n"
" If already present, it is replaced.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" gleamql.header(req, \"Authorization\", \"Bearer token123\")\n"
" ```\n"
).
-spec header(request(HXT), binary(), binary()) -> request(HXT).
header(Req, Key, Value) ->
{request,
begin
_pipe = erlang:element(2, Req),
gleam@http@request:set_header(_pipe, Key, Value)
end,
erlang:element(3, Req)}.
-file("src/gleamql.gleam", 175).
?DOC(
" Set the `Content-Type` header to `application/json`.\n"
"\n"
" This is required by most GraphQL servers.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" gleamql.json_content_type(req)\n"
" ```\n"
).
-spec json_content_type(request(HXW)) -> request(HXW).
json_content_type(Req) ->
{request,
begin
_pipe = erlang:element(2, Req),
gleam@http@request:set_header(
_pipe,
<<"Content-Type"/utf8>>,
<<"application/json"/utf8>>
)
end,
erlang:element(3, Req)}.
-file("src/gleamql.gleam", 192).
?DOC(
" Set the schema of the request (http or https).\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import gleam/http\n"
" gleamql.scheme(req, http.Https)\n"
" ```\n"
).
-spec scheme(request(HXZ), gleam@http:scheme()) -> request(HXZ).
scheme(Req, Scheme) ->
{request,
begin
_pipe = erlang:element(2, Req),
gleam@http@request:set_scheme(_pipe, Scheme)
end,
erlang:element(3, Req)}.
-file("src/gleamql.gleam", 303).
-spec is_data_null(binary()) -> boolean().
is_data_null(Body) ->
Decoder = begin
gleam@dynamic@decode:optional_field(
<<"data"/utf8>>,
none,
gleam@dynamic@decode:optional(
{decoder, fun gleam@dynamic@decode:decode_dynamic/1}
),
fun(Opt_data) -> gleam@dynamic@decode:success(Opt_data) end
)
end,
case gleam@json:parse(Body, Decoder) of
{ok, none} ->
true;
_ ->
false
end.
-file("src/gleamql.gleam", 272).
-spec decode_optional_data(request(HYW), binary()) -> {ok,
gleam@option:option(HYW)} |
{error, list(gleam@dynamic@decode:decode_error())}.
decode_optional_data(Req, Body) ->
case gleam@json:parse(
Body,
gleamql@operation:decoder(erlang:element(3, Req))
) of
{ok, Value} ->
{ok, {some, Value}};
{error, {unable_to_decode, Errors}} ->
case is_data_null(Body) of
true ->
{ok, none};
false ->
{error, Errors}
end;
{error, _} ->
{error,
[{decode_error,
<<"valid JSON"/utf8>>,
<<"malformed JSON"/utf8>>,
[]}]}
end.
-file("src/gleamql.gleam", 327).
-spec decode_graphql_errors(binary()) -> {ok, list(graph_q_l_error())} |
{error, nil}.
decode_graphql_errors(Body) ->
Decoder = begin
gleam@dynamic@decode:field(
<<"errors"/utf8>>,
gleam@dynamic@decode:list(
begin
gleam@dynamic@decode:field(
<<"message"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Message) ->
gleam@dynamic@decode:optional_field(
<<"path"/utf8>>,
none,
gleam@dynamic@decode:optional(
gleam@dynamic@decode:list(
{decoder,
fun gleam@dynamic@decode:decode_dynamic/1}
)
),
fun(Path) ->
gleam@dynamic@decode:optional_field(
<<"extensions"/utf8>>,
none,
gleam@dynamic@decode:optional(
{decoder,
fun gleam@dynamic@decode:decode_dynamic/1}
),
fun(Extensions) ->
gleam@dynamic@decode:success(
{graph_q_l_error,
Message,
Path,
Extensions}
)
end
)
end
)
end
)
end
),
fun(Errors) -> gleam@dynamic@decode:success(Errors) end
)
end,
_pipe = gleam@json:parse(Body, Decoder),
gleam@result:replace_error(_pipe, nil).
-file("src/gleamql.gleam", 252).
-spec handle_status_ok(request(HYO), gleam@http@response:response(binary())) -> {ok,
gleam@option:option(HYO)} |
{error, error(any())}.
handle_status_ok(Req, Resp) ->
case decode_graphql_errors(erlang:element(4, Resp)) of
{ok, Errors} ->
{error, {graph_q_l_errors, Errors}};
{error, _} ->
case decode_optional_data(Req, erlang:element(4, Resp)) of
{ok, Opt_value} ->
{ok, Opt_value};
{error, Decode_errors} ->
{error,
{decode_error, Decode_errors, erlang:element(4, Resp)}}
end
end.
-file("src/gleamql.gleam", 319).
-spec handle_status_not_ok(gleam@http@response:response(binary())) -> {ok,
gleam@option:option(any())} |
{error, error(any())}.
handle_status_not_ok(Resp) ->
case decode_graphql_errors(erlang:element(4, Resp)) of
{ok, Errors} ->
{error, {graph_q_l_errors, Errors}};
{error, _} ->
{error,
{http_error, erlang:element(2, Resp), erlang:element(4, Resp)}}
end.
-file("src/gleamql.gleam", 353).
-spec status_is_ok(integer()) -> boolean().
status_is_ok(Status) ->
Status =:= 200.
-file("src/gleamql.gleam", 219).
?DOC(
" Send the built request to a GraphQL server with variable values.\n"
"\n"
" A HTTP client is needed to send the request, see https://github.com/gleam-lang/http#client-adapters.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import gleam/hackney\n"
" import gleam/json\n"
"\n"
" gleamql.send(request, hackney.send, [\n"
" #(\"code\", json.string(\"GB\")),\n"
" #(\"lang\", json.string(\"en\")),\n"
" ])\n"
" ```\n"
"\n"
" Variable values must be provided as a list of name/JSON pairs. The names\n"
" should match the variables defined in the operation.\n"
"\n"
" Returns `Ok(Some(data))` if the query succeeded and returned data,\n"
" `Ok(None)` if the query succeeded but returned null, or an `Error` if\n"
" the request failed at any level (network, HTTP, GraphQL, or decoding).\n"
).
-spec send(
request(HYC),
fun((gleam@http@request:request(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, HYG}),
list({binary(), gleam@json:json()})
) -> {ok, gleam@option:option(HYC)} | {error, error(HYG)}.
send(Req, Http_send, Variables) ->
Query_string = gleamql@operation:to_string(erlang:element(3, Req)),
Variables_json = gleamql@operation:build_variables(
erlang:element(3, Req),
Variables
),
Request = begin
_pipe = erlang:element(2, Req),
gleam@http@request:set_body(
_pipe,
begin
_pipe@1 = gleam@json:object(
[{<<"query"/utf8>>, gleam@json:string(Query_string)},
{<<"variables"/utf8>>, Variables_json}]
),
gleam@json:to_string(_pipe@1)
end
)
end,
gleam@result:'try'(
begin
_pipe@2 = Request,
_pipe@3 = Http_send(_pipe@2),
gleam@result:map_error(
_pipe@3,
fun(Field@0) -> {network_error, Field@0} end
)
end,
fun(Resp) -> case status_is_ok(erlang:element(2, Resp)) of
true ->
handle_status_ok(Req, Resp);
false ->
handle_status_not_ok(Resp)
end end
).