Packages

A small, transport-agnostic atproto client for Gleam: XRPC, identity, OAuth discovery, blobs, and repo CRUD.

Current section

Files

Jump to
atproto_client src atproto@xrpc.erl
Raw

src/atproto@xrpc.erl

-module(atproto@xrpc).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/atproto/xrpc.gleam").
-export([transport_error_to_string/1, describe/1, parse/2, check_ok/1, send_text/2, get/3, get_bits/3, post_json/4, post_bits/5]).
-export_type([client/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(
" Transport-agnostic XRPC plumbing. The `Client` wraps a `send` function so\n"
" the caller chooses the HTTP backend (erlang `httpc`, JS `fetch`, a test\n"
" stub), keeping this module free of any target-specific dependency. Bodies\n"
" are BitArrays so blobs ride the same client; the text helpers below keep\n"
" JSON call sites string-shaped.\n"
"\n"
" `TransportError`/`XrpcError` alias `atproto_core/xrpc`'s types;\n"
" constructing their variants (e.g. in a custom `send`) needs that import.\n"
).
-type client() :: {client,
fun((gleam@http@request:request(bitstring())) -> {ok,
gleam@http@response:response(bitstring())} |
{error, atproto_core@xrpc:transport_error()})}.
-file("src/atproto/xrpc.gleam", 33).
?DOC(" A one-line human-readable rendering of a transport error.\n").
-spec transport_error_to_string(atproto_core@xrpc:transport_error()) -> binary().
transport_error_to_string(Error) ->
atproto_core@xrpc:transport_error_to_string(Error).
-file("src/atproto/xrpc.gleam", 38).
?DOC(" A one-line human-readable rendering of an error, for CLI and log output.\n").
-spec describe(atproto_core@xrpc:xrpc_error()) -> binary().
describe(Error) ->
atproto_core@xrpc:describe(Error).
-file("src/atproto/xrpc.gleam", 42).
-spec parse(binary(), gleam@dynamic@decode:decoder(AEIC)) -> {ok, AEIC} |
{error, atproto_core@xrpc:xrpc_error()}.
parse(Body, Decoder) ->
atproto_core@xrpc:parse(Body, Decoder).
-file("src/atproto/xrpc.gleam", 48).
?DOC(
" Turn a received response into a `Result`: `Ok` on 2xx, else a `BadStatus`\n"
" with the atproto `error`/`message` parsed out of the body when present.\n"
).
-spec check_ok(gleam@http@response:response(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, atproto_core@xrpc:xrpc_error()}.
check_ok(Resp) ->
atproto_core@xrpc:check_ok(Resp).
-file("src/atproto/xrpc.gleam", 54).
?DOC(
" Send a text request and decode the response body as text. The seam between\n"
" the string-shaped JSON world and the BitArray transport.\n"
).
-spec send_text(client(), gleam@http@request:request(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, atproto_core@xrpc:transport_error()}.
send_text(Client, Req) ->
gleam@result:'try'(
(erlang:element(2, Client))(
gleam@http@request:map(Req, fun gleam_stdlib:identity/1)
),
fun(Resp) -> case gleam@bit_array:to_string(erlang:element(4, Resp)) of
{ok, Body} ->
{ok, gleam@http@response:set_body(Resp, Body)};
{error, _} ->
{error,
{other, <<"response body is not valid utf-8"/utf8>>}}
end end
).
-file("src/atproto/xrpc.gleam", 144).
-spec send_checked(client(), gleam@http@request:request(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, atproto_core@xrpc:xrpc_error()}.
send_checked(Client, Req) ->
_pipe = send_text(Client, Req),
_pipe@1 = gleam@result:map_error(
_pipe,
fun(Field@0) -> {request_failed, Field@0} end
),
gleam@result:'try'(_pipe@1, fun check_ok/1).
-file("src/atproto/xrpc.gleam", 128).
-spec with_auth(
gleam@http@request:request(binary()),
gleam@option:option(binary())
) -> gleam@http@request:request(binary()).
with_auth(Req, Token) ->
case Token of
{some, T} ->
gleam@http@request:set_header(
Req,
<<"authorization"/utf8>>,
<<"Bearer "/utf8, T/binary>>
);
none ->
Req
end.
-file("src/atproto/xrpc.gleam", 135).
-spec base_request(binary(), gleam@option:option(binary())) -> {ok,
gleam@http@request:request(binary())} |
{error, atproto_core@xrpc:xrpc_error()}.
base_request(Url, Token) ->
_pipe = gleam@http@request:to(Url),
_pipe@1 = gleam@result:replace_error(
_pipe,
{request_failed, {invalid_url, Url}}
),
gleam@result:map(_pipe@1, fun(_capture) -> with_auth(_capture, Token) end).
-file("src/atproto/xrpc.gleam", 65).
-spec get(client(), binary(), gleam@option:option(binary())) -> {ok,
gleam@http@response:response(binary())} |
{error, atproto_core@xrpc:xrpc_error()}.
get(Client, Url, Token) ->
gleam@result:'try'(
base_request(Url, Token),
fun(Base) -> send_checked(Client, Base) end
).
-file("src/atproto/xrpc.gleam", 76).
?DOC(
" GET returning the raw bytes (e.g. blob or image downloads). The error body\n"
" on a bad status is decoded leniently for the message.\n"
).
-spec get_bits(client(), binary(), gleam@option:option(binary())) -> {ok,
gleam@http@response:response(bitstring())} |
{error, atproto_core@xrpc:xrpc_error()}.
get_bits(Client, Url, Token) ->
gleam@result:'try'(base_request(Url, Token), fun(Base) -> _pipe = Base,
_pipe@1 = gleam@http@request:map(_pipe, fun gleam_stdlib:identity/1),
_pipe@2 = (erlang:element(2, Client))(_pipe@1),
_pipe@3 = gleam@result:map_error(
_pipe@2,
fun(Field@0) -> {request_failed, Field@0} end
),
gleam@result:'try'(_pipe@3, fun atproto_core@xrpc:check_ok_bits/1) end).
-file("src/atproto/xrpc.gleam", 89).
-spec post_json(
client(),
binary(),
gleam@option:option(binary()),
gleam@json:json()
) -> {ok, gleam@http@response:response(binary())} |
{error, atproto_core@xrpc:xrpc_error()}.
post_json(Client, Url, Token, Body) ->
gleam@result:'try'(base_request(Url, Token), fun(Base) -> _pipe = Base,
_pipe@1 = gleam@http@request:set_method(_pipe, post),
_pipe@2 = gleam@http@request:set_header(
_pipe@1,
<<"content-type"/utf8>>,
<<"application/json"/utf8>>
),
_pipe@3 = gleam@http@request:set_body(
_pipe@2,
gleam@json:to_string(Body)
),
send_checked(Client, _pipe@3) end).
-file("src/atproto/xrpc.gleam", 104).
?DOC(" POST raw bytes (e.g. `uploadBlob`); the response is decoded as text (JSON).\n").
-spec post_bits(
client(),
binary(),
gleam@option:option(binary()),
bitstring(),
binary()
) -> {ok, gleam@http@response:response(binary())} |
{error, atproto_core@xrpc:xrpc_error()}.
post_bits(Client, Url, Token, Body, Content_type) ->
gleam@result:'try'(
base_request(Url, Token),
fun(Base) ->
gleam@result:'try'(
begin
_pipe = Base,
_pipe@1 = gleam@http@request:set_method(_pipe, post),
_pipe@2 = gleam@http@request:set_header(
_pipe@1,
<<"content-type"/utf8>>,
Content_type
),
_pipe@3 = gleam@http@request:map(
_pipe@2,
fun gleam_stdlib:identity/1
),
_pipe@4 = gleam@http@request:set_body(_pipe@3, Body),
_pipe@5 = (erlang:element(2, Client))(_pipe@4),
gleam@result:map_error(
_pipe@5,
fun(Field@0) -> {request_failed, Field@0} end
)
end,
fun(Resp) ->
case gleam@bit_array:to_string(erlang:element(4, Resp)) of
{ok, Text} ->
check_ok(gleam@http@response:set_body(Resp, Text));
{error, _} ->
{error,
{decode_failed,
<<"response body is not valid utf-8"/utf8>>}}
end
end
)
end
).