Layer 4 — mechanical reference for the
sandbar.codec.protocol/Codecdefprotocol and thesandbar.codecmediator namespace. For practical authoring seedoc/guides/implementing-a-codec.md; for the design rationale seedoc/concepts/codec-layer.md.
(ns sandbar.codec.protocol)
(defprotocol Codec
(parse [codec input opts])
(emit [codec entity opts])
(mime-types [codec])
(supports? [codec class-ident])
(round-trip-test [codec entity]))
(parse codec input opts)Parse a native-representation input into an entity-spec map suitable for dt/make.
Arguments
codec — the codec value (a deftype/defrecord instance)input — String or java.io.Reader / java.io.InputStreamopts — map; recognized keys:
:class — class-ident hint disambiguating the entity's type:version — codec-specific format version:strict? — whether to fail (true) or warn (false) on lossy parse:source-uri — origin URI for error reportingReturns entity-spec map of the form {:dt/type :foo/Bar :slots {...}} — or a collection of such maps for multi-entity inputs.
Errors — codec implementations may throw ex-info with :codec/parse-error on malformed input; :data should include enough context to locate the error (line number, byte offset).
(emit codec entity opts)Emit a Sandbar entity as a native-representation string.
Arguments
codec — the codec valueentity — entity map (or collection of entity maps) carrying :dt/type and slot valuesopts — map; recognized keys:
:pretty? — pretty-print where the format supports it:include-id? — include :db/id in the output (default false):version — codec-specific output versionReturns String (or java.io.OutputStream-shaped value for streaming codecs).
Default behavior — derived attributes (:db/id, :db/ident) are not emitted unless :include-id? is true. This is the neutrality discipline: emitted output must travel between model-equivalent backends without database-local identifiers leaking.
(mime-types codec)Returns a vector of MIME type strings the codec accepts and emits.
(proto/mime-types markdown-codec)
;; => ["text/markdown" "text/x-markdown"]
Used by sandbar.codec/parse-mime and codec-for-mime for content-negotiation routing.
(supports? codec class-ident)Predicate — can this codec faithfully round-trip instances of class-ident?
Returns true / false.
Generic codecs (JSON, EDN) typically return true for any class. Specialized codecs (markdown for :mm/Memory) return true only for the classes they explicitly handle.
Used by the mediator to validate codec/class pairing and surface clear errors on mismatch.
(round-trip-test codec entity)Self-diagnostic — verify (parse (emit entity)) ≅ entity for the given entity.
Returns
{:ok? <boolean>
:emitted <string>
:reparsed <entity-spec>
:diff <diff-or-nil>}
When :ok? is false, :diff carries a structural diff of the original vs. reparsed entity.
Used by golden-fixture tests and mediator self-checks.
sandbar.codec)(ns my.app
(:require [sandbar.codec :as codec]))
(codec/register! format codec)Registers a codec under a format keyword. Idempotent — re-registering replaces.
(codec/register! :codec/edn (->EdnCodec))
;; => :codec/edn
Throws ex-info if format is not a keyword or codec doesn't satisfy the Codec protocol.
(codec/unregister! format)Removes a codec from the registry. Idempotent.
(codec/list-codecs)Returns a vector of {:format ... :mime-types [...]} for every registered codec, sorted by format keyword.
(codec/list-codecs)
;; => [{:format :codec/json :mime-types ["application/json"]}
;; {:format :codec/markdown :mime-types ["text/markdown"]}]
(codec/codec-for format)Looks up the codec for the format keyword. Returns nil if absent.
(codec/codec-for :codec/markdown)
;; => #<MarkdownCodec ...>
(codec/codec-for-mime mime-type)Looks up the codec whose mime-types includes the given MIME string. Returns [format codec] or nil. When multiple codecs claim the same MIME type, the first registered wins.
(codec/parse input opts)Parses input using the codec resolved per opts.
Resolution order:
opts has :format, look up by format keyword.opts has :mime-type, look up by MIME.opts has :class, use the class's :dt/native-codec.:codec/no-codec-resolvable.(codec/parse "---\nname: Foo\n---\n# Hello"
{:format :codec/markdown :class :mm/Memory})
;; => {:dt/type :mm/Memory
;; :mm.memory/name "Foo"
;; :mm.memory/first-section ...}
(codec/parse-mime mime-type input)Convenience shorthand: (codec/parse input {:mime-type mime-type}).
Used by REST handlers consuming Content-Type.
(codec/parse-for-class class input opts)Convenience shorthand: (codec/parse input (assoc opts :class class)).
Resolves the codec from the class's :dt/native-codec directly.
(codec/emit entity opts)Emits entity using the codec resolved per opts.
Resolution order (same as parse):
:format keyword in opts.:mime-type in opts.:dt/type → class's :dt/native-codec.(codec/emit some-memory-entity {:format :codec/markdown})
;; => "---\nname: Foo\n---\n# Hello"
(codec/emit-mime mime-type entity)Convenience shorthand: (codec/emit entity {:mime-type mime-type}).
(codec/round-trip-test format-or-codec entity)Delegates to the codec's proto/round-trip-test. Returns the same {:ok? :emitted :reparsed :diff} shape.
Codecs operate at the model layer (:dt/Class, :dt/slots, class hierarchy) — never at the Datomic-schema layer (:db.install/_attribute, :db.unique/identity, :db/id).
The mediator targets sandbar.db.datatype/* for class-default-codec lookups — never raw datomic.api. This preserves the substrate-layering discipline established for the dt/* API.
Per interaction/export_format_must_be_neutral_and_database_agnostic, the wire format must be portable across model-equivalent backends. A future Sandbar instance with a different storage backend (XTDB, raw filesystem) should ingest a Markdown projection from a Datomic-backed instance without loss.
Sandbar ships two reference codecs:
sandbar.codec.markdown — :codec/markdown["text/markdown" "text/x-markdown"]:mm/Memory and :mm/Section (via section-tree walk):db/ident, :mm.memory/rel-path, :mm.memory/first-section; preserve frontmatter ordering on parse; alphabetize on emit when no order is semantically significant:mm/Section entities with pairwise sibling navigationsandbar.codec.json — :codec/json["application/json"]:db.type/long → JSON number; :db.type/bigdec → JSON string (no precision loss); :db.type/instant → ISO 8601 stringex-info :codec/... key | Cause |
|---|---|
:codec/parse-error | Malformed input |
:codec/no-codec-resolvable | None of the resolution paths produced a codec |
:codec/unsupported-class | supports? returned false for the requested class |
:codec/round-trip-failed | round-trip-test produced :ok? false |
All carry an :ex-data map with structured context.
doc/concepts/codec-layer.md — theoretical foundationdoc/guides/implementing-a-codec.md — practical authoring guidedoc/api/dt-star.md — dt/make :format/:source and dt/emit-entity integrationdoc/api/mcp-verbs.md — sandbar.codec.list MCP verbCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |