json_skooma 0.2.5 → 0.2.7
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/CHANGELOG.md +18 -1
- data/README.md +38 -0
- data/lib/json_skooma/formatters.rb +56 -0
- data/lib/json_skooma/keywords/value_schemas.rb +13 -3
- data/lib/json_skooma/registry.rb +14 -3
- data/lib/json_skooma/version.rb +1 -1
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc94f3d758977e845fe5b92b683c57da06d23af3144cee65cd573679b0992b56
|
|
4
|
+
data.tar.gz: 1b9209442642b9bbe4170a400d164eae8ab4f58c4857ebd15c9a79a4532837f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 162215474dabeade31c53db14198eadb391f076b8305565c7f504ddbfe8cc3ea56654f7fdc0bbd4dca488f1adb814914b21e7d5780804d5d54efc4f11b66c8d3
|
|
7
|
+
data.tar.gz: f9158392915e5ea3985ad19f2df4e94ce3988b627f44b57cdbc3eb8b6a2dc01e475c40275e648baf6c64bb27168b292415ef663632f0becd6389f6c37e917863
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning].
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.7] - 2026-06-10
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Include the JSON Schema metaschema data files in the packaged gem. 0.2.6 was built without the `data/draft-*` submodules checked out, so `create_registry` crashed with `Sources::Error` on the released gem. ([@skryukov])
|
|
15
|
+
|
|
16
|
+
## [0.2.6] - 2026-06-10 [YANKED]
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- New `:annotated` output format interleaves the instance data with annotations collected during evaluation (`title`/`description` by default, configurable via `keywords:`). Each node becomes `{"title" => ..., "value" => ...}`, mirroring the shape of the data — handy for rendering values alongside their schema-defined labels. ([@skryukov], [#13](https://github.com/skryukov/json_skooma/issues/13))
|
|
21
|
+
- `JSONSkooma::UnexpectedSchemaClassError < RegistryError` is now raised by `Registry#schema` when the resolved fragment does not match the expected class. Extensions can rescue this specific error instead of matching on message text. ([@skryukov])
|
|
22
|
+
- `Registry#load_json(uri)` is now public so extensions can load a raw document via registered sources. ([@skryukov])
|
|
23
|
+
- `Keywords::ValueSchemas.default_schema_class=` lets extensions set the fallback class used to wrap sub-schemas when a keyword does not specify its own `schema_value_class`. ([@skryukov])
|
|
24
|
+
|
|
10
25
|
## [0.2.5] - 2024-12-25
|
|
11
26
|
|
|
12
27
|
### Added
|
|
@@ -63,7 +78,9 @@ and this project adheres to [Semantic Versioning].
|
|
|
63
78
|
[@killondark]: https://github.com/killondark
|
|
64
79
|
[@skryukov]: https://github.com/skryukov
|
|
65
80
|
|
|
66
|
-
[Unreleased]: https://github.com/skryukov/json_skooma/compare/v0.2.
|
|
81
|
+
[Unreleased]: https://github.com/skryukov/json_skooma/compare/v0.2.7...HEAD
|
|
82
|
+
[0.2.7]: https://github.com/skryukov/json_skooma/compare/v0.2.6...v0.2.7
|
|
83
|
+
[0.2.6]: https://github.com/skryukov/json_skooma/compare/v0.2.5...v0.2.6
|
|
67
84
|
[0.2.5]: https://github.com/skryukov/json_skooma/compare/v0.2.4...v0.2.5
|
|
68
85
|
[0.2.4]: https://github.com/skryukov/json_skooma/compare/v0.2.3...v0.2.4
|
|
69
86
|
[0.2.3]: https://github.com/skryukov/json_skooma/compare/v0.2.2...v0.2.3
|
data/README.md
CHANGED
|
@@ -156,6 +156,44 @@ schema_registry.add_source(
|
|
|
156
156
|
# - http://remote.example/product_definition.yaml -> http://example.com/schemas/product_definition.yaml
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
+
### Extracting annotations
|
|
160
|
+
|
|
161
|
+
The `:annotated` output format re-shapes collected annotations into a hash that mirrors your data: every node becomes a hash of its annotations plus a `"value"` key holding the original value. Useful for rendering data alongside the `title`/`description` texts defined in the schema.
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
schema = JSONSkooma::JSONSchema.new({
|
|
165
|
+
"$schema" => "https://json-schema.org/draft/2020-12/schema",
|
|
166
|
+
"type" => "object",
|
|
167
|
+
"properties" => {
|
|
168
|
+
"user_id" => {
|
|
169
|
+
"type" => "integer",
|
|
170
|
+
"title" => "User Identifier",
|
|
171
|
+
"description" => "A unique numeric ID for the user."
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
result = schema.evaluate({"user_id" => 123})
|
|
177
|
+
|
|
178
|
+
result.output(:annotated)
|
|
179
|
+
# {"user_id"=>
|
|
180
|
+
# {"title"=>"User Identifier",
|
|
181
|
+
# "description"=>"A unique numeric ID for the user.",
|
|
182
|
+
# "value"=>123}}
|
|
183
|
+
|
|
184
|
+
# Pick which annotation keywords to include (default: title and description):
|
|
185
|
+
result.output(:annotated, keywords: %w[title description default deprecated])
|
|
186
|
+
|
|
187
|
+
# Rename the wrapper key:
|
|
188
|
+
result.output(:annotated, value_key: "data")
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Notes:
|
|
192
|
+
|
|
193
|
+
- Annotations contributed through `$ref`/`allOf` are merged into the same location; if several subschemas annotate the same keyword at the same location, the last one wins.
|
|
194
|
+
- Annotations from failed subschemas (e.g. a non-matching `anyOf` branch) are dropped, as the spec prescribes.
|
|
195
|
+
- The root node is returned unwrapped to keep the output data-shaped, so annotations on the root schema itself are not included.
|
|
196
|
+
|
|
159
197
|
## Alternatives
|
|
160
198
|
|
|
161
199
|
- [json_schemer](https://github.com/davishmcclurg/json_schemer) – Draft 4, 6, 7, 2019-09 and 2020-12 compliant
|
|
@@ -133,5 +133,61 @@ module JSONSkooma
|
|
|
133
133
|
end
|
|
134
134
|
end
|
|
135
135
|
register :verbose, Verbose
|
|
136
|
+
|
|
137
|
+
# Re-shapes collected annotations into a hash that mirrors the instance
|
|
138
|
+
# data: every node (except the root) becomes a hash of its annotations
|
|
139
|
+
# plus a "value" key holding the original value (with nested nodes wrapped
|
|
140
|
+
# the same way). Annotations contributed through $ref/allOf land on the
|
|
141
|
+
# same instance location, so they merge naturally; annotations from failed
|
|
142
|
+
# subschemas are dropped, per the JSON Schema spec.
|
|
143
|
+
#
|
|
144
|
+
# result.output(:annotated)
|
|
145
|
+
# # => {"user_id" => {"title" => "User Identifier", "value" => 123}, ...}
|
|
146
|
+
#
|
|
147
|
+
# Options:
|
|
148
|
+
# keywords: list of annotation keywords to include (default: title, description)
|
|
149
|
+
# value_key: key under which the original value is placed (default: "value")
|
|
150
|
+
module Annotated
|
|
151
|
+
DEFAULT_KEYWORDS = %w[title description].freeze
|
|
152
|
+
|
|
153
|
+
class << self
|
|
154
|
+
def call(result, keywords: DEFAULT_KEYWORDS, value_key: "value", **_options)
|
|
155
|
+
annotations = {}
|
|
156
|
+
collect(result, keywords.map(&:to_s), annotations)
|
|
157
|
+
represent(result.instance, annotations, value_key, root: true)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def collect(node, keywords, annotations)
|
|
163
|
+
return unless node.valid?
|
|
164
|
+
|
|
165
|
+
if node.annotation && keywords.include?(node.key)
|
|
166
|
+
annotation = node.annotation
|
|
167
|
+
annotation = annotation.value if annotation.is_a?(JSONNode)
|
|
168
|
+
(annotations[node.instance.path.to_s] ||= {})[node.key] = annotation
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
node.each_children { |child| collect(child, keywords, annotations) }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def represent(instance, annotations, value_key, root: false)
|
|
175
|
+
value =
|
|
176
|
+
case instance.type
|
|
177
|
+
when "object"
|
|
178
|
+
instance.transform_values { |child| represent(child, annotations, value_key) }
|
|
179
|
+
when "array"
|
|
180
|
+
instance.map { |child| represent(child, annotations, value_key) }
|
|
181
|
+
else
|
|
182
|
+
instance.value
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
return value if root
|
|
186
|
+
|
|
187
|
+
(annotations[instance.path.to_s] || {}).merge(value_key => value)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
register :annotated, Annotated
|
|
136
192
|
end
|
|
137
193
|
end
|
|
@@ -4,6 +4,8 @@ module JSONSkooma
|
|
|
4
4
|
module Keywords
|
|
5
5
|
module ValueSchemas
|
|
6
6
|
class << self
|
|
7
|
+
attr_writer :default_schema_class
|
|
8
|
+
|
|
7
9
|
def [](key)
|
|
8
10
|
value_schemas&.[](key) or raise "Unknown value schema: #{key}, known schemas: #{value_schemas.keys.inspect}"
|
|
9
11
|
end
|
|
@@ -12,6 +14,14 @@ module JSONSkooma
|
|
|
12
14
|
(self.value_schemas ||= {})[key] = klass
|
|
13
15
|
end
|
|
14
16
|
|
|
17
|
+
# Class used to wrap schema values when a keyword does not set its own
|
|
18
|
+
# `schema_value_class`. Extensions (e.g. Skooma) override this to plug
|
|
19
|
+
# their own JSONSchema subclass into every sub-schema created by the
|
|
20
|
+
# built-in applicator keywords.
|
|
21
|
+
def default_schema_class
|
|
22
|
+
@default_schema_class || JSONSchema
|
|
23
|
+
end
|
|
24
|
+
|
|
15
25
|
private
|
|
16
26
|
|
|
17
27
|
attr_accessor :value_schemas
|
|
@@ -21,7 +31,7 @@ module JSONSkooma
|
|
|
21
31
|
def wrap_value(value)
|
|
22
32
|
return super unless value.is_a?(Hash) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
23
33
|
|
|
24
|
-
(self.class.schema_value_class ||
|
|
34
|
+
(self.class.schema_value_class || ValueSchemas.default_schema_class).new(
|
|
25
35
|
value,
|
|
26
36
|
parent: parent_schema,
|
|
27
37
|
key: key,
|
|
@@ -46,7 +56,7 @@ module JSONSkooma
|
|
|
46
56
|
value,
|
|
47
57
|
parent: parent_schema,
|
|
48
58
|
key: key,
|
|
49
|
-
item_class: self.class.schema_value_class ||
|
|
59
|
+
item_class: self.class.schema_value_class || ValueSchemas.default_schema_class,
|
|
50
60
|
registry: parent_schema.registry,
|
|
51
61
|
cache_id: parent_schema.cache_id
|
|
52
62
|
)
|
|
@@ -69,7 +79,7 @@ module JSONSkooma
|
|
|
69
79
|
value,
|
|
70
80
|
parent: parent_schema,
|
|
71
81
|
key: key,
|
|
72
|
-
item_class: self.class.schema_value_class ||
|
|
82
|
+
item_class: self.class.schema_value_class || ValueSchemas.default_schema_class,
|
|
73
83
|
registry: parent_schema.registry,
|
|
74
84
|
cache_id: parent_schema.cache_id
|
|
75
85
|
)
|
data/lib/json_skooma/registry.rb
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
module JSONSkooma
|
|
4
4
|
class RegistryError < Error; end
|
|
5
5
|
|
|
6
|
+
# Raised when a ref resolves to an object that is not the class the caller expected.
|
|
7
|
+
# Extension points (e.g. typed OpenAPI refs) can rescue this and load the subtree
|
|
8
|
+
# as the right class without matching on error message text.
|
|
9
|
+
class UnexpectedSchemaClassError < RegistryError
|
|
10
|
+
attr_reader :uri, :expected_class
|
|
11
|
+
|
|
12
|
+
def initialize(uri, expected_class)
|
|
13
|
+
@uri = uri
|
|
14
|
+
@expected_class = expected_class
|
|
15
|
+
super("The object referenced by #{uri} is not #{expected_class}")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
6
19
|
class Registry
|
|
7
20
|
class << self
|
|
8
21
|
attr_accessor :registries
|
|
@@ -79,7 +92,7 @@ module JSONSkooma
|
|
|
79
92
|
schema = JSONPointer.new(uri.fragment).eval(schema) if uri.fragment
|
|
80
93
|
return schema if schema.is_a?(expected_class)
|
|
81
94
|
|
|
82
|
-
raise
|
|
95
|
+
raise UnexpectedSchemaClassError.new(uri, expected_class)
|
|
83
96
|
end
|
|
84
97
|
|
|
85
98
|
def add_metaschema(uri, default_core_vocabulary_uri = nil, *default_vocabulary_uris)
|
|
@@ -113,8 +126,6 @@ module JSONSkooma
|
|
|
113
126
|
@uri_sources[uri] = source
|
|
114
127
|
end
|
|
115
128
|
|
|
116
|
-
private
|
|
117
|
-
|
|
118
129
|
def load_json(uri)
|
|
119
130
|
candidates = @uri_sources
|
|
120
131
|
.select { |source_uri| uri.to_s.start_with?(source_uri) }
|
data/lib/json_skooma/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: json_skooma
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Svyatoslav Kryukov
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: bigdecimal
|
|
@@ -236,7 +235,6 @@ metadata:
|
|
|
236
235
|
homepage_uri: https://github.com/skryukov/json_skooma
|
|
237
236
|
source_code_uri: https://github.com/skryukov/json_skooma
|
|
238
237
|
rubygems_mfa_required: 'true'
|
|
239
|
-
post_install_message:
|
|
240
238
|
rdoc_options: []
|
|
241
239
|
require_paths:
|
|
242
240
|
- lib
|
|
@@ -251,8 +249,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
251
249
|
- !ruby/object:Gem::Version
|
|
252
250
|
version: '0'
|
|
253
251
|
requirements: []
|
|
254
|
-
rubygems_version:
|
|
255
|
-
signing_key:
|
|
252
|
+
rubygems_version: 4.0.6
|
|
256
253
|
specification_version: 4
|
|
257
254
|
summary: I bring some sugar for your JSONs.
|
|
258
255
|
test_files: []
|