github amberframework/amber v2.0.0-beta.6
Amber v2.0.0-beta.6

pre-release4 hours ago

Amber 2.0.0-beta.6

Amber 2.0.0-beta.6 takes libxml2 out of every Amber V2 application. Until
this release, every V2 app linked libxml2, and with it liblzma, because the
schema layer could parse XML request bodies. XML is now opt-in, and the
opt-in is Mica, a pure-Crystal XML parser, so an application that accepts
XML still links no XML library.

What changed

  • XML request bodies are opt-in (#1414). The libxml2-backed
    Amber::Schema::Parser::XMLParser is removed. A request sent as
    application/xml, text/xml or any +xml type is answered with
    415 Unsupported Media Type unless the application registers a parser.
  • ParserRegistry.register_body_parser (#1414) lets an application add
    a request body format with one call at boot. A parser that raises answers
    the request with 400 invalid_request_body.
  • CI is pinned (#1420): Crystal 1.22.1 tarballs checked by sha256,
    GitHub Actions by full commit SHA, the CircleCI image by digest,
    dependencies from a committed shard.lock with --frozen, and a second job
    that builds crystal-alpha from an exact commit and runs the suite on it.
  • Specs pass on Crystal 1.22 (#1418), which rejects the empty request
    resource and the malformed multipart fixture some specs used.

Turning XML back on with Mica

Add Mica v0.1.0
(release,
commit 6a4866e) to shard.yml:

dependencies:
  mica:
    github: amberframework/mica
    version: 0.1.0

Register it in config/initializers/request_body_parsers.cr, and make sure
config/application.cr loads the initializers (require "./initializers/*";
applications generated by Amber CLI 2.0.6 load config/*.cr only):

require "mica/xml/fields"

{"application/xml", "text/xml"}.each do |media_type|
  Amber::Schema::Parser::ParserRegistry.register_body_parser(media_type) do |body|
    Mica::XML::Fields.read(body)
  end
end

List the media types in the action's schema:

class CreateSubscriberSchema < Amber::Schema::Definition
  content_type "application/json", "application/xml", "text/xml"
  field :email_address, String, required: true, format: "email"
end

Mica::XML::Fields.read turns an XML body into the same fields a JSON object
gives, so <subscriber><email_address>ada@example.com</email_address></subscriber>
and {"email_address": "ada@example.com"} validate the same way. It refuses a
DTD at the <!DOCTYPE (so no external entity, entity expansion or attribute
default reaches the fields), caps bodies at 1 MiB and nesting at 64 levels,
and charges every step of the read to a work budget.

Measured

A sample Amber V2 + Grant + PostgreSQL application
(examples/zero_link_app), built as a static musl binary with
--static --release --no-debug in the official Crystal 1.22.1 Alpine image
(pinned by digest, linux/arm64), from an empty compiler cache, twice per variant. Before
is Amber 2.0.0-beta.5 with its built-in XML parser; after is this release
with Mica registered. Evidence, scripts and raw logs:
examples/zero_link_app/evidence/.

before (beta.5) after (#1414 + Mica)
libxml2 objects linked 108 0
liblzma objects linked 68 0
zlib objects linked 15 11
static archives used 12 10
defined xml* / lzma_* / zlib symbols 2,420 / 109 / 46 0 / 0 / 46
-l flags adds -lxml2 -llzma neither
binary size 29,600,248 bytes 26,103,720 bytes (11.8% smaller)
binary size after strip 11,169,352 bytes 10,381,728 bytes (7.1% smaller)
dynamic libraries needed (readelf -d) none (static PIE) none (static PIE)
clean release build, 2 runs 109.0 s, 119.0 s 93.0 s, 81.0 s

Both binaries are static: readelf -d lists no NEEDED entries and there is
no program interpreter. zlib stays linked because Grant
(grant/encryption/compression.cr) and asset_pipeline
(asset_pipeline/static_assets.cr) use the standard library's Compress;
Amber itself does not. The remaining archives are libc, libcrypto, libssl,
libgc, libgmp, libpcre2-8, libyaml, libz and the compiler's libgcc and
libgcc_eh.

Build times are reported, not claimed: in this Docker VM (4 CPUs) the same
beta.5 build ranged from 83 to 133 seconds across five clean builds, so two
runs per variant cannot separate a compile-time change from noise.

The after binary, run in an Alpine image with no libxml2 installed, answered
JSON, application/xml and text/xml bodies with 201 and wrote 3 rows; an
invalid email with 422; malformed XML with 400 and its position; an external
entity and a billion-laughs body with 400 at the <!DOCTYPE (byte 0); and
application/xhtml+xml with 415 (results/after.smoke.txt).

Measured on 2026-11-01 with Amber b9d246ba and Mica 2a4ab10, now
c11b6cc after a history rewrite that left file contents identical. The
sample now pins Mica v0.1.0 (6a4866e; see ../shard.lock).

Upgrade

An application that accepted XML bodies through the schema API either moves
those endpoints to JSON or registers Mica as above. Everything else upgrades
by changing the version:

dependencies:
  amber:
    github: amberframework/amber
    version: 2.0.0-beta.6

Release

The tag is v2.0.0-beta.6. The GitHub release for the tag states the tagged
commit hash, its tree hash, and the sha256 of the source tarball attached to
the release (scripts/release_checksums.sh v2.0.0-beta.6 produces all three).
A file cannot carry its own tarball's hash, so they live in the release.

Verify this release

Commit: a61cb0e436d3cffbc1feb381187b03c25e45f743
Tree: aef5979f5a1beec1c825a05ef83d8942583b5e38
Tarball: amber-2.0.0-beta.6.tar.gz (git archive --format=tar.gz --prefix=amber-2.0.0-beta.6/ a61cb0e436d3cffbc1feb381187b03c25e45f743)
Tarball sha256: ec0e01f0da912fbc618782ff7b8e610d3414a4c26a6be8b6e6b335aeba388d24

Don't miss a new amber release

NewReleases is sending notifications on new releases.