-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.