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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 746b5439c7763d09f66b22bac6b9f924423a2c62c6524301dd1607c4ce9d8bfa
|
|
4
|
+
data.tar.gz: d6fa6a836ab232d508feed3dc603a066b762f81e6ee1b1cce9c46ebf638df19e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0caebf41b4dfaffbafcf8b0bde2ba6e3c3eaa98c8c8dd13eec8e1221290669fd1a6cb2c18b404f7ec7cac4766ef25957970643148b93cd54164472d644ce1611
|
|
7
|
+
data.tar.gz: c2f482f18118b8b173be18c64853d848deedfa1c6d208444ffbbd619a9eb08642772cbede0743a114470e8b88c5dce8b4206202cedcc356bff8a9a1768744a90
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yudai Takada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Senko
|
|
2
|
+
|
|
3
|
+
Senko is a fast JSON Schema validator for Ruby. It compiles schemas into a
|
|
4
|
+
reusable representation, uses generated Ruby code for simple boolean
|
|
5
|
+
validation, and falls back to a full interpreter for detailed errors and
|
|
6
|
+
advanced JSON Schema semantics.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Add this line to your application's Gemfile:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem 'senko'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
And then execute:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
bundle install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or install it yourself as:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
gem install senko
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Senko includes a native extension for validation hot paths. If the extension
|
|
29
|
+
cannot be loaded, validation continues through the pure Ruby fallback.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
Compile a schema once and reuse it:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
require 'senko'
|
|
37
|
+
|
|
38
|
+
schema = Senko.compile(
|
|
39
|
+
'type' => 'object',
|
|
40
|
+
'required' => ['name'],
|
|
41
|
+
'properties' => {
|
|
42
|
+
'name' => { 'type' => 'string', 'minLength' => 1 }
|
|
43
|
+
},
|
|
44
|
+
'additionalProperties' => false
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
schema.valid?({ 'name' => 'senko' })
|
|
48
|
+
# => true
|
|
49
|
+
|
|
50
|
+
schema.valid?({ 'name' => '' })
|
|
51
|
+
# => false
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`Senko.compile` accepts either a schema hash or schema keywords directly.
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
Senko.compile({ 'type' => 'string' })
|
|
58
|
+
Senko.compile('type' => 'string')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Validation
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
schema.valid?(data) # boolean-only validation
|
|
65
|
+
schema.validate(data) # returns Senko::Result
|
|
66
|
+
schema.validate!(data) # returns data or raises Senko::ValidationError
|
|
67
|
+
schema.valid_json?(json) # parses and validates a JSON string
|
|
68
|
+
schema.validate_json(json) # parses JSON and returns Senko::Result
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
One-shot helpers are also available:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
Senko.valid?(schema_hash, data)
|
|
75
|
+
Senko.validate(schema_hash, data)
|
|
76
|
+
Senko.valid_json?(schema_hash, '[1, 2, 3]')
|
|
77
|
+
Senko.validate_json(schema_hash, '[1, 2, 3]')
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Compile from a JSON file:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
schema = Senko.compile_file('schema.json')
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Errors and Output
|
|
87
|
+
|
|
88
|
+
`validate` returns a `Senko::Result`.
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
result = schema.validate({ 'name' => '' })
|
|
92
|
+
|
|
93
|
+
result.valid?
|
|
94
|
+
# => false
|
|
95
|
+
|
|
96
|
+
result.errors.map(&:to_h)
|
|
97
|
+
# => [
|
|
98
|
+
# {
|
|
99
|
+
# 'keywordLocation' => '/properties/name/minLength',
|
|
100
|
+
# 'instanceLocation' => '/name',
|
|
101
|
+
# 'error' => 'string length must be >= 1'
|
|
102
|
+
# }
|
|
103
|
+
# ]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Result objects can be rendered in JSON Schema output-style shapes:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
result.to_basic
|
|
110
|
+
result.to_detailed
|
|
111
|
+
result.to_verbose
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Options
|
|
115
|
+
|
|
116
|
+
Options can be passed to `compile` or to the one-shot helpers.
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
schema = Senko.compile(
|
|
120
|
+
schema_hash,
|
|
121
|
+
format: :assertion,
|
|
122
|
+
fail_fast: true,
|
|
123
|
+
validate_meta_schema: true,
|
|
124
|
+
codegen: :auto
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Common options:
|
|
129
|
+
|
|
130
|
+
- `format: :annotation` records format annotations without failing validation.
|
|
131
|
+
- `format: :assertion` makes supported formats validation errors.
|
|
132
|
+
- `fail_fast: true` stops detailed validation after the first error.
|
|
133
|
+
- `validate_meta_schema: true` checks schema shape before compilation.
|
|
134
|
+
- `codegen: false` disables the generated boolean fast path.
|
|
135
|
+
- `messages: { type: 'custom message' }` customizes built-in error messages.
|
|
136
|
+
- `schemas: { uri => schema_hash }` registers external schemas for `$ref`.
|
|
137
|
+
|
|
138
|
+
### Custom Formats and Keywords
|
|
139
|
+
|
|
140
|
+
Register process-wide extensions:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
Senko.register_format('starts-with-x') do |value|
|
|
144
|
+
value.start_with?('x-')
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
Senko.register_keyword('even') do |data, enabled|
|
|
148
|
+
!enabled || data.even?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
Senko.clear_registry!
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Or pass extensions to a single compiled schema:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
schema = Senko.compile(
|
|
158
|
+
{ 'format' => 'starts-with-x' },
|
|
159
|
+
format: :assertion,
|
|
160
|
+
custom_formats: {
|
|
161
|
+
'starts-with-x' => ->(value) { value.start_with?('x-') }
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Drafts and OpenAPI
|
|
167
|
+
|
|
168
|
+
Senko detects `$schema` when present and defaults to Draft 2020-12 otherwise.
|
|
169
|
+
It supports compatibility paths for Draft 2019-09, Draft 7, Draft 6, and
|
|
170
|
+
Draft 4.
|
|
171
|
+
|
|
172
|
+
Legacy and OpenAPI forms are normalized where possible:
|
|
173
|
+
|
|
174
|
+
- `definitions`
|
|
175
|
+
- tuple `items` and `additionalItems`
|
|
176
|
+
- `dependencies`
|
|
177
|
+
- legacy `id`
|
|
178
|
+
- Draft 4 boolean exclusive numeric bounds
|
|
179
|
+
- OpenAPI 3.0 `nullable: true`
|
|
180
|
+
|
|
181
|
+
OpenAPI-style `oneOf` and `anyOf` schemas with a required const discriminator
|
|
182
|
+
property, or an explicit `discriminator.mapping`, are compiled into direct
|
|
183
|
+
discriminator dispatch.
|
|
184
|
+
|
|
185
|
+
### Performance
|
|
186
|
+
|
|
187
|
+
`valid?` uses generated Ruby code when the schema is supported by the code
|
|
188
|
+
generator. Schemas that require full JSON Schema semantics use the interpreter.
|
|
189
|
+
`validate` always uses the interpreter so detailed errors and annotations remain
|
|
190
|
+
available.
|
|
191
|
+
|
|
192
|
+
Run the local performance gate:
|
|
193
|
+
|
|
194
|
+
```sh
|
|
195
|
+
bundle exec rake benchmark:check
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The benchmark compares Senko against `json_schemer` across codegen,
|
|
199
|
+
interpreter, `$ref`, `oneOf`, and `unevaluatedProperties` scenarios.
|
|
200
|
+
|
|
201
|
+
## Development
|
|
202
|
+
|
|
203
|
+
After checking out the repo, install dependencies:
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
bundle install
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`Gemfile.lock` is intentionally ignored for this gem.
|
|
210
|
+
|
|
211
|
+
Useful commands:
|
|
212
|
+
|
|
213
|
+
```sh
|
|
214
|
+
bundle exec rake native:compile
|
|
215
|
+
bundle exec rake test
|
|
216
|
+
bundle exec rake suite:install
|
|
217
|
+
bundle exec rake suite:optional
|
|
218
|
+
bundle exec rake benchmark:check
|
|
219
|
+
bundle exec rake ci
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`rake ci` compiles the native extension, runs the unit tests, runs the optional
|
|
223
|
+
JSON Schema Test Suite, checks performance, and builds the gem.
|
|
224
|
+
|
|
225
|
+
## Contributing
|
|
226
|
+
|
|
227
|
+
Bug reports and pull requests are welcome. Before opening a pull request, run:
|
|
228
|
+
|
|
229
|
+
```sh
|
|
230
|
+
bundle exec rake ci
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
The gem is available as open source under the terms of the MIT License.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rbconfig'
|
|
5
|
+
require 'rake/testtask'
|
|
6
|
+
|
|
7
|
+
Rake::TestTask.new(:test) do |task|
|
|
8
|
+
task.libs << 'test'
|
|
9
|
+
task.test_files = FileList['test/**/*_test.rb']
|
|
10
|
+
task.warning = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
task default: :test
|
|
14
|
+
|
|
15
|
+
namespace :suite do
|
|
16
|
+
desc 'Install JSON-Schema-Test-Suite into test/suite'
|
|
17
|
+
task :install do
|
|
18
|
+
suite_dir = File.expand_path('test/suite', __dir__)
|
|
19
|
+
if Dir.exist?(suite_dir)
|
|
20
|
+
puts 'test/suite already exists'
|
|
21
|
+
else
|
|
22
|
+
sh 'git', 'clone', 'https://github.com/json-schema-org/JSON-Schema-Test-Suite.git', suite_dir
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
desc 'Run the JSON-Schema-Test-Suite including optional tests'
|
|
27
|
+
task :optional do
|
|
28
|
+
sh({ 'SENKO_OPTIONAL_SUITE' => '1' }, RbConfig.ruby, '-Itest', '-Ilib', 'test/suite_runner_test.rb')
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
namespace :native do
|
|
33
|
+
desc 'Build the native extension in ext/senko'
|
|
34
|
+
task :compile do
|
|
35
|
+
Dir.chdir(File.expand_path('ext/senko', __dir__)) do
|
|
36
|
+
sh RbConfig.ruby, 'extconf.rb'
|
|
37
|
+
sh 'make', 'clean'
|
|
38
|
+
sh 'make'
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
namespace :benchmark do
|
|
44
|
+
desc 'Compare Senko validation speed against json_schemer'
|
|
45
|
+
task :compare do
|
|
46
|
+
sh RbConfig.ruby, 'test/benchmark/bench_compare.rb'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
desc 'Fail if Senko misses the configured json_schemer performance target'
|
|
50
|
+
task :check do
|
|
51
|
+
sh RbConfig.ruby, 'test/benchmark/performance_check.rb'
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc 'Run the local CI checks'
|
|
56
|
+
task ci: ['suite:install', 'native:compile', :test, 'suite:optional', 'benchmark:check', :build]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#include "instructions.h"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#ifndef SENKO_INSTRUCTIONS_H
|
|
2
|
+
#define SENKO_INSTRUCTIONS_H 1
|
|
3
|
+
|
|
4
|
+
#define SENKO_TYPE_NULL 1
|
|
5
|
+
#define SENKO_TYPE_BOOLEAN 2
|
|
6
|
+
#define SENKO_TYPE_INTEGER 4
|
|
7
|
+
#define SENKO_TYPE_NUMBER 12
|
|
8
|
+
#define SENKO_TYPE_STRING 16
|
|
9
|
+
#define SENKO_TYPE_ARRAY 32
|
|
10
|
+
#define SENKO_TYPE_OBJECT 64
|
|
11
|
+
|
|
12
|
+
#endif
|
data/ext/senko/senko.c
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#include "ruby.h"
|
|
2
|
+
#include "validator.h"
|
|
3
|
+
|
|
4
|
+
void
|
|
5
|
+
Init_senko(void)
|
|
6
|
+
{
|
|
7
|
+
VALUE mSenko = rb_define_module("Senko");
|
|
8
|
+
VALUE mNative = rb_define_module_under(mSenko, "Native");
|
|
9
|
+
|
|
10
|
+
rb_define_singleton_method(mNative, "type_mask", senko_native_type_mask, 1);
|
|
11
|
+
rb_define_singleton_method(mNative, "string_length", senko_native_string_length, 1);
|
|
12
|
+
rb_define_singleton_method(mNative, "numeric_lte?", senko_native_numeric_lte, 2);
|
|
13
|
+
rb_define_singleton_method(mNative, "numeric_lt?", senko_native_numeric_lt, 2);
|
|
14
|
+
rb_define_singleton_method(mNative, "numeric_gte?", senko_native_numeric_gte, 2);
|
|
15
|
+
rb_define_singleton_method(mNative, "numeric_gt?", senko_native_numeric_gt, 2);
|
|
16
|
+
rb_define_singleton_method(mNative, "unique_array?", senko_native_unique_array_p, 1);
|
|
17
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#include "ruby.h"
|
|
2
|
+
#include <math.h>
|
|
3
|
+
#include "validator.h"
|
|
4
|
+
#include "instructions.h"
|
|
5
|
+
|
|
6
|
+
static int
|
|
7
|
+
senko_integer_number_p(VALUE value)
|
|
8
|
+
{
|
|
9
|
+
switch (TYPE(value)) {
|
|
10
|
+
case T_FIXNUM:
|
|
11
|
+
case T_BIGNUM:
|
|
12
|
+
return 1;
|
|
13
|
+
case T_FLOAT: {
|
|
14
|
+
double number = RFLOAT_VALUE(value);
|
|
15
|
+
return isfinite(number) && floor(number) == number;
|
|
16
|
+
}
|
|
17
|
+
default:
|
|
18
|
+
if (rb_obj_is_kind_of(value, rb_path2class("BigDecimal")) == Qtrue) {
|
|
19
|
+
VALUE integer = rb_funcall(value, rb_intern2("to_i", 4), 0);
|
|
20
|
+
return RTEST(rb_funcall(value, rb_intern2("==", 2), 1, integer));
|
|
21
|
+
}
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
VALUE
|
|
27
|
+
senko_native_type_mask(VALUE self, VALUE value)
|
|
28
|
+
{
|
|
29
|
+
switch (TYPE(value)) {
|
|
30
|
+
case T_NIL:
|
|
31
|
+
return INT2NUM(SENKO_TYPE_NULL);
|
|
32
|
+
case T_TRUE:
|
|
33
|
+
case T_FALSE:
|
|
34
|
+
return INT2NUM(SENKO_TYPE_BOOLEAN);
|
|
35
|
+
case T_FIXNUM:
|
|
36
|
+
case T_BIGNUM:
|
|
37
|
+
return INT2NUM(SENKO_TYPE_INTEGER);
|
|
38
|
+
case T_FLOAT:
|
|
39
|
+
return INT2NUM(senko_integer_number_p(value) ? SENKO_TYPE_INTEGER : (SENKO_TYPE_NUMBER & ~SENKO_TYPE_INTEGER));
|
|
40
|
+
case T_STRING:
|
|
41
|
+
return INT2NUM(SENKO_TYPE_STRING);
|
|
42
|
+
case T_ARRAY:
|
|
43
|
+
return INT2NUM(SENKO_TYPE_ARRAY);
|
|
44
|
+
case T_HASH:
|
|
45
|
+
return INT2NUM(SENKO_TYPE_OBJECT);
|
|
46
|
+
default:
|
|
47
|
+
if (rb_obj_is_kind_of(value, rb_path2class("BigDecimal")) == Qtrue)
|
|
48
|
+
return INT2NUM(senko_integer_number_p(value) ? SENKO_TYPE_INTEGER : (SENKO_TYPE_NUMBER & ~SENKO_TYPE_INTEGER));
|
|
49
|
+
return INT2NUM(0);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
VALUE
|
|
54
|
+
senko_native_string_length(VALUE self, VALUE value)
|
|
55
|
+
{
|
|
56
|
+
Check_Type(value, T_STRING);
|
|
57
|
+
ID id_length = rb_intern2("length", 6);
|
|
58
|
+
return rb_funcall(value, id_length, 0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
VALUE
|
|
62
|
+
senko_native_numeric_lte(VALUE self, VALUE left, VALUE right)
|
|
63
|
+
{
|
|
64
|
+
ID id_lte = rb_intern2("<=", 2);
|
|
65
|
+
return RTEST(rb_funcall(left, id_lte, 1, right)) ? Qtrue : Qfalse;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
VALUE
|
|
69
|
+
senko_native_numeric_lt(VALUE self, VALUE left, VALUE right)
|
|
70
|
+
{
|
|
71
|
+
ID id_lt = rb_intern2("<", 1);
|
|
72
|
+
return RTEST(rb_funcall(left, id_lt, 1, right)) ? Qtrue : Qfalse;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
VALUE
|
|
76
|
+
senko_native_numeric_gte(VALUE self, VALUE left, VALUE right)
|
|
77
|
+
{
|
|
78
|
+
ID id_gte = rb_intern2(">=", 2);
|
|
79
|
+
return RTEST(rb_funcall(left, id_gte, 1, right)) ? Qtrue : Qfalse;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
VALUE
|
|
83
|
+
senko_native_numeric_gt(VALUE self, VALUE left, VALUE right)
|
|
84
|
+
{
|
|
85
|
+
ID id_gt = rb_intern2(">", 1);
|
|
86
|
+
return RTEST(rb_funcall(left, id_gt, 1, right)) ? Qtrue : Qfalse;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
VALUE
|
|
90
|
+
senko_native_unique_array_p(VALUE self, VALUE array)
|
|
91
|
+
{
|
|
92
|
+
long i, j, length;
|
|
93
|
+
ID id_eq;
|
|
94
|
+
|
|
95
|
+
Check_Type(array, T_ARRAY);
|
|
96
|
+
length = RARRAY_LEN(array);
|
|
97
|
+
id_eq = rb_intern2("==", 2);
|
|
98
|
+
|
|
99
|
+
for (i = 0; i < length; i++) {
|
|
100
|
+
for (j = i + 1; j < length; j++) {
|
|
101
|
+
if (RTEST(rb_funcall(rb_ary_entry(array, i), id_eq, 1, rb_ary_entry(array, j))))
|
|
102
|
+
return Qfalse;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return Qtrue;
|
|
107
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#ifndef SENKO_VALIDATOR_H
|
|
2
|
+
#define SENKO_VALIDATOR_H 1
|
|
3
|
+
|
|
4
|
+
#include "ruby.h"
|
|
5
|
+
|
|
6
|
+
VALUE senko_native_type_mask(VALUE self, VALUE value);
|
|
7
|
+
VALUE senko_native_string_length(VALUE self, VALUE value);
|
|
8
|
+
VALUE senko_native_numeric_lte(VALUE self, VALUE left, VALUE right);
|
|
9
|
+
VALUE senko_native_numeric_lt(VALUE self, VALUE left, VALUE right);
|
|
10
|
+
VALUE senko_native_numeric_gte(VALUE self, VALUE left, VALUE right);
|
|
11
|
+
VALUE senko_native_numeric_gt(VALUE self, VALUE left, VALUE right);
|
|
12
|
+
VALUE senko_native_unique_array_p(VALUE self, VALUE array);
|
|
13
|
+
|
|
14
|
+
#endif
|
data/lib/senko/cache.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Senko
|
|
4
|
+
class Cache
|
|
5
|
+
def initialize(max_size: 256)
|
|
6
|
+
@store = {}
|
|
7
|
+
@max_size = max_size
|
|
8
|
+
@access_order = []
|
|
9
|
+
@mutex = Mutex.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def get(key)
|
|
13
|
+
@mutex.synchronize do
|
|
14
|
+
value = @store[key]
|
|
15
|
+
touch(key) if value
|
|
16
|
+
value
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def put(key, value)
|
|
21
|
+
@mutex.synchronize do
|
|
22
|
+
evict_lru if @store.size >= @max_size && !@store.key?(key)
|
|
23
|
+
@store[key] = value
|
|
24
|
+
touch(key)
|
|
25
|
+
end
|
|
26
|
+
value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def fetch(key)
|
|
30
|
+
cached = get(key)
|
|
31
|
+
return cached if cached
|
|
32
|
+
|
|
33
|
+
put(key, yield)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def clear
|
|
37
|
+
@mutex.synchronize do
|
|
38
|
+
@store.clear
|
|
39
|
+
@access_order.clear
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def touch(key)
|
|
46
|
+
@access_order.delete(key)
|
|
47
|
+
@access_order << key
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def evict_lru(count = [@max_size / 4, 1].max)
|
|
51
|
+
count.times do
|
|
52
|
+
key = @access_order.shift
|
|
53
|
+
@store.delete(key) if key
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|