yeptris 0.6.58.1-aarch64-linux → 0.6.60.1-aarch64-linux
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/yeptris_native/version.h +2 -2
- data/lib/yeptris/3.3/native.so +0 -0
- data/lib/yeptris/3.4/native.so +0 -0
- data/lib/yeptris/4.0/native.so +0 -0
- data/lib/yeptris/document.rb +15 -0
- data/lib/yeptris/ffi.rb +112 -0
- data/lib/yeptris/psych/visitors.rb +16 -6
- data/lib/yeptris/psych.rb +11 -5
- data/lib/yeptris/validation.rb +320 -0
- data/lib/yeptris/yaml.rb +128 -6
- data/lib/yeptris.rb +2 -1
- data/libyeptris.so +0 -0
- data/vendor/libyeptris/CMakeLists.txt +1 -1
- data/vendor/libyeptris/src/CMakeLists.txt +8 -0
- data/vendor/libyeptris/src/include/yeptris/emit.h +30 -2
- data/vendor/libyeptris/src/include/yeptris/parse.h +52 -0
- data/vendor/libyeptris/src/include/yeptris/resolve.h +8 -0
- data/vendor/libyeptris/src/include/yeptris/validate.h +259 -15
- data/vendor/libyeptris/src/include/yeptris/values.h +8 -2
- data/vendor/libyeptris/src/yeptris/cbor/cbor.h +10 -0
- data/vendor/libyeptris/src/yeptris/cbor/decode.c +5 -7
- data/vendor/libyeptris/src/yeptris/cbor/encode.c +9 -9
- data/vendor/libyeptris/src/yeptris/cbor/from_marshal.c +1 -1
- data/vendor/libyeptris/src/yeptris/common/error.h +2 -0
- data/vendor/libyeptris/src/yeptris/doc.h +20 -0
- data/vendor/libyeptris/src/yeptris/emit/emit.c +85 -13
- data/vendor/libyeptris/src/yeptris/emit/writer.c +158 -642
- data/vendor/libyeptris/src/yeptris/emit/writer.h +15 -1
- data/vendor/libyeptris/src/yeptris/events/push.c +28 -3
- data/vendor/libyeptris/src/yeptris/events/values.c +24 -0
- data/vendor/libyeptris/src/yeptris/marshal/to_json.c +650 -0
- data/vendor/libyeptris/src/yeptris/parse/engine.c +121 -40
- data/vendor/libyeptris/src/yeptris/parse/stream_iter.c +326 -0
- data/vendor/libyeptris/src/yeptris/parse.c +181 -46
- data/vendor/libyeptris/src/yeptris/resolve/compat11.c +1 -1
- data/vendor/libyeptris/src/yeptris/resolve/core12.c +11 -3
- data/vendor/libyeptris/src/yeptris/resolve/full12.c +201 -0
- data/vendor/libyeptris/src/yeptris/resolve/json12.c +12 -6
- data/vendor/libyeptris/src/yeptris/resolve/resolver.h +20 -0
- data/vendor/libyeptris/src/yeptris/validate/compile.c +306 -0
- data/vendor/libyeptris/src/yeptris/validate/internal.h +255 -10
- data/vendor/libyeptris/src/yeptris/validate/jsonschema.c +1025 -125
- data/vendor/libyeptris/src/yeptris/validate/output.c +246 -0
- data/vendor/libyeptris/src/yeptris/validate/regexp.c +791 -163
- data/vendor/libyeptris/src/yeptris/validate/registry.c +118 -0
- data/vendor/libyeptris/src/yeptris/validate/resources.c +601 -0
- data/vendor/libyeptris/src/yeptris/validate/stream.c +1155 -0
- data/vendor/libyeptris/src/yeptris/validate/validate.c +330 -226
- metadata +11 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ee77fd51891c7d933f22d71cfebbdfb83a038422ef407625efc71ccbbaf4480a
|
|
4
|
+
data.tar.gz: e420af3ccf3b7cfa54da29d310c09c004e4d11dcf45f8814e2d286fb9b728318
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ba893a5b1d87c8f40fb9a6331c3fe887fe41023a7f14ea2131638e3d9eaa6fffdb4e32c17631b68b5e205566b2f9942fd78d22701dafc464ef4fde5f9aa8b64d
|
|
7
|
+
data.tar.gz: d45bc687e00f72a52d7f27169d6ffb36416601b364c2f76a6de0e736910667b5ab55809fc16e75ffce38c26eada4bedea07dd52c334abd33c4bf67ade792eea9
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
#define YEPTRIS_VERSION_MAJOR 0
|
|
13
13
|
#define YEPTRIS_VERSION_MINOR 6
|
|
14
|
-
#define YEPTRIS_VERSION_PATCH
|
|
15
|
-
#define YEPTRIS_VERSION_STRING "0.6.
|
|
14
|
+
#define YEPTRIS_VERSION_PATCH 60
|
|
15
|
+
#define YEPTRIS_VERSION_STRING "0.6.60"
|
|
16
16
|
|
|
17
17
|
#ifdef __cplusplus
|
|
18
18
|
extern "C" {
|
data/lib/yeptris/3.3/native.so
CHANGED
|
Binary file
|
data/lib/yeptris/3.4/native.so
CHANGED
|
Binary file
|
data/lib/yeptris/4.0/native.so
CHANGED
|
Binary file
|
data/lib/yeptris/document.rb
CHANGED
|
@@ -238,6 +238,21 @@ class Yeptris::Document
|
|
|
238
238
|
r.nil? ? nil : r.to_ruby
|
|
239
239
|
end
|
|
240
240
|
|
|
241
|
+
# The schema-family validation walk (validate.h, #882) over this
|
|
242
|
+
# document — or over every document of the stream it holds (the
|
|
243
|
+
# engine's report is flat across the stream; paths are root-relative
|
|
244
|
+
# per document). schema: :core_12 or :json_12 (the validation
|
|
245
|
+
# families; :compat_11 is a resolve schema, not a validation
|
|
246
|
+
# family). Returns plain Yeptris::Validation::Diagnostic values
|
|
247
|
+
# (empty = valid).
|
|
248
|
+
def validate(schema: :core_12)
|
|
249
|
+
ensure_alive!
|
|
250
|
+
report = ::FFI::MemoryPointer.new(:pointer)
|
|
251
|
+
v = ::Yeptris::Validation
|
|
252
|
+
st = Yeptris::FFI.yeptris_validate_document(@c_ptr, v.schema_id(schema), report)
|
|
253
|
+
v.consume_report(st, report, "validate document")
|
|
254
|
+
end
|
|
255
|
+
|
|
241
256
|
# ---- construction conveniences (TODO.impl/11 phase 3) ----
|
|
242
257
|
|
|
243
258
|
def new_mapping
|
data/lib/yeptris/ffi.rb
CHANGED
|
@@ -56,6 +56,7 @@ module Yeptris
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
typedef :pointer, :yeptris_document
|
|
59
|
+
typedef :pointer, :yeptris_stream_iter
|
|
59
60
|
typedef :pointer, :yeptris_node
|
|
60
61
|
typedef :pointer, :yeptris_status_out
|
|
61
62
|
typedef :int, :yeptris_status
|
|
@@ -87,6 +88,24 @@ module Yeptris
|
|
|
87
88
|
%i[pointer size_t pointer yeptris_status_out], :yeptris_document
|
|
88
89
|
attach_function :yeptris_parse_json, %i[pointer size_t yeptris_status_out], :yeptris_document
|
|
89
90
|
|
|
91
|
+
# The tolerant per-document stream iterator (engine #921): frames
|
|
92
|
+
# documents scanner-accurately, reports failures per document (the
|
|
93
|
+
# failed run's line/col ARE that document's own), resyncs to the
|
|
94
|
+
# next line-start marker. On an engine older than the iterator the
|
|
95
|
+
# attach stubs above raise the stale-engine message at USE
|
|
96
|
+
# (each_document's caller sees it directly — no separate gate
|
|
97
|
+
# constant, the @missing law covers it).
|
|
98
|
+
attach_function :yeptris_stream_iter_new, %i[pointer size_t pointer], :yeptris_stream_iter
|
|
99
|
+
attach_function :yeptris_stream_iter_next,
|
|
100
|
+
%i[yeptris_stream_iter pointer pointer], :yeptris_status
|
|
101
|
+
attach_function :yeptris_stream_iter_status,
|
|
102
|
+
%i[yeptris_stream_iter pointer pointer], :yeptris_status
|
|
103
|
+
attach_function :yeptris_stream_iter_free, [:yeptris_stream_iter], :void
|
|
104
|
+
# the capability probe (evaluated AFTER the attaches above — the
|
|
105
|
+
# CBOR.available? pattern): specs and hosts gate on it; at USE a
|
|
106
|
+
# stale engine still raises the stub's message
|
|
107
|
+
STREAM_ITER = !(@missing ||= []).include?(:yeptris_stream_iter_new)
|
|
108
|
+
|
|
90
109
|
# The JSON tape (TODO.restructure/85; the JSON surface's load
|
|
91
110
|
# engine — issue #81): ONE call, three bulk columns, spans borrow
|
|
92
111
|
# the caller's string (no arena copy, no second validating parse).
|
|
@@ -427,6 +446,9 @@ module Yeptris
|
|
|
427
446
|
|
|
428
447
|
SCHEMA_12_CORE = 0
|
|
429
448
|
SCHEMA_11_COMPAT = 1
|
|
449
|
+
# resolve.h's third kind (spec 10.2): plain scalars must match a
|
|
450
|
+
# JSON production; the validate face's schema-family walk rides it.
|
|
451
|
+
SCHEMA_12_JSON = 2
|
|
430
452
|
|
|
431
453
|
OK = 0
|
|
432
454
|
ERROR_PARSE = 1
|
|
@@ -437,6 +459,96 @@ module Yeptris
|
|
|
437
459
|
ERROR_ARG = 6
|
|
438
460
|
ERROR_UNSUPPORTED = 7
|
|
439
461
|
ERROR_INTERNAL = 8
|
|
462
|
+
|
|
463
|
+
# ---- the validation face (validate.h; #882, engine 0.6.59+) ----
|
|
464
|
+
# Whole reports: one call produces a report the caller drains
|
|
465
|
+
# (count/at) and frees once. SCHEMA_ERROR (9, defined above) means
|
|
466
|
+
# the walk completed and the instance is INVALID — drain the
|
|
467
|
+
# report. Absent on older libraries: the attachment helper records
|
|
468
|
+
# the miss and VALIDATION gates the Ruby face with the friendly
|
|
469
|
+
# error at use.
|
|
470
|
+
|
|
471
|
+
typedef :pointer, :yeptris_schema
|
|
472
|
+
typedef :pointer, :yeptris_validate_report
|
|
473
|
+
|
|
474
|
+
# yeptris_diagnostic (validate.h): ABI-frozen — four uint32 facts
|
|
475
|
+
# then two bounded NUL-terminated texts. 16 + 256 + 128 bytes.
|
|
476
|
+
class CDiagnostic < ::FFI::Struct
|
|
477
|
+
layout :code, :uint32,
|
|
478
|
+
:line, :uint32,
|
|
479
|
+
:col, :uint32,
|
|
480
|
+
:offset, :uint32,
|
|
481
|
+
:path, [:char, 256],
|
|
482
|
+
:detail, [:char, 128]
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# The schema-family walk: parse + validate fused (tag consistency,
|
|
486
|
+
# canonical-form key uniqueness; the JSON family's string-key +
|
|
487
|
+
# JSON-production rules under SCHEMA_12_JSON).
|
|
488
|
+
attach_function :yeptris_validate, %i[pointer size_t int pointer], :int
|
|
489
|
+
# An already-parsed document (or stream) against the family walk.
|
|
490
|
+
attach_function :yeptris_validate_document, %i[yeptris_document int pointer], :int
|
|
491
|
+
# JSON Schema draft 2020-12 subset, one-shot (schema bytes parsed
|
|
492
|
+
# per call).
|
|
493
|
+
attach_function :yeptris_json_schema_validate,
|
|
494
|
+
%i[pointer size_t pointer size_t pointer], :int
|
|
495
|
+
# The compiled-schema economy (#885 item 3): parse once, validate
|
|
496
|
+
# many. NULL from compile = failure (yeptris_last_error carries why).
|
|
497
|
+
attach_function :yeptris_schema_compile, %i[pointer size_t], :yeptris_schema
|
|
498
|
+
attach_function :yeptris_schema_validate,
|
|
499
|
+
%i[yeptris_schema pointer size_t pointer], :int
|
|
500
|
+
attach_function :yeptris_schema_free, [:yeptris_schema], :void
|
|
501
|
+
attach_function :yeptris_validate_report_count,
|
|
502
|
+
[:yeptris_validate_report], :size_t
|
|
503
|
+
attach_function :yeptris_validate_report_at,
|
|
504
|
+
%i[yeptris_validate_report size_t], :pointer
|
|
505
|
+
attach_function :yeptris_validate_report_free,
|
|
506
|
+
[:yeptris_validate_report], :void
|
|
507
|
+
|
|
508
|
+
# The diag codes (validate.h's YeptrisDiagCode, appending-only;
|
|
509
|
+
# pinned values — the engine's test_abi pins the head).
|
|
510
|
+
DIAG_NONE = 0
|
|
511
|
+
DIAG_TAG_MISMATCH = 1
|
|
512
|
+
DIAG_KEY_NOT_STRING = 2
|
|
513
|
+
DIAG_NON_JSON_TYPE = 3
|
|
514
|
+
DIAG_DUPLICATE_KEY = 4
|
|
515
|
+
DIAG_TYPE = 5
|
|
516
|
+
DIAG_REQUIRED = 6
|
|
517
|
+
DIAG_ENUM = 7
|
|
518
|
+
DIAG_CONST = 8
|
|
519
|
+
DIAG_PATTERN = 9
|
|
520
|
+
DIAG_MINIMUM = 10
|
|
521
|
+
DIAG_MAXIMUM = 11
|
|
522
|
+
DIAG_EXCLUSIVE_MINIMUM = 12
|
|
523
|
+
DIAG_EXCLUSIVE_MAXIMUM = 13
|
|
524
|
+
DIAG_MIN_LENGTH = 14
|
|
525
|
+
DIAG_MAX_LENGTH = 15
|
|
526
|
+
DIAG_MIN_ITEMS = 16
|
|
527
|
+
DIAG_MAX_ITEMS = 17
|
|
528
|
+
DIAG_UNIQUE_ITEMS = 18
|
|
529
|
+
DIAG_ADDITIONAL_PROPERTY = 19
|
|
530
|
+
DIAG_ANY_OF = 20
|
|
531
|
+
DIAG_ONE_OF = 21
|
|
532
|
+
DIAG_NOT = 22
|
|
533
|
+
DIAG_REF = 23
|
|
534
|
+
DIAG_FALSE_SCHEMA = 24
|
|
535
|
+
DIAG_DEPTH = 25
|
|
536
|
+
DIAG_LIMIT = 26
|
|
537
|
+
DIAG_MULTIPLE_OF = 27
|
|
538
|
+
DIAG_MIN_PROPERTIES = 28
|
|
539
|
+
DIAG_MAX_PROPERTIES = 29
|
|
540
|
+
DIAG_CONTAINS = 30
|
|
541
|
+
DIAG_MIN_CONTAINS = 31
|
|
542
|
+
DIAG_MAX_CONTAINS = 32
|
|
543
|
+
DIAG_DEPENDENT_REQUIRED = 33
|
|
544
|
+
|
|
545
|
+
# The face's availability (engine 0.6.59+); the Validation module
|
|
546
|
+
# raises the friendly error at use when the loaded library predates
|
|
547
|
+
# the attachments (the stubs above would raise NotFoundError raw).
|
|
548
|
+
VALIDATION = !(@missing ||= []).include?(:yeptris_validate) &&
|
|
549
|
+
!(@missing ||= []).include?(:yeptris_schema_compile) &&
|
|
550
|
+
!(@missing ||= []).include?(:yeptris_validate_report_at)
|
|
551
|
+
|
|
440
552
|
# The load-time essentials (the read path + the ownership
|
|
441
553
|
# contract): absent on engines far older than the optional
|
|
442
554
|
# surfaces — raise the friendly error once every constant has
|
|
@@ -13,12 +13,17 @@ module Yeptris
|
|
|
13
13
|
# !ruby/... tags Psych's own emitter produces.
|
|
14
14
|
class YAMLTree
|
|
15
15
|
# stdlib's factory (relaton's db cache calls it): the options
|
|
16
|
-
#
|
|
16
|
+
# reach the engine's emitter — :line_width and :canonical ride
|
|
17
|
+
# yeptris_emit_options (13B), :indentation is accepted for
|
|
18
|
+
# Psych parity (the engine's YAML emitter carries no
|
|
19
|
+
# indentation knob; emit.h's struct is size/canonical/
|
|
20
|
+
# best_width/explicit_doc_start).
|
|
17
21
|
def self.create(options = {}, emitter = nil)
|
|
18
|
-
new
|
|
22
|
+
new(options: options)
|
|
19
23
|
end
|
|
20
24
|
|
|
21
|
-
def initialize(schema: nil)
|
|
25
|
+
def initialize(schema: nil, options: {})
|
|
26
|
+
@options = options || {}
|
|
22
27
|
@tree = ::Yeptris::Document.create(schema: schema)
|
|
23
28
|
@names = {} # object_id -> anchor name
|
|
24
29
|
@counter = 0
|
|
@@ -48,7 +53,9 @@ module Yeptris
|
|
|
48
53
|
end
|
|
49
54
|
|
|
50
55
|
def finish
|
|
51
|
-
@tree.serialize(
|
|
56
|
+
@tree.serialize(canonical: @options[:canonical] ? true : false,
|
|
57
|
+
best_width: @options[:line_width] || 0,
|
|
58
|
+
explicit_doc_start: true)
|
|
52
59
|
end
|
|
53
60
|
|
|
54
61
|
# The built-document handle stdlib's dump tail serializes
|
|
@@ -57,8 +64,11 @@ module Yeptris
|
|
|
57
64
|
@doc = doc
|
|
58
65
|
end
|
|
59
66
|
|
|
60
|
-
def yaml(io = nil,
|
|
61
|
-
|
|
67
|
+
def yaml(io = nil, options = {})
|
|
68
|
+
options ||= {}
|
|
69
|
+
out = @doc.serialize(canonical: options[:canonical] ? true : false,
|
|
70
|
+
best_width: options[:line_width] || 0,
|
|
71
|
+
explicit_doc_start: true)
|
|
62
72
|
return out unless io
|
|
63
73
|
|
|
64
74
|
io.write(out)
|
data/lib/yeptris/psych.rb
CHANGED
|
@@ -424,11 +424,16 @@ module Yeptris
|
|
|
424
424
|
|
|
425
425
|
# Arbitrary object graphs through the YAMLTree visitor
|
|
426
426
|
# (anchors, !ruby/ tags); plain data rides the fast builder.
|
|
427
|
+
# options reach the engine's emitter: :line_width folds flow
|
|
428
|
+
# collections past the width (the engine default is 80),
|
|
429
|
+
# :canonical switches the fixed form; :indentation is accepted
|
|
430
|
+
# for Psych parity (the engine's YAML emitter has no indentation
|
|
431
|
+
# knob). The audit round-2 finding — width accepted and
|
|
432
|
+
# discarded — is closed here and in YAMLTree.create.
|
|
427
433
|
def dump(obj, io = nil, options = {})
|
|
428
434
|
# stdlib's Object#to_yaml calls Psych.dump(self, options) —
|
|
429
435
|
# the options hash lands in the io slot positionally; treat a
|
|
430
|
-
# Hash there as options
|
|
431
|
-
# visitor's defaults, #95 thread)
|
|
436
|
+
# Hash there as options
|
|
432
437
|
if io.is_a?(::Hash)
|
|
433
438
|
options = io
|
|
434
439
|
io = nil
|
|
@@ -443,11 +448,12 @@ module Yeptris
|
|
|
443
448
|
# path would emit it tag-less iso8601 (re-loading as Time);
|
|
444
449
|
# the visitor carries stdlib's !ruby/object:DateTime tag
|
|
445
450
|
when ::DateTime
|
|
446
|
-
Visitors::YAMLTree.new.push(obj).finish
|
|
451
|
+
Visitors::YAMLTree.new(options: options).push(obj).finish
|
|
447
452
|
when nil, true, false, ::String, ::Integer, ::Float, ::Symbol, ::Date, ::Time
|
|
448
|
-
Yeptris::YAML.dump(obj, header: true
|
|
453
|
+
Yeptris::YAML.dump(obj, header: true, width: options[:line_width],
|
|
454
|
+
canonical: options[:canonical] ? true : false)
|
|
449
455
|
else
|
|
450
|
-
Visitors::YAMLTree.new.push(obj).finish
|
|
456
|
+
Visitors::YAMLTree.new(options: options).push(obj).finish
|
|
451
457
|
end
|
|
452
458
|
return out unless io
|
|
453
459
|
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yeptris
|
|
4
|
+
# The validation face (engine validate.h; issue #882, binding
|
|
5
|
+
# TODO item 6). Two checks over one engine pass:
|
|
6
|
+
#
|
|
7
|
+
# * the SCHEMA-FAMILY walk (`validate` with a schema symbol) —
|
|
8
|
+
# explicit-tag consistency, mapping-key uniqueness by canonical
|
|
9
|
+
# form, and (under :json_12) the spec 10.2 JSON-production rules;
|
|
10
|
+
# * JSON SCHEMA draft 2020-12 (`validate` with `json_schema:`) —
|
|
11
|
+
# one-shot over schema bytes, or against a compiled
|
|
12
|
+
# {Yeptris::Validation::Schema} handle (parse once, validate
|
|
13
|
+
# many; #885 item 3).
|
|
14
|
+
#
|
|
15
|
+
# Diagnostics cross the FFI boundary once, as plain Ruby values
|
|
16
|
+
# ({Diagnostic}): no C pointer ever leaks out of this module.
|
|
17
|
+
module Validation
|
|
18
|
+
# Raised for an instance that carries diagnostics. The full report
|
|
19
|
+
# rides `diagnostics` (plain {Diagnostic} values, never pointers).
|
|
20
|
+
class Error < ::Yeptris::Error
|
|
21
|
+
attr_reader :diagnostics
|
|
22
|
+
|
|
23
|
+
def initialize(diagnostics)
|
|
24
|
+
@diagnostics = diagnostics
|
|
25
|
+
first = diagnostics.first
|
|
26
|
+
detail = first ? "#{first.keyword} at #{first.path}: #{first.detail}" : "no diagnostics"
|
|
27
|
+
super("validation failed (#{diagnostics.size} diagnostics): #{detail}")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The code -> keyword table (validate.h's YeptrisDiagCode). The
|
|
32
|
+
# 2020-12 walk's codes name their JSON Schema keyword; the
|
|
33
|
+
# schema-family walk's codes name the violated YAML rule.
|
|
34
|
+
KEYWORD = {
|
|
35
|
+
FFI::DIAG_NONE => "none",
|
|
36
|
+
FFI::DIAG_TAG_MISMATCH => "tag-mismatch",
|
|
37
|
+
FFI::DIAG_KEY_NOT_STRING => "key-not-string",
|
|
38
|
+
FFI::DIAG_NON_JSON_TYPE => "non-json-type",
|
|
39
|
+
FFI::DIAG_DUPLICATE_KEY => "duplicate-key",
|
|
40
|
+
FFI::DIAG_TYPE => "type",
|
|
41
|
+
FFI::DIAG_REQUIRED => "required",
|
|
42
|
+
FFI::DIAG_ENUM => "enum",
|
|
43
|
+
FFI::DIAG_CONST => "const",
|
|
44
|
+
FFI::DIAG_PATTERN => "pattern",
|
|
45
|
+
FFI::DIAG_MINIMUM => "minimum",
|
|
46
|
+
FFI::DIAG_MAXIMUM => "maximum",
|
|
47
|
+
FFI::DIAG_EXCLUSIVE_MINIMUM => "exclusiveMinimum",
|
|
48
|
+
FFI::DIAG_EXCLUSIVE_MAXIMUM => "exclusiveMaximum",
|
|
49
|
+
FFI::DIAG_MIN_LENGTH => "minLength",
|
|
50
|
+
FFI::DIAG_MAX_LENGTH => "maxLength",
|
|
51
|
+
FFI::DIAG_MIN_ITEMS => "minItems",
|
|
52
|
+
FFI::DIAG_MAX_ITEMS => "maxItems",
|
|
53
|
+
FFI::DIAG_UNIQUE_ITEMS => "uniqueItems",
|
|
54
|
+
FFI::DIAG_ADDITIONAL_PROPERTY => "additionalProperties",
|
|
55
|
+
FFI::DIAG_ANY_OF => "anyOf",
|
|
56
|
+
FFI::DIAG_ONE_OF => "oneOf",
|
|
57
|
+
FFI::DIAG_NOT => "not",
|
|
58
|
+
FFI::DIAG_REF => "$ref",
|
|
59
|
+
FFI::DIAG_FALSE_SCHEMA => "false",
|
|
60
|
+
FFI::DIAG_DEPTH => "depth",
|
|
61
|
+
FFI::DIAG_LIMIT => "limit",
|
|
62
|
+
FFI::DIAG_MULTIPLE_OF => "multipleOf",
|
|
63
|
+
FFI::DIAG_MIN_PROPERTIES => "minProperties",
|
|
64
|
+
FFI::DIAG_MAX_PROPERTIES => "maxProperties",
|
|
65
|
+
FFI::DIAG_CONTAINS => "contains",
|
|
66
|
+
FFI::DIAG_MIN_CONTAINS => "minContains",
|
|
67
|
+
FFI::DIAG_MAX_CONTAINS => "maxContains",
|
|
68
|
+
FFI::DIAG_DEPENDENT_REQUIRED => "dependentRequired",
|
|
69
|
+
}.freeze
|
|
70
|
+
|
|
71
|
+
# One structured diagnostic: the engine's yeptris_diagnostic read
|
|
72
|
+
# into plain Ruby at drain time (a value class — ==, eql?, hash —
|
|
73
|
+
# never an FFI wrapper).
|
|
74
|
+
class Diagnostic
|
|
75
|
+
attr_reader :code, :path, :detail, :line, :col, :offset
|
|
76
|
+
|
|
77
|
+
def initialize(code:, path:, detail:, line: 0, col: 0, offset: 0)
|
|
78
|
+
@code = code
|
|
79
|
+
@path = path
|
|
80
|
+
@detail = detail
|
|
81
|
+
@line = line
|
|
82
|
+
@col = col
|
|
83
|
+
@offset = offset
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The violated keyword ("type", "minimum", "duplicate-key", ...).
|
|
87
|
+
def keyword
|
|
88
|
+
KEYWORD.fetch(code, "unknown-#{code}")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The JSON-pointer instance path ("/a/b/0", ~0/~1 escaped) split
|
|
92
|
+
# into its unescaped segments; the root is [].
|
|
93
|
+
def path_array
|
|
94
|
+
return [] if path.empty?
|
|
95
|
+
|
|
96
|
+
path.split("/")[1..].map { |s| s.gsub("~1", "/").gsub("~0", "~") }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The engine's bounded explanation ("3 is less than the minimum 10").
|
|
100
|
+
def message
|
|
101
|
+
detail
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
alias to_s message
|
|
105
|
+
|
|
106
|
+
def ==(other)
|
|
107
|
+
other.is_a?(Diagnostic) && code == other.code && path == other.path &&
|
|
108
|
+
detail == other.detail && line == other.line && col == other.col &&
|
|
109
|
+
offset == other.offset
|
|
110
|
+
end
|
|
111
|
+
alias eql? ==
|
|
112
|
+
|
|
113
|
+
def hash
|
|
114
|
+
[code, path, detail, line, col, offset].hash
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def to_h
|
|
118
|
+
{ code: code, keyword: keyword, path: path, path_array: path_array,
|
|
119
|
+
detail: detail, message: message, line: line, col: col, offset: offset }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def inspect
|
|
123
|
+
"#<#{self.class.name} #{keyword} path=#{path.inspect} line=#{line} " \
|
|
124
|
+
"col=#{col} detail=#{detail.inspect}>"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The compiled schema (yeptris_schema_compile): the schema bytes,
|
|
129
|
+
# its DOM, and the pattern kernel's programs retained behind one
|
|
130
|
+
# opaque handle — parse ONCE, validate many streams against it.
|
|
131
|
+
# Ownership mirrors Yeptris::Document: an explicit #free and a GC
|
|
132
|
+
# finalizer flip the same freed flag (the pointer-only finalizer —
|
|
133
|
+
# it captures the raw address, never the wrapper, so no
|
|
134
|
+
# use-after-free race).
|
|
135
|
+
class Schema
|
|
136
|
+
# Shared between the instance and its finalizer (the
|
|
137
|
+
# leptris-ruby double-free fix).
|
|
138
|
+
Freed = Struct.new(:state) # :alive | :freed
|
|
139
|
+
|
|
140
|
+
attr_reader :c_ptr
|
|
141
|
+
|
|
142
|
+
# Compiles JSON Schema bytes (JSON is YAML flow — a YAML-shaped
|
|
143
|
+
# mapping schema compiles too). Raises ArgumentError for empty
|
|
144
|
+
# bytes (the engine's "hold no document") and ParseError for
|
|
145
|
+
# bytes that do not parse.
|
|
146
|
+
def self.compile(bytes)
|
|
147
|
+
raise ::ArgumentError, "schema bytes are required" if bytes.nil?
|
|
148
|
+
bytes = ::Yeptris.read_input(bytes).to_s
|
|
149
|
+
raise ::ArgumentError, "schema bytes hold no document" if bytes.empty?
|
|
150
|
+
|
|
151
|
+
ptr = FFI.yeptris_schema_compile(bytes, bytes.bytesize)
|
|
152
|
+
raise ::Yeptris::ParseError,
|
|
153
|
+
"schema compile failed: #{FFI.last_error_message}" if ptr.null?
|
|
154
|
+
|
|
155
|
+
new(ptr)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Block form: the handle frees when the block exits (valid or not).
|
|
159
|
+
def self.open(bytes)
|
|
160
|
+
schema = compile(bytes)
|
|
161
|
+
begin
|
|
162
|
+
yield schema
|
|
163
|
+
ensure
|
|
164
|
+
schema.free
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @api private — produced by .compile; a raw pointer argument is
|
|
169
|
+
# not a caller surface (the same contract as Document).
|
|
170
|
+
def initialize(c_ptr)
|
|
171
|
+
unless c_ptr.is_a?(::FFI::Pointer)
|
|
172
|
+
raise ArgumentError,
|
|
173
|
+
"a schema FFI pointer is required — build schemas with " \
|
|
174
|
+
"Yeptris::Validation::Schema.compile"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
@c_ptr = c_ptr
|
|
178
|
+
@freed = Freed.new(:alive)
|
|
179
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(c_ptr)) unless c_ptr.null?
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Validates one instance stream against the retained model.
|
|
183
|
+
# Returns the diagnostics array (empty = valid).
|
|
184
|
+
def validate(bytes)
|
|
185
|
+
ensure_alive!
|
|
186
|
+
bytes = ::Yeptris.read_input(bytes).to_s
|
|
187
|
+
report = ::FFI::MemoryPointer.new(:pointer)
|
|
188
|
+
st = FFI.yeptris_schema_validate(@c_ptr, bytes, bytes.bytesize, report)
|
|
189
|
+
Validation.consume_report(st, report, "schema validate")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def valid?(bytes)
|
|
193
|
+
validate(bytes).empty?
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def ensure_alive!
|
|
197
|
+
raise ::Yeptris::FreedError, "schema is freed" if @freed.state == :freed
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def free
|
|
201
|
+
return if @freed.state == :freed
|
|
202
|
+
|
|
203
|
+
@freed.state = :freed
|
|
204
|
+
ObjectSpace.undefine_finalizer(self)
|
|
205
|
+
FFI.yeptris_schema_free(@c_ptr)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def freed?
|
|
209
|
+
@freed.state == :freed
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Pointer-only finalizer (Document's law): no Ruby objects, no
|
|
213
|
+
# wrapper — a finalizer closing over the wrapper races its sweep.
|
|
214
|
+
def self.finalize(c_ptr)
|
|
215
|
+
proc { FFI.yeptris_schema_free(c_ptr) }
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
class << self
|
|
220
|
+
# The schema-family walk over the engine's own front door
|
|
221
|
+
# (encodings, BOM, every document of the stream). schema: one of
|
|
222
|
+
# :core_12, :compat_11, :json_12. Returns the diagnostics array.
|
|
223
|
+
def validate_family(bytes, schema: :core_12)
|
|
224
|
+
require_engine!
|
|
225
|
+
bytes = ::Yeptris.read_input(bytes).to_s
|
|
226
|
+
report = ::FFI::MemoryPointer.new(:pointer)
|
|
227
|
+
st = FFI.yeptris_validate(bytes, bytes.bytesize, schema_id(schema), report)
|
|
228
|
+
consume_report(st, report, "validate")
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# JSON Schema validation: `schema` is a compiled
|
|
232
|
+
# {Yeptris::Validation::Schema} (validate many) or schema bytes
|
|
233
|
+
# (one-shot). Returns the diagnostics array.
|
|
234
|
+
def validate_schema(bytes, schema)
|
|
235
|
+
require_engine!
|
|
236
|
+
case schema
|
|
237
|
+
when Schema then schema.validate(bytes)
|
|
238
|
+
when String then one_shot(bytes, schema)
|
|
239
|
+
else
|
|
240
|
+
raise ::ArgumentError,
|
|
241
|
+
"json_schema: must be a Yeptris::Validation::Schema or schema " \
|
|
242
|
+
"bytes (got #{schema.class})"
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# The engine's whole-report drain shared by every face: maps the
|
|
247
|
+
# status, drains the diagnostics into plain values, frees the
|
|
248
|
+
# report — exactly once, on every path.
|
|
249
|
+
def consume_report(status, report, action)
|
|
250
|
+
ptr = report.read_pointer
|
|
251
|
+
diags = drain_report(ptr)
|
|
252
|
+
return diags if status == FFI::OK || status == FFI::SCHEMA_ERROR
|
|
253
|
+
|
|
254
|
+
raise ::ArgumentError, "#{action}: #{FFI.last_error_message}" if status == FFI::ERROR_ARG
|
|
255
|
+
|
|
256
|
+
raise ::Yeptris::ParseError, "#{action} failed: #{FFI.last_error_message}"
|
|
257
|
+
ensure
|
|
258
|
+
FFI.yeptris_validate_report_free(ptr) unless ptr.null?
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# The schema symbol -> YeptrisSchema id (resolve.h's enum) for
|
|
262
|
+
# the VALIDATION family walks: only the YAML 1.2 families are
|
|
263
|
+
# validation families — the engine rejects :compat_11 with
|
|
264
|
+
# "schema 1 is not a validation family", surfaced here as the
|
|
265
|
+
# same ArgumentError the other bad symbols take.
|
|
266
|
+
def schema_id(name)
|
|
267
|
+
case name
|
|
268
|
+
when :core_12 then FFI::SCHEMA_12_CORE
|
|
269
|
+
when :json_12 then FFI::SCHEMA_12_JSON
|
|
270
|
+
when :compat_11
|
|
271
|
+
raise ::ArgumentError,
|
|
272
|
+
"schema: :compat_11 is not a validation family — the walk " \
|
|
273
|
+
"covers the YAML 1.2 families (:core_12, :json_12)"
|
|
274
|
+
else
|
|
275
|
+
raise ::ArgumentError,
|
|
276
|
+
"schema: must be :core_12, :compat_11, or :json_12 (got #{name.inspect})"
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
private
|
|
281
|
+
|
|
282
|
+
def one_shot(bytes, schema_bytes)
|
|
283
|
+
bytes = ::Yeptris.read_input(bytes).to_s
|
|
284
|
+
schema_bytes = schema_bytes.to_s
|
|
285
|
+
report = ::FFI::MemoryPointer.new(:pointer)
|
|
286
|
+
st = FFI.yeptris_json_schema_validate(schema_bytes, schema_bytes.bytesize,
|
|
287
|
+
bytes, bytes.bytesize, report)
|
|
288
|
+
consume_report(st, report, "json schema validate")
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# drain before free: the diagnostics are read into plain Ruby
|
|
292
|
+
# while the report is still alive. The texts re-tag UTF-8 (the
|
|
293
|
+
# binding's string discipline — #135: engine materializations
|
|
294
|
+
# ride UTF-8, not the raw BINARY read).
|
|
295
|
+
def drain_report(report)
|
|
296
|
+
return [] if report.null?
|
|
297
|
+
|
|
298
|
+
FFI.yeptris_validate_report_count(report).times.map do |i|
|
|
299
|
+
d = FFI::CDiagnostic.new(FFI.yeptris_validate_report_at(report, i))
|
|
300
|
+
Diagnostic.new(
|
|
301
|
+
code: d[:code],
|
|
302
|
+
path: d.pointer.get_string(FFI::CDiagnostic.offset_of(:path))
|
|
303
|
+
.force_encoding(::Encoding::UTF_8),
|
|
304
|
+
detail: d.pointer.get_string(FFI::CDiagnostic.offset_of(:detail))
|
|
305
|
+
.force_encoding(::Encoding::UTF_8),
|
|
306
|
+
line: d[:line], col: d[:col], offset: d[:offset]
|
|
307
|
+
)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def require_engine!
|
|
312
|
+
return if FFI::VALIDATION
|
|
313
|
+
|
|
314
|
+
raise ::Yeptris::Error,
|
|
315
|
+
"yeptris: the validation face needs libyeptris 0.6.59+; the " \
|
|
316
|
+
"loaded library predates it. Update the engine library."
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|