An open-source GS1 Digital Link conformant resolver, written in Clojure. Passes all 18 conformance criteria of the GS1-Conformant resolver standard v1.2.0 and scores 31/31 against the official browser-based test suite.
A reference implementation for people who want a working, spec-conformant GS1 DL resolver they can read, run, and extend. In-memory store, no database, no external service dependencies at runtime. The codebase is small enough to read in one sitting.
- 18 deftests covering each conformance criterion (58 assertions)
- 31/31 on the upstream GS1 test suite, driven from headless Chromium
- Continuous integration runs both suites on every push
Out of scope for this version:
- Full GS1 Digital Link Compression decoder (a stub lookup table is in place)
- Persistence beyond an in-memory atom
gs1.resolver.path ;; pure path-segment helpers
gs1.resolver.store ;; in-memory atom + put!/get-record
gs1.resolver.fixtures ;; conformance seed data
gs1.resolver.parse ;; DL URI validation + GS1 AI vocabulary
gs1.resolver.linkset ;; RFC 9264 linkset builder (pure)
gs1.resolver.handler ;; Ring handler — orchestrates the above
gs1.resolver.server ;; Jetty entry point (HTTP + optional HTTPS)
Dependencies flow one way. Linkset is a pure function of records, not a function that reaches into the store. The handler does the lookup.
Requirements: Java 21+, Clojure CLI.
clojure -M:run
# starts on http://localhost:8080The server seeds a single GTIN (09506000164908) with five link types
so you can probe the conformance behaviour immediately:
curl -i http://localhost:8080/01/09506000164908
curl -i 'http://localhost:8080/01/09506000164908?linkType=linkset'
curl -i 'http://localhost:8080/01/09506000164908?linkType=gs1:certificationInfo'
curl -i http://localhost:8080/.well-known/gs1resolverThe conformance suite expects HTTPS. Generate a self-signed keystore once:
./scripts/gen-keystore.shThen run with TLS:
SSL_PORT=8443 clojure -M:run
# starts on http://localhost:8080 and https://localhost:8443Environment variables: PORT (default 8080), SSL_PORT (no default —
HTTPS off unless set), KEYSTORE (default resources/keystore.jks),
KEY_PASSWORD (default changeit).
clojure -X:testDrives the upstream JavaScript suite against your running resolver.
# one-time setup
npm install
npx playwright install chromium
sudo npx playwright install-deps chromium # Linux only
./scripts/fetch-harness.sh # download upstream GS1 suite assets
./scripts/gen-keystore.sh
# in one terminal
SSL_PORT=8443 clojure -M:run
# in another
node run-conformance.mjsThe runner boots a tiny static server for the harness page, intercepts
the suite's calls to tester.php via Playwright's page.route (emulating
the upstream PHP cURL helper with Node fetch), waits until the suite's
result list stops growing, then prints a pass/fail summary.
Edit src/gs1/resolver/fixtures.clj. Each record looks like:
{"/01/09506000164908"
{:item-description "Crew neck white t-shirt"
:default-link-type "gs1:pip"
:links
{"gs1:pip"
[{:href "https://example.com/pip"
:title "Product Info"
:type "text/html"
:hreflang ["en"]}]
"gs1:certificationInfo" [...]}}}Anchor paths follow the canonical GS1 DL form: /01/{gtin} for primary,
/01/{gtin}/21/{serial} for a serial-number qualifier, etc. The
:default-link-type MUST exist as a key in :links.
clojure -M:nrepl
# nREPL on port 1667 with CIDER middlewareThe conformance harness loads three unmodified files from the GS1 test
suite plus an Ajv bundle. They live in harness/ at runtime but are
not committed — scripts/fetch-harness.sh downloads them, pinned to
known commit SHAs and verified by checksum. To refresh against a newer
upstream, update the SHAs at the top of the script, re-run it, and copy
the printed checksums into the script's EXPECTED_ table.
Issues and pull requests welcome. Before opening a PR:
clojure -X:testpassesnode run-conformance.mjsreturns 31/31 (or document a regression)
Apache License 2.0 — see LICENSE.
Third-party vendored components in harness/ are credited in NOTICE.