Idiomatic Clojure authoring for URPX entities. Output of every builder is
a coerced (typed) entity ready for urpx.price/resolve-prices,
urpx.emit/write-*, or any other consumer of urpx.schema-shaped data.
(require '[urpx.dsl :as dsl])
(dsl/dsl-scope (dsl/season :as :ex/summer :season-name "Summer" :month [5 6 7 8 9 10]) (dsl/ledger :as :ex/commodity :name "Commodity" :ledger-type :urpx/supplyLedger) (dsl/price-definition :as :ex/pd-summer :name "Commodity Summer" :applies-in-season :ex/summer :references-ledger :ex/commodity))
dsl-scope macro naming entities by keywordcurie the default keyword -> @id functionrdf-list an ordered RDF list valueliteral a typed literal, a JSON-LD value objectmake-builder generator: schema → constructor functionentity-type introspect a schema for its @type literalurpx.schema
(season, metric-input, price-definition,
rate-plan, urpx-package, coverage-claim, …),
with no exceptions.Every builder:
:jsonld/type automatically from the schema (no boilerplate).:urpx/* keys (:urpx/seasonName) or auto-derived kebab-case short
forms (:season-name); both can be mixed in one call.:decode/urpx-jsonld
decoders: BigDecimal, integer, exact ratio or xsd:decimal string →
BigDecimal (a double, a float or an inexact ratio refuses, since
it has already lost the written scale), ISO string → LocalDate /
LocalDateTime / LocalTime (zoned forms when a dateTime or time
carries an offset),
--MM xsd:gMonth → int month-of-year,
ISO 8601 string → Duration / Period.:urpx/X keyword value as the vocabulary term
{:jsonld/id "urpx:X"} (:day-type :urpx/allDays), element-wise
in a vector.nil-valued kwargs (so (builder :foo (when … x)) cleanly
omits the field). Explicit false is preserved.ex-info with the Malli explanation on failure.Inside a dsl-scope, builders also take :as k for the entity's @id
and read other qualified keywords as references; see dsl-scope.
Idiomatic Clojure authoring for URPX entities. Output of every builder is
a coerced (typed) entity ready for `urpx.price/resolve-prices`,
`urpx.emit/write-*`, or any other consumer of `urpx.schema`-shaped data.
## At a glance
(require '[urpx.dsl :as dsl])
(dsl/dsl-scope
(dsl/season :as :ex/summer :season-name "Summer" :month [5 6 7 8 9 10])
(dsl/ledger :as :ex/commodity
:name "Commodity"
:ledger-type :urpx/supplyLedger)
(dsl/price-definition :as :ex/pd-summer
:name "Commodity Summer"
:applies-in-season :ex/summer
:references-ledger :ex/commodity))
## Public API
- `dsl-scope` macro naming entities by keyword
- `curie` the default keyword -> @id function
- `rdf-list` an ordered RDF list value
- `literal` a typed literal, a JSON-LD value object
- `make-builder` generator: schema → constructor function
- `entity-type` introspect a schema for its @type literal
- a named builder for every entity-type schema in `urpx.schema`
(`season`, `metric-input`, `price-definition`,
`rate-plan`, `urpx-package`, `coverage-claim`, …),
with no exceptions.
## Builder semantics
Every builder:
- Sets `:jsonld/type` automatically from the schema (no boilerplate).
- Accepts kwargs (or a Clojure 1.11+ trailing map) of either canonical
`:urpx/*` keys (`:urpx/seasonName`) or auto-derived kebab-case short
forms (`:season-name`); both can be mixed in one call.
- Auto-coerces raw inputs through the schema's `:decode/urpx-jsonld`
decoders: BigDecimal, integer, exact ratio or xsd:decimal string →
`BigDecimal` (a double, a float or an inexact ratio refuses, since
it has already lost the written scale), ISO string → `LocalDate` /
`LocalDateTime` / `LocalTime` (zoned forms when a dateTime or time
carries an offset),
`--MM` xsd:gMonth → int month-of-year,
ISO 8601 string → `Duration` / `Period`.
- Reads a `:urpx/X` keyword value as the vocabulary term
`{:jsonld/id "urpx:X"}` (`:day-type :urpx/allDays`), element-wise
in a vector.
- Drops `nil`-valued kwargs (so `(builder :foo (when … x))` cleanly
omits the field). Explicit `false` is preserved.
- Validates the assembled entity against the schema and throws
`ex-info` with the Malli explanation on failure.
Inside a `dsl-scope`, builders also take `:as k` for the entity's @id
and read other qualified keywords as references; see `dsl-scope`.(curie k)The default dsl-scope :id-fn: :scl/summer -> "scl:summer".
The default `dsl-scope` `:id-fn`: `:scl/summer` -> "scl:summer".
(dsl-scope & body)Evaluate body in a scope where entities are named by keyword. An
optional leading map literal holds options:
:id-fn keyword -> @id string; default curie (:scl/summer ->
"scl:summer")
:outside {keyword @type}: nodes built elsewhere, such as in another
document, that keywords in the scope may name. A keyword
here reads as a reference to (id-fn k) and is judged
against the slot's range by the @type given, as a local one
is; no builder in the scope may declare it.
Inside the scope, a builder:
:as k to set the entity's @id to (id-fn k);urpx and jsonld
namespaces as the Ref {:jsonld/id (id-fn k)};:as or by an
explicit :jsonld/id, for a different node. Building the same node
twice, to embed it in two places, is accepted.A reference may come before or after its declaration. When the body
finishes, the scope realizes every lazy sequence in its value, so a
builder inside for or map runs in the scope. Then every keyword
referenced whose @id no builder in the scope declared is refused in one
ex-info naming each such keyword and the @type of each builder that
referenced it, under :unresolved. Then every keyword referenced from a
slot whose entity is not an instance of the slot's
urpx.schema/ranges class is refused in one ex-info, under
:mistyped, as {:ref k :type t :by @type :property p :range class}.
A reference to an entity outside the scope is a Ref map,
{:jsonld/id "…"}, and is not judged.
Each scope has its own state; an inner scope does not see the outer.
Evaluate `body` in a scope where entities are named by keyword. An
optional leading map literal holds options:
:id-fn keyword -> @id string; default `curie` (`:scl/summer` ->
"scl:summer")
:outside {keyword @type}: nodes built elsewhere, such as in another
document, that keywords in the scope may name. A keyword
here reads as a reference to `(id-fn k)` and is judged
against the slot's range by the @type given, as a local one
is; no builder in the scope may declare it.
Inside the scope, a builder:
- takes `:as k` to set the entity's @id to `(id-fn k)`;
- reads a qualified-keyword value outside the `urpx` and `jsonld`
namespaces as the Ref `{:jsonld/id (id-fn k)}`;
- refuses an @id already declared in the scope, by `:as` or by an
explicit `:jsonld/id`, for a different node. Building the same node
twice, to embed it in two places, is accepted.
A reference may come before or after its declaration. When the body
finishes, the scope realizes every lazy sequence in its value, so a
builder inside `for` or `map` runs in the scope. Then every keyword
referenced whose @id no builder in the scope declared is refused in one
ex-info naming each such keyword and the @type of each builder that
referenced it, under `:unresolved`. Then every keyword referenced from a
slot whose entity is not an instance of the slot's
`urpx.schema/ranges` class is refused in one ex-info, under
`:mistyped`, as `{:ref k :type t :by @type :property p :range class}`.
A reference to an entity outside the scope is a Ref map,
`{:jsonld/id "…"}`, and is not judged.
Each scope has its own state; an inner scope does not see the outer.(entity-type schema)Extract the @type literal (e.g. "urpx:Season") from a Malli entity
schema by introspecting its :jsonld/type entry — which must have the
form [:= "urpx:X"]. Schemas wrapped in [:schema {:registry ...} <inner>] (e.g. ConditionExpression) are unwrapped via m/deref first.
Returns nil if the schema doesn't carry a :jsonld/type entry shaped
that way.
Extract the @type literal (e.g. "urpx:Season") from a Malli entity
schema by introspecting its :jsonld/type entry — which must have the
form `[:= "urpx:X"]`. Schemas wrapped in `[:schema {:registry ...}
<inner>]` (e.g. ConditionExpression) are unwrapped via `m/deref` first.
Returns nil if the schema doesn't carry a :jsonld/type entry shaped
that way.(literal value datatype)A JSON-LD value object: the lexical form value typed datatype,
{:jsonld/value value :jsonld/type datatype}.
(dsl/rdf-list [(dsl/literal "2009" "xsd:gYear") (dsl/literal "2010" "xsd:gYear")])
A JSON-LD value object: the lexical form `value` typed `datatype`,
`{:jsonld/value value :jsonld/type datatype}`.
(dsl/rdf-list [(dsl/literal "2009" "xsd:gYear")
(dsl/literal "2010" "xsd:gYear")])(make-builder schema)(make-builder schema {:keys [passthrough]})Given a urpx.schema entity schema, return a constructor function. The constructor:
dsl-scope, takes :as k for the entity's @id and reads
other qualified-keyword values as references; see dsl-scope.
Outside one, refuses :as.:urpx/X keyword value as the Ref {:jsonld/id "urpx:X"},
inside a scope or not.:jsonld/* key, nor in :passthrough, naming the key and the
entity type, so a misspelled key throws instead of being emitted.urpx.coerce/jsonld-transformer:
already-typed values pass through; raw values (numbers, strings,
gMonth strings, …) are decoded per the schema's :decode/urpx-jsonld
decoders.:reason ::inexact-decimal.urpx.schema/ranges class, under :mistyped as
urpx.schema/mistyped-references reports it.The schema must carry a :jsonld/type entry with shape [:= "urpx:X"]
so the @type can be inferred.
Options:
:passthrough, a collection of further keys the constructor admits,
for a property the schema does not model:
(make-builder schema/RatePlanVersion {:passthrough #{:ex/note}})
Given a urpx.schema entity schema, return a constructor function. The
constructor:
- Accepts kwargs (or a trailing map per Clojure 1.11+ unification) of
canonical :urpx/* keys; the entity's :jsonld/type is set automatically
from the schema.
- Inside a `dsl-scope`, takes `:as k` for the entity's @id and reads
other qualified-keyword values as references; see `dsl-scope`.
Outside one, refuses `:as`.
- Reads a `:urpx/X` keyword value as the Ref `{:jsonld/id "urpx:X"}`,
inside a scope or not.
- Refuses a key that is neither a key of the schema, a short form of
one, a `:jsonld/*` key, nor in `:passthrough`, naming the key and the
entity type, so a misspelled key throws instead of being emitted.
- Drops kwargs whose value is nil so optional fields can be supplied
conditionally without producing present-but-nil entries.
- Decodes against the schema with `urpx.coerce/jsonld-transformer`:
already-typed values pass through; raw values (numbers, strings,
gMonth strings, …) are decoded per the schema's :decode/urpx-jsonld
decoders.
- Refuses a double, a float or an inexact ratio for an xsd:decimal
field, naming the field, under `:reason ::inexact-decimal`.
- Validates the assembled entity against the schema; throws ex-info
(with the Malli explanation attached) on validation failure.
- Refuses an inline node, at any depth, that is not an instance of
its slot's `urpx.schema/ranges` class, under `:mistyped` as
`urpx.schema/mistyped-references` reports it.
The schema must carry a :jsonld/type entry with shape `[:= "urpx:X"]`
so the @type can be inferred.
Options:
- `:passthrough`, a collection of further keys the constructor admits,
for a property the schema does not model:
(make-builder schema/RatePlanVersion {:passthrough #{:ex/note}})(rdf-list members)The ordered RDF list of members, {:jsonld/list [...]}, emitted as a
JSON-LD @list. A member is written as given, except that a builder
reads a keyword member as it reads a keyword value:
(dsl/rdf-list [{:jsonld/value "P" :jsonld/type "xsd:string"} {:jsonld/value "Q" :jsonld/type "xsd:string"}]) (dsl/rdf-list [:ex/summer :ex/winter]) ; in a dsl-scope
The ordered RDF list of `members`, `{:jsonld/list [...]}`, emitted as a
JSON-LD `@list`. A member is written as given, except that a builder
reads a keyword member as it reads a keyword value:
(dsl/rdf-list [{:jsonld/value "P" :jsonld/type "xsd:string"}
{:jsonld/value "Q" :jsonld/type "xsd:string"}])
(dsl/rdf-list [:ex/summer :ex/winter]) ; in a dsl-scopecljdoc 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 |