Current section
Files
Jump to
Current section
Files
src/version_bump@github_api.erl
-module(version_bump@github_api).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/version_bump/github_api.gleam").
-export([build_release_payload/5, create_release/8, parse_repo_url/1]).
-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(
" Minimal GitHub REST client used by the GitHub publish plugin.\n"
"\n"
" Pure request-building and URL-parsing helpers are separated from the\n"
" effectful `create_release`, which actually performs the HTTP call. This\n"
" keeps the parsing/serialisation logic unit-testable without a network.\n"
).
-file("src/version_bump/github_api.gleam", 27).
?DOC(
" Serialise the JSON body for a create-release request.\n"
"\n"
" PURE: the `POST` payload for `api.github.com/repos/{owner}/{repo}/releases`.\n"
).
-spec build_release_payload(binary(), binary(), binary(), boolean(), binary()) -> binary().
build_release_payload(Tag, Name, Body, Prerelease, Target) ->
_pipe = gleam@json:object(
[{<<"tag_name"/utf8>>, gleam@json:string(Tag)},
{<<"name"/utf8>>, gleam@json:string(Name)},
{<<"body"/utf8>>, gleam@json:string(Body)},
{<<"prerelease"/utf8>>, gleam@json:bool(Prerelease)},
{<<"target_commitish"/utf8>>, gleam@json:string(Target)}]
),
gleam@json:to_string(_pipe).
-file("src/version_bump/github_api.gleam", 144).
?DOC(" Parse the `html_url` field out of a GitHub release JSON response, if present.\n").
-spec parse_html_url(binary()) -> gleam@option:option(binary()).
parse_html_url(Body) ->
Decoder = begin
gleam@dynamic@decode:field(
<<"html_url"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Url) -> gleam@dynamic@decode:success(Url) end
)
end,
case gleam@json:parse(Body, Decoder) of
{ok, Url@1} ->
{some, Url@1};
{error, _} ->
none
end.
-file("src/version_bump/github_api.gleam", 155).
-spec http_error_to_string(gleam@httpc:http_error()) -> binary().
http_error_to_string(Err) ->
case Err of
invalid_utf8_response ->
<<"GitHub API returned a non-UTF-8 response"/utf8>>;
response_timeout ->
<<"GitHub API request timed out"/utf8>>;
{failed_to_connect, _, _} ->
<<"Failed to connect to the GitHub API"/utf8>>
end.
-file("src/version_bump/github_api.gleam", 108).
?DOC(" Build the `httpc` request from primitives (Erlang target only).\n").
-spec build_erlang_request(binary(), binary(), binary()) -> {ok,
gleam@http@request:request(binary())} |
{error, binary()}.
build_erlang_request(Url, Token, Body) ->
case gleam@http@request:to(Url) of
{ok, Req} ->
{ok,
begin
_pipe = Req,
_pipe@1 = gleam@http@request:set_method(_pipe, post),
_pipe@2 = gleam@http@request:set_body(_pipe@1, Body),
_pipe@3 = gleam@http@request:set_header(
_pipe@2,
<<"authorization"/utf8>>,
<<"Bearer "/utf8, Token/binary>>
),
_pipe@4 = gleam@http@request:set_header(
_pipe@3,
<<"accept"/utf8>>,
<<"application/vnd.github+json"/utf8>>
),
_pipe@5 = gleam@http@request:set_header(
_pipe@4,
<<"content-type"/utf8>>,
<<"application/json"/utf8>>
),
_pipe@6 = gleam@http@request:set_header(
_pipe@5,
<<"x-github-api-version"/utf8>>,
<<"2022-11-28"/utf8>>
),
gleam@http@request:set_header(
_pipe@6,
<<"user-agent"/utf8>>,
<<"version_bump"/utf8>>
)
end};
{error, _} ->
{error, <<"invalid GitHub API URL: "/utf8, Url/binary>>}
end.
-file("src/version_bump/github_api.gleam", 96).
?DOC(
" Perform the POST and yield `#(status_code, body)`. A status of `0` signals a\n"
" transport-level failure, with the message in the body slot.\n"
"\n"
" This function has a Gleam body (the Erlang/`httpc` implementation, also used\n"
" by any target without an external) and a JavaScript `@external` that uses\n"
" `fetch`. That is how one call site stays target-agnostic while the actual I/O\n"
" is synchronous on the BEAM and promise-based on Node.\n"
).
-spec send(binary(), binary(), binary()) -> version_bump@task:task({integer(),
binary()}).
send(Url, Token, Body) ->
case build_erlang_request(Url, Token, Body) of
{error, Message} ->
version_bump_task_ffi:resolve({0, Message});
{ok, Req} ->
case gleam@httpc:send(Req) of
{ok, Resp} ->
version_bump_task_ffi:resolve(
{erlang:element(2, Resp), erlang:element(4, Resp)}
);
{error, Err} ->
version_bump_task_ffi:resolve(
{0, http_error_to_string(Err)}
)
end
end.
-file("src/version_bump/github_api.gleam", 51).
?DOC(
" Create a GitHub release and return the resulting `Release`, asynchronously.\n"
"\n"
" The HTTP send is cross-target via `send`: on Erlang it uses `httpc`\n"
" synchronously; on JavaScript it uses `fetch` (a real promise). Both yield a\n"
" `Task(#(status, body))`, which is mapped here into a `Release` (parsing\n"
" `html_url`), a non-2xx `NetworkError`, or a transport `NetworkError`\n"
" (signalled as status `0`).\n"
).
-spec create_release(
binary(),
binary(),
binary(),
binary(),
binary(),
binary(),
boolean(),
binary()
) -> version_bump@task:task({ok, version_bump@release:release()} |
{error, version_bump@error:release_error()}).
create_release(Token, Owner, Repo, Tag, Name, Body, Prerelease, Target) ->
Url = <<<<<<<<<<<<"https://"/utf8, "api.github.com"/utf8>>/binary,
"/repos/"/utf8>>/binary,
Owner/binary>>/binary,
"/"/utf8>>/binary,
Repo/binary>>/binary,
"/releases"/utf8>>,
Payload = build_release_payload(Tag, Name, Body, Prerelease, Target),
version_bump_task_ffi:map(
send(Url, Token, Payload),
fun(Outcome) ->
{Status, Resp_body} = Outcome,
case Status of
0 ->
{error,
{network_error,
<<"Failed to reach the GitHub API: "/utf8,
Resp_body/binary>>}};
S when (S >= 200) andalso (S < 300) ->
{ok,
{release,
Name,
parse_html_url(Resp_body),
Tag,
Tag,
none,
<<"github"/utf8>>}};
S@1 ->
{error,
{network_error,
<<<<<<"GitHub API responded with status "/utf8,
(erlang:integer_to_binary(S@1))/binary>>/binary,
": "/utf8>>/binary,
Resp_body/binary>>}}
end
end
).
-file("src/version_bump/github_api.gleam", 133).
?DOC(
" Extract `(owner, repo)` from an HTTPS or `git@` GitHub remote URL.\n"
"\n"
" PURE. Parsing lives in `repo_url` (shared with the `forgejo` plugin); this\n"
" keeps only the pair GitHub needs and maps failure onto `NetworkError`.\n"
).
-spec parse_repo_url(binary()) -> {ok, {binary(), binary()}} |
{error, version_bump@error:release_error()}.
parse_repo_url(Url) ->
case version_bump@repo_url:parse(Url) of
{ok, {repo_ref, _, Owner, Repo}} ->
{ok, {Owner, Repo}};
{error, nil} ->
{error,
{network_error,
<<"Could not parse owner/repo from URL: "/utf8, Url/binary>>}}
end.