Skip to content

Remove jsx use otp27 json - #261

Merged
elbrujohalcon merged 4 commits into
inaka:masterfrom
kivra-kenand:remove-jsx-use-otp27-json
Aug 24, 2026
Merged

elbrujohalcon merged 4 commits into
inaka:masterfrom
kivra-kenand:remove-jsx-use-otp27-json

Conversation

@kivra-kenand

Copy link
Copy Markdown
Contributor

Why

jsx encodes one notification map and two small JWT objects — and it is the worst tool available for it on two counts.

It is unmaintained.
Last Hex release is 3.1.0 from April 2021, over five years ago. The repo is not formally archived and saw a commit in mid-2024, but 25 issues are open and nothing has shipped. It is a dependency that ~73M downloads inherit and nobody is steering.

It is slow.
jsx is pure Erlang with no NIF and is widely reported as the slowest of the mainstream Erlang JSON libraries. Measured on this library's own payloads (OTP 28.4.2, 200k iterations, iolist_to_binary included so it is like-for-like):

workload jsx stdlib json
notification, 153 bytes 10.73 us/op 2.56 us/op 4.2x faster
JWT header 4.29 us/op 0.62 us/op 6.9x faster

For a push provider on a hot path, paying 4-7x on every notification for a stalled dependency makes no sense once stdlib does the same job. This drops the runtime dependency list from three to two (gun, base64url).

The cost is the OTP 27 floor: this drops OTP 24-26, which the old minimum_otp_vsn allowed.
That is breaking and wants a version bump — the number is yours to pick.

What changed

jsx -> json.
Both push paths use iolist_to_binary(json:encode(_)); the wrap is required because json:encode/1 returns iodata(). generate_token/3 builds its JWT header and payload as maps — unlike jsx, json:encode/1 rejects proplists with {unsupported_type, _}. jsx dropped from rebar.config, rebar.lock and applications; minimum_otp_vsn 24 -> 27; CI matrix 27/28/29, which also needed rebar3 (3.22.1 cannot build on 28+), checkout and setup-beam bumped.

Tests for the encoding.
push_notification_json_body asserts the body handed to gun:post/4 round-trips via json:decode/1 across binary keys, atom keys, nesting and integers, and asserts it is_binary/1 — the guard for the iodata() pitfall.
generate_token_jwt_claims decodes the JWT and checks its claims, guarding the proplist-to-map change.

ct re-enabled.
It was disabled in 3adaba0 because the suite still mocked gun:await/3 and gun:await_body/3, APIs apns_connection stopped calling when responses moved to {gun_response, _} / {gun_data, _} messages — those tests were waiting for a reply that could never arrive. The mocks now drive the async API. Also: push_notification_timeout expected a bare timeout where the code returns {error, timeout}, and apns_meta_SUITE is deleted — it wrapped xref/dialyzer/elvis, all already separate workflow steps, and was failing on a katana_test elvis_core mismatch (undef export_used_types) plus a missing PLT.
That drops katana_test and mixer as test deps.

Two calls worth extra attention

{error, not_connection_owner} is gone.
The README said pushes must come from the creating process, but nothing has enforced it since requests became per-stream with their own From, which made multi-process pushing safe. The stale test was the last thing asserting it.
It is replaced by push_notification_from_any_process, which proves the current behaviour, and the README now documents it. Say the word and I will restore the guard instead.

response() and json() corrected.
The new tests made dialyzer fail, all of it tracing to specs that did not match the code: the body is binary() not [term()]; timeouts return {error, term()}, not the bare timeout that was never returned; and json() excluded the atom keys the README's own examples use. Fixing the types beat loosening the tests, but this changes exported types, so it is the easiest thing here to split out.

Verification

xref, dialyzer, hank, lint, ct all clean; 19 tests pass. Local runs on OTP 28.4.2 / rebar3 3.26.0; the matrix covers 27-29. The encoding test is mutation-checked — removing iolist_to_binary makes it fail.

Deliberately not included

gun deliberately left at 2.1.0: worth bumping, but gun is fully mocked here so a bump would stay green even if the transport regressed.

@dsrosario

Copy link
Copy Markdown
Collaborator

Code change looks good to me, but CI didn't run.
@kivra-kenand can you update the CHANGELOG.md?

@elbrujohalcon

Copy link
Copy Markdown
Member

@kivra-kenand thanks! Can you please update meck so CI can run on OTP29?

@kivra-kenand

Copy link
Copy Markdown
Contributor Author

Yes sorry, been on vacation (silly Sweden). Looking into it now!

@kivra-kenand

Copy link
Copy Markdown
Contributor Author

Code change looks good to me, but CI didn't run. @kivra-kenand can you update the CHANGELOG.md?

I see that CHANGELOG does not have 2.5.0 nor 2.6.0 in it, and it is to be generated after a merge+tag. Do you want me to add a manual ## BREAKING CHANGE at the top without a tag? I am unsure how that would play along with auto-generated docs via github_changelog_generator tho.
How do you want me to proceed?

@elbrujohalcon

Copy link
Copy Markdown
Member

Code change looks good to me, but CI didn't run. @kivra-kenand can you update the CHANGELOG.md?

I see that CHANGELOG does not have 2.5.0 nor 2.6.0 in it, and it is to be generated after a merge+tag. Do you want me to add a manual ## BREAKING CHANGE at the top without a tag? I am unsure how that would play along with auto-generated docs via github_changelog_generator tho. How do you want me to proceed?

I believe that we stopped using CHANGELOG.md a long time ago… when github releases started to generate release notes on their own. I think those are more useful than CHANGELOG.md, anyway.

@elbrujohalcon
elbrujohalcon merged commit c07be82 into inaka:master Aug 24, 2026
3 checks passed
@elbrujohalcon

Copy link
Copy Markdown
Member

@kivra-kenand / @dsrosario : https://hex.pm/packages/apns4erl/3.0.0 enjoy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants