Class: Ruby::Rego::Builtins::Codecs::JsonSchema::Matcher

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby/rego/builtins/codecs/json_schema_match.rb

Overview

A pure-boolean document validator. Holds the root schema for $ref resolution and a per-ref visited set keyed by [ref, document object id] so a self-referential schema validating a non-shrinking document terminates instead of looping. rubocop:disable Metrics/ClassLength

Constant Summary collapse

MAX_DEPTH =

Validation recurses through matches? (document drill-down, $ref-following, allOf/anyOf nesting) and canonical (enum/const/uniqueItems equality). Both are bounded here so a pathologically deep document or schema returns undefined instead of a SystemStackError that would abort the whole policy (only BuiltinArgumentError is rescued by the registry). The bound matches JSON.parse’s default max_nesting (100) already enforced on the string-document path, keeping the raw-input and string-input paths consistent. gojsonschema validates deeper (to roughly 6000) before it too stack-overflows; documents nested past this bound are a documented divergence (undefined vs OPA’s true/false) rather than a crash in either tool.

100
TOO_DEEP =

Sentinel thrown past the recursion and caught at the match entry point.

:__match_schema_too_deep__

Instance Method Summary collapse

Constructor Details

#initialize(root) ⇒ Matcher

Returns a new instance of Matcher.



92
93
94
95
96
97
# File 'lib/ruby/rego/builtins/codecs/json_schema_match.rb', line 92

def initialize(root)
  @root = root
  @visited = {}
  @depth = 0
  @re2_cache = {}
end

Instance Method Details

#matches?(document, schema) ⇒ Boolean

rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize Whether document validates against schema (a subschema of the root). Applies every keyword gojsonschema knows; the document matches only when all applicable keywords are satisfied. :reek:TooManyStatements

Returns:

  • (Boolean)


126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/ruby/rego/builtins/codecs/json_schema_match.rb', line 126

def matches?(document, schema)
  return schema unless schema.is_a?(Hash) # a boolean schema matches all (true) / nothing (false)

  with_depth do
    ref = schema["$ref"]
    next ref_matches?(document, schema, ref) if ref.is_a?(String)

    type_ok?(document, schema["type"]) &&
      enum_ok?(document, schema) && const_ok?(document, schema) &&
      number_ok?(document, schema) && string_ok?(document, schema) &&
      array_ok?(document, schema) && object_ok?(document, schema) &&
      logic_ok?(document, schema) && format_ok?(document, schema)
  end
end

#re2_matches?(pattern, string) ⇒ Boolean

An unanchored RE2 match of pattern (a pattern keyword or patternProperties key) against string, compiling each distinct pattern at most once per match call. patternProperties tests every schema pattern against every document property name (N x M), so without this cache the gem would recompile N x M times — a CPU amplification on a valid schema+document. A compiled regex (or nil, for an unusable pattern) is memoised by pattern string for the call’s lifetime. :reek:NilCheck

Returns:

  • (Boolean)


105
106
107
108
109
110
# File 'lib/ruby/rego/builtins/codecs/json_schema_match.rb', line 105

def re2_matches?(pattern, string)
  return false unless string.is_a?(String) && string.valid_encoding?

  regex = @re2_cache.fetch(pattern) { @re2_cache[pattern] = JsonSchema.re2_compile(pattern) }
  regex ? regex.match?(string) : false
end

#with_depthObject

Bound one level of recursion; throw TOO_DEEP past the whole walk when the limit is exceeded.



113
114
115
116
117
118
119
120
# File 'lib/ruby/rego/builtins/codecs/json_schema_match.rb', line 113

def with_depth
  @depth += 1
  throw TOO_DEEP, TOO_DEEP if @depth > MAX_DEPTH

  yield
ensure
  @depth -= 1
end