senko 0.1.0
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 +7 -0
- data/Gemfile +13 -0
- data/LICENSE +21 -0
- data/README.md +235 -0
- data/Rakefile +56 -0
- data/ext/senko/extconf.rb +5 -0
- data/ext/senko/instructions.c +1 -0
- data/ext/senko/instructions.h +12 -0
- data/ext/senko/senko.c +17 -0
- data/ext/senko/validator.c +107 -0
- data/ext/senko/validator.h +14 -0
- data/lib/senko/cache.rb +57 -0
- data/lib/senko/code_generator.rb +270 -0
- data/lib/senko/compiler/instruction.rb +69 -0
- data/lib/senko/compiler/optimizer.rb +80 -0
- data/lib/senko/compiler/ref_resolver.rb +409 -0
- data/lib/senko/compiler.rb +991 -0
- data/lib/senko/dialect.rb +59 -0
- data/lib/senko/errors.rb +41 -0
- data/lib/senko/format.rb +327 -0
- data/lib/senko/native.rb +11 -0
- data/lib/senko/result.rb +65 -0
- data/lib/senko/schema.rb +58 -0
- data/lib/senko/validator.rb +1391 -0
- data/lib/senko/version.rb +5 -0
- data/lib/senko/vocabulary.rb +25 -0
- data/lib/senko.rb +171 -0
- data/sig/senko.rbs +45 -0
- metadata +170 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senko
|
|
4
|
+
class Vocabulary
|
|
5
|
+
attr_reader :custom_keywords
|
|
6
|
+
|
|
7
|
+
def initialize(custom_keywords = {})
|
|
8
|
+
@custom_keywords = custom_keywords || {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def custom?(keyword)
|
|
12
|
+
@custom_keywords.key?(keyword)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def validator_for(keyword)
|
|
16
|
+
@custom_keywords[keyword]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def register(keyword, validator)
|
|
20
|
+
raise ArgumentError, 'keyword validator must respond to call' unless validator.respond_to?(:call)
|
|
21
|
+
|
|
22
|
+
@custom_keywords[keyword.to_s] = validator
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/senko.rb
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
require_relative 'senko/cache'
|
|
6
|
+
require_relative 'senko/code_generator'
|
|
7
|
+
require_relative 'senko/compiler'
|
|
8
|
+
require_relative 'senko/dialect'
|
|
9
|
+
require_relative 'senko/errors'
|
|
10
|
+
require_relative 'senko/format'
|
|
11
|
+
require_relative 'senko/native'
|
|
12
|
+
require_relative 'senko/result'
|
|
13
|
+
require_relative 'senko/schema'
|
|
14
|
+
require_relative 'senko/validator'
|
|
15
|
+
require_relative 'senko/version'
|
|
16
|
+
require_relative 'senko/vocabulary'
|
|
17
|
+
|
|
18
|
+
module Senko
|
|
19
|
+
GLOBAL_CACHE = Cache.new
|
|
20
|
+
GLOBAL_FORMATS = {}
|
|
21
|
+
GLOBAL_KEYWORDS = {}
|
|
22
|
+
UNSET = Object.new
|
|
23
|
+
@registry_version = 0
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
def compile(schema = UNSET, **options)
|
|
28
|
+
schema, options = split_schema_keywords(options) if schema.equal?(UNSET)
|
|
29
|
+
|
|
30
|
+
merged_options = merged_options(options)
|
|
31
|
+
if cacheable?(schema, merged_options)
|
|
32
|
+
GLOBAL_CACHE.fetch(cache_key(schema, merged_options)) { build_schema(schema, merged_options) }
|
|
33
|
+
else
|
|
34
|
+
build_schema(schema, merged_options)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def compile_file(path, **options)
|
|
39
|
+
compile(JSON.parse(File.read(path)), **options)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def valid?(schema, data, **options)
|
|
43
|
+
compile(schema, **options).valid?(data)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def validate(schema, data, **options)
|
|
47
|
+
compile(schema, **options).validate(data)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def valid_json?(schema, json, **options)
|
|
51
|
+
compile(schema, **options).valid_json?(json)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def validate_json(schema, json, **options)
|
|
55
|
+
compile(schema, **options).validate_json(json)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def clear_cache!
|
|
59
|
+
GLOBAL_CACHE.clear
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def register_format(name, callable = nil, &block)
|
|
64
|
+
validator = callable || block
|
|
65
|
+
raise ArgumentError, 'format validator must respond to call' unless validator.respond_to?(:call)
|
|
66
|
+
|
|
67
|
+
GLOBAL_FORMATS[name.to_s] = validator
|
|
68
|
+
touch_registry!
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def register_keyword(name, callable = nil, &block)
|
|
72
|
+
validator = callable || block
|
|
73
|
+
raise ArgumentError, 'keyword validator must respond to call' unless validator.respond_to?(:call)
|
|
74
|
+
|
|
75
|
+
GLOBAL_KEYWORDS[name.to_s] = validator
|
|
76
|
+
touch_registry!
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def clear_registry!
|
|
80
|
+
GLOBAL_FORMATS.clear
|
|
81
|
+
GLOBAL_KEYWORDS.clear
|
|
82
|
+
touch_registry!
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def registry_version
|
|
86
|
+
@registry_version
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def split_schema_keywords(keywords)
|
|
90
|
+
schema = {}
|
|
91
|
+
options = {}
|
|
92
|
+
|
|
93
|
+
keywords.each do |key, value|
|
|
94
|
+
if key.is_a?(Symbol) && Compiler::DEFAULT_OPTIONS.key?(key)
|
|
95
|
+
options[key] = value
|
|
96
|
+
else
|
|
97
|
+
schema[key] = value
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
return [keywords, {}] if schema.empty?
|
|
102
|
+
|
|
103
|
+
[schema, options]
|
|
104
|
+
end
|
|
105
|
+
private_class_method :split_schema_keywords
|
|
106
|
+
|
|
107
|
+
def merged_options(options)
|
|
108
|
+
merged = Compiler::DEFAULT_OPTIONS.merge(options)
|
|
109
|
+
merged[:custom_formats] = GLOBAL_FORMATS.merge(merged[:custom_formats] || {})
|
|
110
|
+
merged[:custom_keywords] = GLOBAL_KEYWORDS.merge(merged[:custom_keywords] || {})
|
|
111
|
+
merged[:registry_version] = registry_version
|
|
112
|
+
merged
|
|
113
|
+
end
|
|
114
|
+
private_class_method :merged_options
|
|
115
|
+
|
|
116
|
+
def touch_registry!
|
|
117
|
+
@registry_version += 1
|
|
118
|
+
clear_cache!
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
private_class_method :touch_registry!
|
|
122
|
+
|
|
123
|
+
def build_schema(schema, options)
|
|
124
|
+
instructions = Compiler.new(options).compile(schema)
|
|
125
|
+
generated_validator = CodeGenerator.generate(schema) unless options[:codegen] == false
|
|
126
|
+
Schema.new(
|
|
127
|
+
source: schema,
|
|
128
|
+
instructions: instructions,
|
|
129
|
+
options: options,
|
|
130
|
+
generated_validator: generated_validator
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
private_class_method :build_schema
|
|
134
|
+
|
|
135
|
+
def cacheable?(schema, options)
|
|
136
|
+
!contains_callable?(schema) && !contains_callable?(options)
|
|
137
|
+
end
|
|
138
|
+
private_class_method :cacheable?
|
|
139
|
+
|
|
140
|
+
def contains_callable?(value)
|
|
141
|
+
if value.respond_to?(:call)
|
|
142
|
+
true
|
|
143
|
+
elsif value.is_a?(Hash)
|
|
144
|
+
value.any? { |key, child| contains_callable?(key) || contains_callable?(child) }
|
|
145
|
+
elsif value.is_a?(Array)
|
|
146
|
+
value.any? { |child| contains_callable?(child) }
|
|
147
|
+
else
|
|
148
|
+
false
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
private_class_method :contains_callable?
|
|
152
|
+
|
|
153
|
+
def cache_key(schema, options)
|
|
154
|
+
JSON.generate(canonical([schema, options]))
|
|
155
|
+
end
|
|
156
|
+
private_class_method :cache_key
|
|
157
|
+
|
|
158
|
+
def canonical(value)
|
|
159
|
+
case value
|
|
160
|
+
when Hash
|
|
161
|
+
value.keys.sort_by(&:to_s).map { |key| [key.to_s, canonical(value[key])] }
|
|
162
|
+
when Array
|
|
163
|
+
value.map { |child| canonical(child) }
|
|
164
|
+
when Symbol
|
|
165
|
+
value.to_s
|
|
166
|
+
else
|
|
167
|
+
value
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
private_class_method :canonical
|
|
171
|
+
end
|
data/sig/senko.rbs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Senko
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
def self.compile: (Hash[String, untyped] | bool schema, **untyped options) -> Schema
|
|
5
|
+
def self.compile_file: (String path, **untyped options) -> Schema
|
|
6
|
+
def self.valid?: (Hash[String, untyped] | bool schema, untyped data, **untyped options) -> bool
|
|
7
|
+
def self.validate: (Hash[String, untyped] | bool schema, untyped data, **untyped options) -> Result
|
|
8
|
+
def self.valid_json?: (Hash[String, untyped] | bool schema, String json, **untyped options) -> bool
|
|
9
|
+
def self.validate_json: (Hash[String, untyped] | bool schema, String json, **untyped options) -> Result
|
|
10
|
+
def self.clear_cache!: () -> nil
|
|
11
|
+
def self.register_format: (String name, ?untyped callable) { (String) -> bool } -> nil
|
|
12
|
+
def self.register_keyword: (String name, ?untyped callable) { (untyped, untyped) -> bool } -> nil
|
|
13
|
+
def self.clear_registry!: () -> nil
|
|
14
|
+
|
|
15
|
+
class Schema
|
|
16
|
+
def valid?: (untyped data) -> bool
|
|
17
|
+
def validate: (untyped data, ?fail_fast: bool) -> Result
|
|
18
|
+
def valid_json?: (String json) -> bool
|
|
19
|
+
def validate_json: (String json, ?fail_fast: bool) -> Result
|
|
20
|
+
def validate!: (untyped data) -> untyped
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Result
|
|
24
|
+
attr_reader errors: Array[Error]
|
|
25
|
+
def valid?: () -> bool
|
|
26
|
+
def to_basic: () -> Hash[String, untyped]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class Error
|
|
30
|
+
attr_reader message: String
|
|
31
|
+
attr_reader instance_location: String
|
|
32
|
+
attr_reader keyword_location: String
|
|
33
|
+
attr_reader keyword: String
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module Native
|
|
37
|
+
def self.type_mask: (untyped value) -> Integer
|
|
38
|
+
def self.string_length: (String value) -> Integer
|
|
39
|
+
def self.numeric_lte?: (Numeric left, Numeric right) -> bool
|
|
40
|
+
def self.numeric_lt?: (Numeric left, Numeric right) -> bool
|
|
41
|
+
def self.numeric_gte?: (Numeric left, Numeric right) -> bool
|
|
42
|
+
def self.numeric_gt?: (Numeric left, Numeric right) -> bool
|
|
43
|
+
def self.unique_array?: (Array[untyped] array) -> bool
|
|
44
|
+
end
|
|
45
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: senko
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yudai Takada
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: addressable
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: bigdecimal
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: date
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: ipaddr
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: json
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: simpleidn
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: uri
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
description: A compiled JSON Schema validator targeting Draft 2020-12 with a pure
|
|
111
|
+
Ruby fallback.
|
|
112
|
+
email:
|
|
113
|
+
- t.yudai92@gmail.com
|
|
114
|
+
executables: []
|
|
115
|
+
extensions:
|
|
116
|
+
- ext/senko/extconf.rb
|
|
117
|
+
extra_rdoc_files: []
|
|
118
|
+
files:
|
|
119
|
+
- Gemfile
|
|
120
|
+
- LICENSE
|
|
121
|
+
- README.md
|
|
122
|
+
- Rakefile
|
|
123
|
+
- ext/senko/extconf.rb
|
|
124
|
+
- ext/senko/instructions.c
|
|
125
|
+
- ext/senko/instructions.h
|
|
126
|
+
- ext/senko/senko.c
|
|
127
|
+
- ext/senko/validator.c
|
|
128
|
+
- ext/senko/validator.h
|
|
129
|
+
- lib/senko.rb
|
|
130
|
+
- lib/senko/cache.rb
|
|
131
|
+
- lib/senko/code_generator.rb
|
|
132
|
+
- lib/senko/compiler.rb
|
|
133
|
+
- lib/senko/compiler/instruction.rb
|
|
134
|
+
- lib/senko/compiler/optimizer.rb
|
|
135
|
+
- lib/senko/compiler/ref_resolver.rb
|
|
136
|
+
- lib/senko/dialect.rb
|
|
137
|
+
- lib/senko/errors.rb
|
|
138
|
+
- lib/senko/format.rb
|
|
139
|
+
- lib/senko/native.rb
|
|
140
|
+
- lib/senko/result.rb
|
|
141
|
+
- lib/senko/schema.rb
|
|
142
|
+
- lib/senko/validator.rb
|
|
143
|
+
- lib/senko/version.rb
|
|
144
|
+
- lib/senko/vocabulary.rb
|
|
145
|
+
- sig/senko.rbs
|
|
146
|
+
homepage: https://example.invalid/senko
|
|
147
|
+
licenses:
|
|
148
|
+
- MIT
|
|
149
|
+
metadata:
|
|
150
|
+
homepage_uri: https://example.invalid/senko
|
|
151
|
+
source_code_uri: https://example.invalid/senko
|
|
152
|
+
rubygems_mfa_required: 'true'
|
|
153
|
+
rdoc_options: []
|
|
154
|
+
require_paths:
|
|
155
|
+
- lib
|
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
157
|
+
requirements:
|
|
158
|
+
- - ">="
|
|
159
|
+
- !ruby/object:Gem::Version
|
|
160
|
+
version: '3.1'
|
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
|
+
requirements:
|
|
163
|
+
- - ">="
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '0'
|
|
166
|
+
requirements: []
|
|
167
|
+
rubygems_version: 4.0.6
|
|
168
|
+
specification_version: 4
|
|
169
|
+
summary: Fast JSON Schema validator for Ruby
|
|
170
|
+
test_files: []
|