Packages

Gleam library for interacting with Automatic Certificate Management Environment (ACME) servers like Let's Encrypt

Retired package: Release invalid

Current section

Files

Jump to
acumen src acumen@create_order.erl
Raw

src/acumen@create_order.erl

-module(acumen@create_order).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/acumen/create_order.gleam").
-export([build/3, not_after/2, not_before/2, profile/2, replaces/2, request/1, response/1]).
-export_type([request_builder/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(
" Create a new ACME order for certificate issuance.\n"
"\n"
" Before obtaining a certificate, you must create an order specifying which\n"
" identifiers (domain names or IP addresses) you want on the certificate.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" import acumen\n"
" import acumen/create_order\n"
" import acumen/order.{type Order}\n"
"\n"
" let assert Ok(ord) = create_order.request(identifiers: [\n"
" acumen.DnsIdentifier(\"example.com\"),\n"
" acumen.DnsIdentifier(\"www.example.com\"),\n"
" ])\n"
"\n"
" let assert Ok(#(resp, ctx)) = acumen.execute(\n"
" ctx,\n"
" build: create_order.build(ord, _, registered_key),\n"
" send: httpc.send,\n"
" )\n"
"\n"
" let assert Ok(created_order) = create_order.response(resp)\n"
" ```\n"
).
-opaque request_builder() :: {request_builder,
list(acumen:identifier_()),
gleam@option:option(gleam@time@timestamp:timestamp()),
gleam@option:option(gleam@time@timestamp:timestamp()),
gleam@option:option(binary()),
gleam@option:option(binary())}.
-file("src/acumen/create_order.gleam", 95).
-spec identifier_to_json(acumen:identifier_()) -> gleam@json:json().
identifier_to_json(Identifier) ->
case Identifier of
{dns_identifier, Value} ->
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"dns"/utf8>>)},
{<<"value"/utf8>>, gleam@json:string(Value)}]
);
{ip_identifier, Value@1} ->
gleam@json:object(
[{<<"type"/utf8>>, gleam@json:string(<<"ip"/utf8>>)},
{<<"value"/utf8>>, gleam@json:string(Value@1)}]
)
end.
-file("src/acumen/create_order.gleam", 77).
-spec build_payload(request_builder()) -> gleam@json:json().
build_payload(Builder) ->
Utc = gleam@time@duration:seconds(0),
Timestamp_field = fun(Ts) ->
gleam@json:string(gleam@time@timestamp:to_rfc3339(Ts, Utc))
end,
_pipe = [{<<"identifiers"/utf8>>,
gleam@json:array(
erlang:element(2, Builder),
fun identifier_to_json/1
)} |
gleam@option:values(
[gleam@option:map(
erlang:element(3, Builder),
fun(V) -> {<<"notBefore"/utf8>>, Timestamp_field(V)} end
),
gleam@option:map(
erlang:element(4, Builder),
fun(V@1) -> {<<"notAfter"/utf8>>, Timestamp_field(V@1)} end
),
gleam@option:map(
erlang:element(5, Builder),
fun(V@2) -> {<<"profile"/utf8>>, gleam@json:string(V@2)} end
),
gleam@option:map(
erlang:element(6, Builder),
fun(V@3) ->
{<<"replaces"/utf8>>, gleam@json:string(V@3)}
end
)]
)],
gleam@json:object(_pipe).
-file("src/acumen/create_order.gleam", 59).
?DOC(
" Builds the signed HTTP request for order creation.\n"
"\n"
" Sends the identifiers and any optional constraints (profile,\n"
" notBefore/notAfter, replaces) to the ACME server's `newOrder` endpoint.\n"
).
-spec build(request_builder(), acumen:context(), acumen:registered_key()) -> {ok,
gleam@http@request:request(binary())} |
{error, acumen:acme_error()}.
build(Builder, Context, Key) ->
_pipe = build_payload(Builder),
_pipe@1 = gleam@json:to_string(_pipe),
_pipe@2 = acumen@internal@jws:sign_with_kid(
erlang:element(2, Key),
erlang:element(3, Key),
_pipe@1,
erlang:element(3, Context),
erlang:element(4, erlang:element(2, Context))
),
_pipe@3 = gleam@result:map_error(
_pipe@2,
fun(Field@0) -> {jws_error, Field@0} end
),
gleam@result:'try'(
_pipe@3,
fun(_capture) ->
acumen:build_post_request(
erlang:element(4, erlang:element(2, Context)),
_capture
)
end
).
-file("src/acumen/create_order.gleam", 110).
?DOC(
" Sets the requested notAfter constraint on the certificate.\n"
"\n"
" The CA may ignore this or apply its own constraints.\n"
).
-spec not_after(request_builder(), gleam@time@timestamp:timestamp()) -> request_builder().
not_after(Builder, Timestamp) ->
{request_builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
{some, Timestamp},
erlang:element(5, Builder),
erlang:element(6, Builder)}.
-file("src/acumen/create_order.gleam", 120).
?DOC(
" Sets the requested notBefore constraint on the certificate.\n"
"\n"
" The CA may ignore this or apply its own constraints.\n"
).
-spec not_before(request_builder(), gleam@time@timestamp:timestamp()) -> request_builder().
not_before(Builder, Timestamp) ->
{request_builder,
erlang:element(2, Builder),
{some, Timestamp},
erlang:element(4, Builder),
erlang:element(5, Builder),
erlang:element(6, Builder)}.
-file("src/acumen/create_order.gleam", 130).
?DOC(
" Sets the certificate issuance profile for the order.\n"
"\n"
" Available profiles can be discovered with `acumen.profiles(directory)`.\n"
).
-spec profile(request_builder(), binary()) -> request_builder().
profile(Builder, Name) ->
{request_builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
{some, Name},
erlang:element(6, Builder)}.
-file("src/acumen/create_order.gleam", 138).
?DOC(
" Sets the certificate being replaced.\n"
"\n"
" Build `cert_id` with `renewal_info.cert_id` or\n"
" `renewal_info.cert_id_from_certificate`.\n"
).
-spec replaces(request_builder(), binary()) -> request_builder().
replaces(Builder, Cert_id) ->
{request_builder,
erlang:element(2, Builder),
erlang:element(3, Builder),
erlang:element(4, Builder),
erlang:element(5, Builder),
{some, Cert_id}}.
-file("src/acumen/create_order.gleam", 152).
?DOC(
" Creates a new order request builder. At least one identifier is required.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let assert Ok(ord) = create_order.request(identifiers: [\n"
" acumen.DnsIdentifier(\"example.com\"),\n"
" acumen.DnsIdentifier(\"www.example.com\"),\n"
" ])\n"
" ```\n"
).
-spec request(list(acumen:identifier_())) -> {ok, request_builder()} |
{error, acumen:acme_error()}.
request(Identifiers) ->
case Identifiers of
[] ->
{error,
{invalid_request, <<"identifiers list cannot be empty"/utf8>>}};
_ ->
{ok, {request_builder, Identifiers, none, none, none, none}}
end.
-file("src/acumen/create_order.gleam", 182).
-spec parse_order_creation_response(gleam@http@response:response(binary())) -> {ok,
acumen@order:order()} |
{error, acumen:acme_error()}.
parse_order_creation_response(Resp) ->
gleam@result:'try'(
begin
_pipe = gleam@http@response:get_header(Resp, <<"location"/utf8>>),
gleam@result:replace_error(
_pipe,
{invalid_response, <<"missing Location header"/utf8>>}
)
end,
fun(Location) -> _pipe@1 = acumen@url:from_string(Location),
_pipe@2 = gleam@result:replace_error(
_pipe@1,
{invalid_response, <<"invalid Location URI"/utf8>>}
),
gleam@result:'try'(
_pipe@2,
fun(_capture) ->
acumen@order:parse_order_response(Resp, _capture)
end
) end
).
-file("src/acumen/create_order.gleam", 172).
?DOC(
" Parses the order creation response.\n"
"\n"
" Accepts both 200 and 201 status codes — the server may return an\n"
" existing order for duplicate identifiers.\n"
).
-spec response(gleam@http@response:response(binary())) -> {ok,
acumen@order:order()} |
{error, acumen:acme_error()}.
response(Resp) ->
case erlang:element(2, Resp) of
200 ->
parse_order_creation_response(Resp);
201 ->
parse_order_creation_response(Resp);
_ ->
{error,
{invalid_response,
acumen@internal@utils:unexpected_status_message(
erlang:element(2, Resp)
)}}
end.