Coverage
What the conversion expresses today, and what it refuses.
A type that is not supported throws
Schema conversion not yet implemented for <name>. The issue tracker lists
which types those are. Some lib.types constructors build the same type and so
convert the same way: port and ints.u16, uniq and unique.
Types that convert
lib.types |
Emitted as |
|---|---|
bool |
type = "boolean" |
enum |
enum with the listed values |
str, lines, commas, envVar, separatedString |
type = "string" |
strMatching <regex> |
type = "string" |
passwdEntry <type> |
type = "string" with pattern = "^[^:\n]*$", the check that rejects colons and newlines |
path, pathWith, pathInStore |
type = "string" |
package, shellPackage |
$ref to the package object in $defs, see Packages |
int |
type = "integer" with the 64-bit minimum/maximum |
float, number |
type = "number" with double-precision bounds |
ints.between, numbers.between |
the numeric type with minimum/maximum parsed out of the type description |
ints.positive |
type = "integer" with exclusiveMinimum = 0 |
port, ints.u16 |
type = "integer", minimum = 0, maximum = 65535 |
attrs |
type = "object", propertyNames restricted to valid Nix attribute names, additionalProperties the permissive value schema |
attrsOf, lazyAttrsOf, attrsWith |
type = "object" with the element schema as additionalProperties |
listOf |
type = "array" with the element schema as items |
nonEmptyListOf |
the listOf schema with minItems = 1, and no default, as the empty list the type rejects |
nullOr |
null merged into the element's type where the element has one, an anyOf otherwise |
either, oneOf |
one merged schema where the alternatives merge (either int str becomes type = [ "integer" "string" ]), an anyOf otherwise; oneOf folds pairwise into either |
submodule, submoduleWith |
type = "object" with properties, inferred required, and additionalProperties = false unless the module has a freeform type |
attrTag |
type = "object" with a oneOf of single-tag branches. The uninhabited attrTag { } becomes { type = "object"; not = { }; }, since oneOf = [ ] is not a legal schema |
deferredModule |
the attrs schema |
unspecified, raw, anything |
the permissive value schema, pulled in by $ref |
pkgs.formats.{json,yaml,toml,ini} types |
the format's recursive value type, resolved through $ref and $defs |
Packages
A user names a package by its attrpath in Nixpkgs, though the real type is a
derivation. package refers to a shared definition in $defs:
{
"$ref": "#/$defs/package",
"$defs": {
"package": {
"type": "object",
"properties": {
"_type": { "const": "package" },
"attrpath": { "type": "array", "items": { "type": "string" }, "minItems": 1 }
},
"required": ["_type", "attrpath"],
"additionalProperties": false
}
}
}
{ "_type": "package", "attrpath": ["python3Packages", "requests"] } refers to
pkgs.python3Packages.requests. Nixpkgs has a pkgs attribute, so
["pkgs", "python3Packages", "requests"] refers to the same package.
An object keeps a package distinct from a string, so either package str
becomes an anyOf of the two and a value shows which one it is. The _type
marker follows the Nix convention for tagged values such as mkIf. It keeps a
package distinct from other objects, such as the value of a submodule with an
attrpath option, so a consumer can find packages in the data without the
schema.
resolvePackages converts such data back to Nix. It replaces each package
value with the package it refers to in a package set:
config = module-schema.resolvePackages pkgs (builtins.fromJSON json);
A default or example that the option documents as Nix code, such as the
pkgs.hello that mkPackageOption renders, stays a string of Nix code, as in
lib.optionAttrSetToDocList.
functionTo
functionTo throws. Nix's toJSON and fromJSON cannot usefully round-trip a
function, and the library defines no JSON representation for one yet. The
message says so, and suggests marking the option internal or
visible = false so it is excluded from the interface.
Module system features
description,default,example(rendered asexamples, with aliteralExpressionunwrapped to its text, and de-duplicated against the type's empty value)type.emptyValue, used as adefaultwhen the option sets nonedefaultText, which overridesdefaultwith its rendered.text, matching whatlib.optionAttrSetToDocListdoes for the manualreadOnly, passed through as it is. Nix means "can be set only once" by it, which is close to, but not the same as, the JSON Schema meaninginternal = trueandvisible = false, droppedvisible = "shallow"andvisible = "transparent", both of which keep the option."shallow"replaces the sub-structure with the permissive value schema rather than dropping it, so the schema still accepts everything the option accepts- freeform modules:
_module.freeformTypebecomesadditionalProperties, propagated one nesting level down requiredinference for submodules and for nested attrsets of options, deliberately over-approximated so that anything validating against the schema is deployable_moduleand_freeformOptionsstripping, so a schema built from a rawgetSubOptionsdoes not leak them as properties- type-level recursion, resolved with
$refand$defs
Note that the internal/visible filtering happens while walking an attrset of
options. A bare optionToSchema applied to an internal option still produces a
schema.
JSON Schema dialect
The output is draft 2020-12.
Where this is checked
lib/tests/module-schema.nix-- the type matrix and the visibility rules.lib/tests/internal.nix-- the schema algebra:allowsType,subsumes,union. One case there asserts the deliberately wrong answerallowsTypegives for a schema whose types are constrained by ananyOf, so the wrong answer is pinned rather than accidental.lib/tests/interval.nix-- the interval arithmetic behind the numeric bounds.lib/tests/services/-- golden schemas for the Mastodon, PeerTube and Pixelfed NixOS modules.