diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fd98658..ad38095 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,4 @@ -name: Publish to Hex.pm +name: CI on: push: @@ -13,31 +13,48 @@ jobs: name: Test runs-on: ${{ matrix.os }} strategy: + # Let every OTP release report its own result instead of cancelling + # the remaining jobs as soon as one of them fails. + fail-fast: false matrix: + # Each OTP release is paired with the newest rebar3 release that + # supports it: 3.24 is the last one for OTP 25, 3.25 the last one + # for OTP 26, and 3.28 covers OTP 27 and later. + # + # OTP 29 is not listed yet: the test dependency meck 0.9.2 does not + # compile there (its bare `catch` is deprecated in OTP 29), and the + # meck releases that do compile dropped OTP 25. + # # ubuntu-latest resolves to Ubuntu 26.04, for which no OTP 25 builds # are published (erlef/setup-beam supports OTP 26+ there and 24.3+ on # ubuntu-24.04), so the OTP 25 job runs on ubuntu-24.04. include: - otp: '25' + rebar3: '3.24' os: ubuntu-24.04 - otp: '26' + rebar3: '3.25' os: ubuntu-latest - otp: '27' + rebar3: '3.28' + os: ubuntu-latest + - otp: '28' + rebar3: '3.28' os: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: erlef/setup-beam@v1 with: otp-version: ${{ matrix.otp }} - rebar3-version: '3.24' + rebar3-version: ${{ matrix.rebar3 }} - name: Restore dependencies cache - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: _build - key: ${{ runner.os }}-otp-${{ matrix.otp }}-build-${{ hashFiles('rebar.config') }} - restore-keys: ${{ runner.os }}-otp-${{ matrix.otp }}-build- + key: ${{ runner.os }}-otp-${{ matrix.otp }}-rebar3-${{ matrix.rebar3 }}-build-${{ hashFiles('rebar.config') }} + restore-keys: ${{ runner.os }}-otp-${{ matrix.otp }}-rebar3-${{ matrix.rebar3 }}-build- - name: Compile run: rebar3 compile @@ -59,19 +76,19 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: erlef/setup-beam@v1 with: otp-version: '27' - rebar3-version: '3.24' + rebar3-version: '3.28' - name: Restore dependencies cache - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: _build - key: ${{ runner.os }}-otp-27-build-${{ hashFiles('rebar.config') }} - restore-keys: ${{ runner.os }}-otp-27-build- + key: ${{ runner.os }}-otp-27-rebar3-3.28-build-${{ hashFiles('rebar.config') }} + restore-keys: ${{ runner.os }}-otp-27-rebar3-3.28-build- - name: Publish to Hex.pm run: rebar3 hex publish --repo hexpm --yes diff --git a/README.md b/README.md index 41f25b6..74eb48c 100644 --- a/README.md +++ b/README.md @@ -79,23 +79,25 @@ maps:get(<<"country_name">>, Result). %% Access response fields maps:get(<<"ip">>, Result). %% => <<"8.8.8.8">> maps:get(<<"country_name">>, Result). %% => <<"United States">> -maps:get(<<"city">>, Result). %% => <<"Ashburn">> -maps:get(<<"latitude">>, Result). %% => 39.03 -maps:get(<<"longitude">>, Result). %% => -77.5 +maps:get(<<"country_code">>, Result). %% => <<"US">> + +%% Fields the API has no data for are null. 8.8.8.8 is an anycast address +%% located at country level only, so it has no city and country-level coordinates. +maps:get(<<"city">>, Result). %% => null +maps:get(<<"latitude">>, Result). %% => 39.7599983215332 +maps:get(<<"longitude">>, Result). %% => -98.5 %% Nested data ASN = maps:get(<<"asn">>, Result). maps:get(<<"name">>, ASN). %% => <<"Google LLC">> Threat = maps:get(<<"threat">>, Result). -maps:get(<<"is_vpn">>, Threat). %% => false +maps:get(<<"is_vpn">>, Threat). %% => false +maps:get(<<"is_datacenter">>, Threat). %% => true %% Flags the API includes only when they are true: read them with a default maps:get(<<"is_anycast">>, Result, false). %% => true maps:get(<<"is_satellite">>, Result, false). %% => false - -TimeZone = maps:get(<<"time_zone">>, Result). -maps:get(<<"name">>, TimeZone). %% => <<"America/New_York">> ``` ### Filter Response Fields @@ -144,7 +146,7 @@ end. ## Connection reuse -The `ipdata` application owns a dedicated `httpc` profile, also named `ipdata`, that keeps persistent HTTPS connections (HTTP/1.1 keep-alive) to the API for as long as the application runs. Start the application once per node and keep it running: every lookup from every process then goes out on an already established connection instead of paying a new TCP and TLS handshake (roughly 660 to 980 ms for a cold connection to api.ipdata.co against 220 to 300 ms for a request on a reused one). Idle connections are closed after 60 seconds. HTTP/2 is not available with `httpc`, so the saving is the TLS handshake, not multiplexing; an HTTP/2 client such as `gun` would need a connection-owner process and a new client lifecycle, which is out of scope for this library. TLS 1.3 session tickets are enabled so that a connection opened after the idle timeout can resume the previous TLS session. +The `ipdata` application owns a dedicated `httpc` profile, also named `ipdata`, that keeps persistent HTTPS connections (HTTP/1.1 keep-alive) to the API for as long as the application runs. Start the application once per node and keep it running: every lookup from every process then goes out on an already established connection. A new connection pays a TCP and TLS handshake; a request on a reused connection does not. Idle connections are closed after 60 seconds. HTTP/2 is not available with `httpc`, so the saving is the TLS handshake, not multiplexing; an HTTP/2 client such as `gun` would need a connection-owner process and a new client lifecycle, which is out of scope for this library. TLS 1.3 session tickets are enabled so that a connection opened after the idle timeout can resume the previous TLS session. A client map returned by `ipdata:new/1,2` is plain immutable data: it holds no process, socket or other resource, so it can be shared freely between processes, kept in application state or rebuilt at any time, and there is nothing to close. `application:stop(ipdata)` stops the profile and closes its connections. If a request is made before the application has been started, the profile is started on demand (`inets` and `ssl` must be running).