Class: Cocina::Models::Validators::JsonSchemaValidator

Inherits:
Object
  • Object
show all
Defined in:
lib/cocina/models/validators/json_schema_validator.rb

Overview

Validates Cocina model instances against the JSON schema.

The schema uses OpenAPI 3.1.0 conventions with unevaluatedProperties: false to enforce strict validation. However, allOf/$ref composition causes two categories of cascade noise in the structured evaluation output that must be filtered before reporting:

1. falseSchema entries — every `unevaluatedProperties: false` sub-schema emits a
 "False schema does not allow …" entry for each value at that path. These are always
 redundant with the parent unevaluatedProperties entry.

2. Root-level unevaluatedProperties listing only known model attributes as unexpected —
 the validator can't prove top-level fields were "evaluated" through allOf/$ref, so it
 reports every legitimate root property as unexpected at root "".

For example, validating a DRO with an unexpected releaseTags in administrative produces one actionable error plus ~12 cascade entries. The de-noiser keeps the actionable error and discards the rest.

Constant Summary collapse

SCHEMA_PATH =
::File.expand_path('../../../../schema.json', __dir__)

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(clazz, attributes) ⇒ JsonSchemaValidator

Returns a new instance of JsonSchemaValidator.

Parameters:

  • clazz (Class)

    the Cocina model class being validated (e.g., Cocina::Models::DRO)

  • attributes (Hash)

    the attributes of the model instance being validated



52
53
54
55
# File 'lib/cocina/models/validators/json_schema_validator.rb', line 52

def initialize(clazz, attributes)
  @clazz = clazz
  @attributes = attributes
end

Class Method Details

.documentHash

Returns a hash representation of the schema.json document.

Returns:

  • (Hash)

    a hash representation of the schema.json document



43
44
45
46
47
48
# File 'lib/cocina/models/validators/json_schema_validator.rb', line 43

def self.document
  @document ||= begin
    file_content = ::File.read(SCHEMA_PATH)
    JSON.parse(file_content)
  end
end

.validateObject

See Also:



27
28
29
30
31
32
33
34
# File 'lib/cocina/models/validators/json_schema_validator.rb', line 27

def self.validate(...)
  # Skip when nested inside another Validatable's construction: the enclosing object's own
  # schema check already covers this, so re-evaluating it here would be
  # redundant. See Validatable#new.
  return if Thread.current[:cocina_construction_depth].to_i.positive?

  new(...).validate
end

.validator_for(def_name) ⇒ JSONSchema validator

Returns a cached per-definition validator.

Returns:

  • (JSONSchema validator)

    a cached per-definition validator



37
38
39
40
# File 'lib/cocina/models/validators/json_schema_validator.rb', line 37

def self.validator_for(def_name)
  @validators ||= {}
  @validators[def_name] ||= JSONSchema.validator_for({ '$ref' => "#/$defs/#{def_name}", '$defs' => document['$defs'] })
end

Instance Method Details

#validateNilClass

Validates attributes against the Cocina model schema.

Injects the cocinaVersion if the model includes it as an attribute, then validates the attributes against the schema definition for this model. De-noises unevaluatedProperties cascade errors before raising a ValidationError.

Uses the structured evaluation API to capture sub-errors inside anyOf branches, producing actionable messages (e.g. path-qualified minItems/pattern failures) rather than the generic "not valid under anyOf" message.

Returns:

  • (NilClass)

    returns nil if validation passes

Raises:

  • (ValidationError)

    if validation fails, with a de-noised error message



69
70
71
72
73
74
75
76
# File 'lib/cocina/models/validators/json_schema_validator.rb', line 69

def validate
  attributes['cocinaVersion'] = Cocina::Models::VERSION if clazz.attribute_names.include?(:cocinaVersion)

  evaluation = self.class.validator_for(method_name).evaluate(attributes.as_json)
  return if evaluation.valid?

  raise ValidationError, "When validating #{method_name}: " + filtered_error_messages(evaluation).join('; ')
end