# hex_core [![Build Status](https://github.com/hexpm/hex_core/actions/workflows/main.yml/badge.svg)](https://github.com/hexpm/hex_core/actions/workflows/main.yml) Reference implementation of Hex specifications: . ## Usage Let's use default config for now. See "Configuration" section below for customization. ```erlang Config = hex_core:default_config(). ``` **WARNING** The built-in httpc-based HTTP client adapter has insecure default settings and thus it is not recommended for usage, in particular for API write operations. Use HTTP clients with secure defaults like [hackney](https://hex.pm/packages/hackney) and [Mint](https://hex.pm/packages/mint). See "Configuration" and "Wrapper Module" sections for more information on customising your usage of `hex_core`. ### Repository Get all package names: ```erlang > hex_repo:get_names(Config). {ok,{200, ..., #{packages => [ #{name => <<"package1">>}, #{name => <<"package2">>}, ...]}}} ``` Get all package versions from repository: ```erlang > hex_repo:get_versions(Config). {ok,{200, ..., #{packages => [[ #{name => <<"package1">>, retired => [], versions => [<<"1.0.0">>]}, #{name => <<"package2">>, retired => [], versions => [<<"0.5.0">>]}, ...]}}} ``` Get package releases from repository: ```erlang > hex_repo:get_package(Config, <<"package1">>). {ok,{200, ..., #{releases => [ #{checksum => ..., version => <<"0.5.0">>, dependencies => []}], #{checksum => ..., version => <<"1.0.0">>, dependencies => []}], ...]}}} ``` ### API For a full list of all parameters and returned objects for the API, check out the API docs: . Get package from HTTP API: ```erlang > hex_api_package:get(Config, <<"package1">>). {ok, {200, ..., #{ <<"name">> => <<"package1">>, <<"meta">> => #{ <<"description">> => ..., <<"licenses">> => ..., <<"links">> => ..., <<"maintainers">> => ..., }, ..., <<"releases">> => [ #{<<"url">> => ..., <<"version">> => <<"0.5.0">>}], #{<<"url">> => ..., <<"version">> => <<"1.0.0">>}], ... ] }}} ``` Get package tarball: ```erlang {ok, {200, _, Tarball}} = hex_repo:get_tarball(Config, <<"package1">>, <<"1.0.0">>). ``` Publish package tarball: ```erlang {ok, {200, _Headers, _Body} = hex_api_package:publish(Config, Tarball). ``` ### Two-Factor Authentication When using OAuth tokens, two-factor authentication may be required. If required, the server will return `{error, otp_required}` and you should retry the request with the TOTP code via the `api_otp` configuration option. If the TOTP code is incorrect, the server will return `{error, invalid_totp}` and you should retry with the correct TOTP code. ```erlang %% First attempt without OTP case hex_api_release:publish(Config, Tarball) of {error, otp_required} -> %% Retry with TOTP code ConfigWithOTP = Config#{api_otp := <<"123456">>}, hex_api_release:publish(ConfigWithOTP, Tarball); {error, invalid_totp} -> %% Retry with correct TOTP code ConfigWithOTP = Config#{api_otp := <<"123456">>}, hex_api_release:publish(ConfigWithOTP, Tarball); Result -> Result end. ``` API keys don't require TOTP validation. ### Package tarballs Unpack package tarball: ```erlang {ok, #{outer_checksum := Checksum, contents := Contents, metadata := Metadata}} = hex_tarball:unpack(Tarball, memory). ``` Remember to verify the outer tarball checksum against the registry checksum returned from `hex_repo:get_package(Config, Package)`. Create package tarball: ```erlang {ok, #{tarball := Tarball, inner_checksum := InnerChecksum, outer_checksum := OuterChecksum}} = hex_tarball:create(Metadata, Contents). ``` ## Configuration The default configuration, provided by `hex_core:default_config/0`, uses built-in httpc-based adapter and Hex.pm APIs: and . HTTP client configuration can be overridden as follows: ```erlang Config = maps:merge(hex_core:default_config(), #{ http_adapter => {my_hackney_adapter, my_hackney_adapter_config}, http_user_agent_fragment => <<"(my_app/0.1.0) (hackney/1.12.1) ">> }), hex_repo:get_names(Config). %% my_hackney_adapter.erl -module(my_hackney_adapter). -behaviour(hex_http). -exports([request/5]). request(Method, URI, ReqHeaders, Body, AdapterConfig) -> %% ... ``` See the [`hex_core` module](src/hex_core.erl) for more information about the configuration. ## Wrapper Module It's recommended to write a wrapper module because a lot of decisions are left to the user, e.g.: where to get configuration from, how to handle caching, failures etc. For a sample, see: [`examples/myapp_hex.erl`](examples/myapp_hex.erl). Here's an excerpt: ```erlang -module(myapp_hex). -export([ get_api_package/1, get_repo_tarball/2, get_repo_versions/0 ]). %%==================================================================== %% API functions %%==================================================================== get_api_package(Name) -> case hex_api_package:get(config(), Name) of {ok, {200, _Headers, Payload}} -> {ok, Payload}; Other -> Other end. get_repo_versions() -> case hex_repo:get_versions(config()) of {ok, {200, _Headers, Payload}} -> {ok, maps:get(packages, Payload)}; Other -> Other end. %%==================================================================== %% Internal functions %%==================================================================== config() -> Config1 = hex_core:default_config(), Config2 = put_http_config(Config1), Config3 = maybe_put_api_key(Config2), Config3. put_http_config(Config) -> maps:put(http_user_agent_fragment, <<"(myapp/1.0.0) (httpc)">>, Config). maybe_put_api_key(Config) -> case os:getenv("HEX_API_KEY") of false -> Config; Key -> maps:put(api_key, Key, Config) end. ``` ## Installation ### Rebar3 Add to `rebar.config`: ```erlang {deps, [ {hex_core, "0.12.0"} ]} ``` ### Mix Add to `mix.exs`: ```elixir defp deps do [ {:hex_core, "~> 0.12.0"} ] end ``` ## Development * Run `rebar3 as dev compile` to re-generate protobuf files * Run `rebar3 as test proper` for property-based tests * Run `rebar3 ex_doc` to generate documentation