For programmatic construction or test-fixture authoring, urpx.dsl provides idiomatic Clojure builders for every entity-type schema in urpx.schema. Output is a coerced (typed) entity, ready for urpx.price/resolve-prices, urpx.emit/write-*, or any other consumer.
(require '[urpx.dsl :as dsl])
(dsl/dsl-scope
(let [summer (dsl/season :as :ex/summer :season-name "Summer" :month [5 6 7 8 9 10])
winter (dsl/season :as :ex/winter :season-name "Winter" :month [11 12 1 2 3 4])
ledger-comm (dsl/ledger :as :ex/ledger-commodity
:name "Commodity"
:ledger-type {:jsonld/id "urpx:supplyLedger"})
pd-cs (dsl/price-definition :as :ex/pd-commodity-summer
:name "Commodity Summer"
:applies-in-season :ex/summer
:references-ledger :ex/ledger-commodity)]
;; ...assemble into a rate-plan via dsl/rate-plan, dsl/rate-plan-version, etc.
))
The DSL handles the boring parts so authoring code stays declarative:
@type is inferred from the schema (no :jsonld/type "urpx:Season" boilerplate).:decode/urpx-jsonld decoders: a BigDecimal, integer, exact ratio or xsd:decimal string → BigDecimal (a double or float refuses, since it has already lost the written scale: write 0.1610M or "0.1610"), ISO strings → LocalDate / LocalDateTime / LocalTime, or the zoned forms when a dateTime or time carries an offset, --MM xsd:gMonth → int.ex-info with the Malli explanation if the entity doesn't validate against its schema, so shape drift surfaces at the call site.:urpx/seasonName or auto-derived kebab-case :season-name (both work, can be mixed in one call).:jsonld/* throws, naming the key and the entity type. (dsl/make-builder schema/X {:passthrough #{…}}) builds a constructor that admits further keys.Inside a dsl-scope, :as k gives an entity the @id (dsl/curie k), and a qualified-keyword value anywhere else in the scope is a Ref to the same @id:
(dsl/dsl-scope
(dsl/season :as :ex/summer :season-name "Summer" :month [5 6 7 8 9 10])
(dsl/season :as :ex/winter :season-name "Winter" :month [11 12 1 2 3 4])
(dsl/ledger :as :ex/commodity :name "Commodity" :ledger-type :urpx/supplyLedger)
(dsl/price-definition :as :ex/pd-summer
:name "Summer commodity"
:applies-in-season :ex/summer ; → {:jsonld/id "ex:summer"}
:references-ledger :ex/commodity))
ex-info naming each keyword and the builders that used it.:mistyped. :applies-in-season :ex/commodity, where :ex/commodity is a Ledger, refuses because urpx:appliesInSeason takes a urpx:Season. A subclass passes: a UtilityOperator where an Organization is taken. A builder refuses an inline node of the wrong type the same way, without a scope.@id declared twice in a scope throws, whether by :as or by :jsonld/id.(dsl/dsl-scope {:id-fn f} …) names @ids with f.{:jsonld/id "…"}.make-builder derives a constructor from any Malli schema with a :jsonld/type [:= "urpx:X"] entry — useful for ad-hoc construction or scripts that don't import every named builder:
(let [build-tou-period (dsl/make-builder schema/TOUPeriod)]
(build-tou-period :urpx/name "Summer Peak"
:urpx/touPeriodNumber 1
:urpx/hasTimeBracket []))
The named builders (season, metric-input, price-definition, rate-plan, urpx-package, coverage-claim, …) are each just (def name (make-builder schema/X)), one for every entity-type schema in urpx.schema with no exceptions — a test pins that. Where a schema dual-accepts two @type spellings (source-publication), the builder sets the v0.5.1 one and the schema still validates the v0.2.x one on parse.
For a longer worked walkthrough that mirrors selected examples from the upstream URPX Tutorial, see doc/dsl-tutorial.md — each example shows the canonical URPX JSON-LD shape and the equivalent DSL form, with deep-links back to the upstream source section.
urpx.defs/defurpx defines a var whose value is validated against a schema chosen by a kind keyword. A corpus written as Clojure source uses it for the tables a filing states, and its generator finds every definition of a kind with defs-of-kind.
(require '[urpx.defs :as defs :refer [defurpx]])
(defmethod defs/kind-schema :ex/rates [_]
[:map-of :keyword decimal?])
(defurpx :ex/rates rtc-rates
"Cents per kWh."
{:peak 16.10M :off-peak 8.05M})
(defs/defs-of-kind :ex/rates ['my.corpus]) ; => [#'my.corpus/rtc-rates]
Validation at macroexpansion. The value is checked before the def, so an invalid value or an unknown kind interns nothing and a failed re-evaluation keeps the previous value. The message names the kind, the var, its file and line, and each failing path:
Invalid :ex/rates my.corpus/rtc-rates (my/corpus.clj:7)
at [:peak]: 16.9 should be a decimal
The ex-data carries :kind, :where and the humanized :errors.
Closed schemas. Every map in a kind's schema is closed unless it says {:closed false}, so a misspelled key refuses. A map reached through a :ref keeps its own :closed property.
Kinds. kind-schema is a multimethod; its :default refuses. The kind is a keyword literal. The urpx.kind namespace is reserved for kinds clj-urpx defines; qualify your own kinds in your namespace.
No registry. The kind is in the var's metadata as :urpx/kind, with the docstring, :file and :line. defs-of-kind reads the vars interned in the namespaces you pass, so an unmapped var is not returned.
The jar carries a clj-kondo config whose hook lints defurpx as a def. Import it into a project that depends on clj-urpx:
clj-kondo --lint "$(clojure -Spath)" --copy-configs --skip-lint
Can 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 |