Early developmentNot everything described here has shipped yet.

Skip to content

Validation Codes ​

Constraint violations returned by NeoWiki's validation API use stable code strings. This document is the reference for those codes.

Violations are returned by:

  • POST /neowiki/v0/subject/validate — dry-run validation of a proposed create-shape body.
  • POST /neowiki/v0/subject/{subjectId}/validate — dry-run validation of a proposed update-shape body against an existing Subject's Schema.
  • POST /neowiki/v0/subject and PUT /neowiki/v0/subject/{subjectId} — the write endpoints include the resulting violations array in their 201/200 success body. Whether a write with violations is rejected is covered under Severity, blocking, and enforcement.
  • Every endpoint that writes a page's Subjects, in the 422 body of a write that breaks the rules for Dependent Subjects.

The /validate endpoints return 200 OK with a {violations: [...]} body whenever the request is well-formed; violations in the body do not change the HTTP status. 400 is reserved for malformed input, 404 for a missing Subject (update dry-run). A missing Schema is reported differently per endpoint — see schema-not-found. POST /subject returns 409 when the page already has the requested Subject (the create did not run, so the body carries no violations).

Each violation in the response has this shape:

json
{
  "propertyName": "Website",
  "code": "invalid-url",
  "args": [],
  "severity": "error",
  "valuePartIndex": 0
}
  • propertyName is the property name as a string, or null for Subject-level violations.
  • code is one of the stable strings documented below.
  • args is always present; [] when there is nothing to interpolate.
  • severity is always present and is either error or warning. See Severity, blocking, and enforcement.
  • valuePartIndex is the zero-based index of the offending part of a multi-part value. Only the codes that document it set it; the key is omitted from the JSON otherwise.
  • subjectId is the ID of the Subject the violation is on. The codes for Dependent Subjects always set it, as do the violations of a dependentSubjects item. Without it, the violation is on the Subject the request creates, replaces or validates.

Severity, blocking, and enforcement ​

Severity decides whether a write can be rejected: warnings never block, errors can. Each code in the reference below documents its own severity.

Where a violation is backed by a Constraint the schema author writes — required, multiple, minimum, maximum, minLength, maxLength, uniqueItems, options — that author sets its severity in the Schema, per Constraint, and the default is warning. See Constraint severity for the JSON. Because the default is warning, an unannotated Schema blocks nothing at all: invalid Subjects are a normal, supported state, and blocking is what an author opts into. Every other code reports a system condition rather than a user-correctable Constraint, so its severity is fixed.

Blocking matters only when an admin enables enforcement ($wgNeoWikiEnforceValidation, or enforceValidation on MediaWiki:NeoWiki; off by default, so every write that keeps the rules for Dependent Subjects persists). Under enforcement, a write that introduces new error violations is rejected with 422 Unprocessable Entity and an { status, message, violations } body, where violations carries the full proposed list, not just the newly-introduced ones. Violations already present on the stored Subject never block, so an already-invalid Subject stays editable, and raising a Constraint's severity does not make an existing violation count as newly introduced. See ADR 21 and ADR 26.

Code reference ​

required ​

A property declared required: true has no usable value. Fires when the Subject body has no Statement for the property, and when a Statement is present but its value is empty for its type: only whitespace (text, date, dateTime), no parts (url, select, monolingualText), no targets (relation), or no value at all (number, boolean).

args: []. severity: set by the required Constraint (default warning).

type-mismatch ​

The type recorded on the Statement when it was written (ADR 11) no longer matches the type the Schema currently declares for the property — for example, a Statement written while the property was a url, after the Schema changed the property to number. When this fires, it is the only violation reported for that property: per-type checks and required are suppressed.

args: [writerType, currentType]. severity: error (fixed).

invalid-url ​

On url properties. A non-empty value does not match the allowed URL pattern. The pattern accepts http:// and https:// schemes (or no scheme), domain-like hosts, IPv4 literals, localhost, port, path, query, and fragment. It rejects other schemes (ftp://, file://), spaces, and disallowed characters.

args: []. valuePartIndex: the offending part. severity: error (fixed).

unique ​

On text, url and monolingualText properties with uniqueItems enabled: the value contains duplicate parts. On monolingualText a part duplicates another only when both its text and its language repeat.

args: []. severity: set by the uniqueItems Constraint (default warning).

min-length / max-length ​

On text and monolingualText properties. A part's trimmed length — its text's, for monolingualText — is below minLength or above maxLength. Parts with no text are not length-checked.

args: [minLength] / [maxLength]. valuePartIndex: the offending part. severity: set by the minLength / maxLength Constraint (default warning).

min-value / max-value ​

On number, date, and dateTime properties. The value is below the property's inclusive minimum or above its inclusive maximum.

args: [minimum] / [maximum] — a number for number properties, the declared ISO 8601 string for date and dateTime. severity: set by the minimum / maximum Constraint (default warning).

invalid-option ​

On select properties. A part is not in the property's options allow-list.

args: [offendingPart]. valuePartIndex: the offending part. severity: set by the options Constraint (default warning).

single-value-only ​

On single-valued (multiple: false) select, relation and monolingualText properties: more than one part (select, monolingualText) or relation target (relation) was supplied.

args: []. severity: set by the multiple Constraint (default warning).

invalid-datetime ​

On dateTime properties. The value is not a strict ISO 8601 / xsd:dateTime string with an explicit timezone offset or Z. Includes calendar-overflow cases like 2025-02-30T00:00:00Z, partial dates (2025, 2025-06, 2025-06-15), and missing offsets.

args: []. severity: error (fixed).

invalid-date ​

On date properties. The value is not a strict ISO 8601 calendar date (YYYY-MM-DD). Time or timezone components and calendar overflows like 2025-02-30 are rejected.

args: []. severity: error (fixed).

unregistered-type ​

The property's type has no registered PropertyType — typically the extension providing the type is disabled. The value cannot be interpreted, so it is preserved verbatim and no other checks run for the property. A required property of an unregistered type reports this code instead of required, so the Subject stays saveable.

args: [propertyType]. severity: warning (fixed).

schema-not-found ​

The Subject's Schema cannot be loaded — usually deleted or renamed since the Subject was created, or the Subject was created or imported referencing a Schema that does not (yet) exist. A reference to a Source this wiki does not have reports the same. The write proceeds and reports the violation; creating or renaming the Schema page, or registering the missing Source, resolves it. Subject-level: propertyName is null.

Returned by the update dry-run and both write endpoints. The create dry-run (POST /subject/validate) instead returns 404, because there the Schema is the addressed resource.

args: [schemaName]. severity: warning (fixed).

relation-target-schema-mismatch ​

On relation properties. The relation targets a Subject that exists but whose own Schema is none of the property's targetSchemas. A target that cannot be resolved is reported as relation-target-not-found instead.

args: [actualSchema]. valuePartIndex: the offending target. severity: error (fixed).

relation-target-not-found ​

On relation properties. The relation targets a Subject ID that resolves to no Subject you may read: either none exists, or the one that does is on a page you cannot read. Deliberately a warning: pointing at a not-yet-created Subject is wiki-native red-link behavior, and an import may legitimately mint the target later.

args: [targetId]. valuePartIndex: the offending target. severity: warning (fixed).

relation-target-unresolvable-source ​

On relation properties. The relation targets a Subject ID whose source key names a Source this wiki has not registered, so the target cannot be reached at all. Reported instead of relation-target-not-found, which is about a Source that could answer and did not.

Only a violation the edit introduces blocks it, so a Subject that already carries such a target stays editable.

It is also not Schema-scoped: it is reported for every relation value on the Subject, including a Statement the Schema does not declare and a Subject whose Schema cannot be loaded at all. Such a target would be unreadable from the moment it is written, which no later Schema change fixes.

args: [targetId]. valuePartIndex: the offending target. severity: error (fixed).

Rules for Dependent Subjects ​

A write through any endpoint that breaks a rule for Dependent Subjects is refused with 422, whatever the enforcement setting, and its body lists the codes below with the request's other violations. Only a violation the write introduces refuses it, so a page that already breaks a rule stays editable. The /validate endpoints do not check these rules.

dependent-subject-referenced-twice ​

A Dependent Subject is the target of more than one relation on its page.

args: []. subjectId: the Dependent Subject. severity: error (fixed).

dependent-target-not-on-page ​

On relation properties whose target Schemas declare their Subjects dependent. The relation's target is not on the page of the Subject holding it, or does not exist. Write a new target in the same request, in dependentSubjects.

args: [targetId]. valuePartIndex: the offending target. subjectId: the Subject holding the relation. severity: error (fixed).

relation-to-dependent-subject ​

On relation properties. The relation points at a Dependent Subject from a property whose target Schemas do not declare their Subjects dependent, or from a property the Schema does not declare.

args: [targetId]. valuePartIndex: the offending target. subjectId: the Subject holding the relation. severity: error (fixed).

dependent-subject-is-main ​

A Dependent Subject is its page's main Subject.

args: []. subjectId: the Dependent Subject. severity: error (fixed).

dependent-subject-has-label ​

A Dependent Subject has a label.

args: []. subjectId: the Dependent Subject. severity: error (fixed).

dependent-subject-without-host ​

The write creates a Dependent Subject, or supplies one in dependentSubjects, that no standalone Subject on its page points at, directly or through other Dependent Subjects; or it moves a Dependent Subject on its own. Write a Dependent Subject in the same request as the relation to it; to move one, move its Host Subject.

args: []. subjectId: the Dependent Subject. severity: error (fixed).

Out-of-schema Statements ​

A Statement whose property is not declared on the current Schema is ignored — no violation. This is schema-drift tolerance: a property may have been removed from the Schema while Subjects still carry old Statements. The exceptions are relation-target-unresolvable-source and relation-to-dependent-subject, reported even for Statements the Schema does not declare.

Adding a new validation code ​

If you're a plugin author (e.g. registering a custom PropertyType) and want to surface a new violation code:

  1. Return a Violation from your PropertyType::validate() method. Leave propertyName as null — SubjectValidator attaches it.
  2. Use a short kebab-case code that describes the violated rule, not the implementation (invalid-postcode, not regex-failed-1).
  3. Set args to the values you'd want interpolated into a user-facing message — the same convention as the codes above.
  4. If the violation points at a specific part of a multi-part Value, set valuePartIndex to that part's zero-based index.
  5. Set the severity. A Violation defaults to warning, which never blocks a write. If your code is backed by a Constraint the schema author writes, take the configured value with $definition->severityOf( 'yourConstraintKey' ) so authors can make it blocking; if it reports a fixed system condition, pass Severity::Error or Severity::Warning explicitly. Severity applies to Constraints only — a severity written on one of your Display Attributes is discarded.
  6. Document your new code in your extension's documentation.

RedHerb's ColorType (tests/RedHerb/src/ColorType.php) is a worked example, including its own invalid-color code.