jsi 0.2.0 → 0.6.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 +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +36 -0
- data/LICENSE.md +613 -0
- data/README.md +153 -52
- data/lib/jsi/base.rb +485 -338
- data/lib/jsi/jsi_coder.rb +24 -18
- data/lib/jsi/metaschema.rb +7 -0
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +100 -0
- data/lib/jsi/metaschema_node.rb +245 -0
- data/lib/jsi/pathed_node.rb +49 -46
- data/lib/jsi/ptr.rb +292 -0
- data/lib/jsi/schema/application/child_application/contains.rb +16 -0
- data/lib/jsi/schema/application/child_application/draft04.rb +22 -0
- data/lib/jsi/schema/application/child_application/draft06.rb +29 -0
- data/lib/jsi/schema/application/child_application/draft07.rb +29 -0
- data/lib/jsi/schema/application/child_application/items.rb +18 -0
- data/lib/jsi/schema/application/child_application/properties.rb +25 -0
- data/lib/jsi/schema/application/child_application.rb +40 -0
- data/lib/jsi/schema/application/draft04.rb +8 -0
- data/lib/jsi/schema/application/draft06.rb +8 -0
- data/lib/jsi/schema/application/draft07.rb +8 -0
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
- data/lib/jsi/schema/application/inplace_application/draft04.rb +26 -0
- data/lib/jsi/schema/application/inplace_application/draft06.rb +27 -0
- data/lib/jsi/schema/application/inplace_application/draft07.rb +33 -0
- data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
- data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
- data/lib/jsi/schema/application/inplace_application/someof.rb +29 -0
- data/lib/jsi/schema/application/inplace_application.rb +46 -0
- data/lib/jsi/schema/application.rb +12 -0
- data/lib/jsi/schema/draft04.rb +14 -0
- data/lib/jsi/schema/draft06.rb +14 -0
- data/lib/jsi/schema/draft07.rb +14 -0
- data/lib/jsi/schema/issue.rb +36 -0
- data/lib/jsi/schema/ref.rb +159 -0
- data/lib/jsi/schema/schema_ancestor_node.rb +119 -0
- data/lib/jsi/schema/validation/array.rb +69 -0
- data/lib/jsi/schema/validation/const.rb +20 -0
- data/lib/jsi/schema/validation/contains.rb +25 -0
- data/lib/jsi/schema/validation/core.rb +39 -0
- data/lib/jsi/schema/validation/dependencies.rb +49 -0
- data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
- data/lib/jsi/schema/validation/draft04.rb +112 -0
- data/lib/jsi/schema/validation/draft06.rb +122 -0
- data/lib/jsi/schema/validation/draft07.rb +159 -0
- data/lib/jsi/schema/validation/enum.rb +25 -0
- data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
- data/lib/jsi/schema/validation/items.rb +54 -0
- data/lib/jsi/schema/validation/not.rb +20 -0
- data/lib/jsi/schema/validation/numeric.rb +121 -0
- data/lib/jsi/schema/validation/object.rb +45 -0
- data/lib/jsi/schema/validation/pattern.rb +34 -0
- data/lib/jsi/schema/validation/properties.rb +101 -0
- data/lib/jsi/schema/validation/property_names.rb +32 -0
- data/lib/jsi/schema/validation/ref.rb +40 -0
- data/lib/jsi/schema/validation/required.rb +27 -0
- data/lib/jsi/schema/validation/someof.rb +90 -0
- data/lib/jsi/schema/validation/string.rb +47 -0
- data/lib/jsi/schema/validation/type.rb +49 -0
- data/lib/jsi/schema/validation.rb +51 -0
- data/lib/jsi/schema.rb +528 -233
- data/lib/jsi/schema_classes.rb +238 -51
- data/lib/jsi/schema_registry.rb +141 -0
- data/lib/jsi/schema_set.rb +141 -0
- data/lib/jsi/simple_wrap.rb +8 -3
- data/lib/jsi/typelike_modules.rb +75 -68
- data/lib/jsi/util/attr_struct.rb +106 -0
- data/lib/jsi/util.rb +167 -64
- data/lib/jsi/validation/error.rb +34 -0
- data/lib/jsi/validation/result.rb +210 -0
- data/lib/jsi/validation.rb +15 -0
- data/lib/jsi/version.rb +3 -1
- data/lib/jsi.rb +72 -9
- data/lib/schemas/json-schema.org/draft-04/schema.rb +12 -0
- data/lib/schemas/json-schema.org/draft-06/schema.rb +12 -0
- data/lib/schemas/json-schema.org/draft-07/schema.rb +12 -0
- data/readme.rb +138 -0
- data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
- data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
- data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
- metadata +80 -107
- data/.simplecov +0 -1
- data/LICENSE.txt +0 -21
- data/Rakefile.rb +0 -9
- data/jsi.gemspec +0 -31
- data/lib/jsi/base/to_rb.rb +0 -126
- data/lib/jsi/json/node.rb +0 -243
- data/lib/jsi/json/pointer.rb +0 -330
- data/lib/jsi/json-schema-fragments.rb +0 -59
- data/lib/jsi/json.rb +0 -8
- data/test/base_array_test.rb +0 -209
- data/test/base_hash_test.rb +0 -204
- data/test/base_test.rb +0 -422
- data/test/jsi_coder_test.rb +0 -85
- data/test/jsi_json_arraynode_test.rb +0 -150
- data/test/jsi_json_hashnode_test.rb +0 -132
- data/test/jsi_json_node_test.rb +0 -310
- data/test/jsi_json_pointer_test.rb +0 -106
- data/test/jsi_test.rb +0 -11
- data/test/jsi_typelike_as_json_test.rb +0 -53
- data/test/schema_test.rb +0 -196
- data/test/spreedly_openapi_test.rb +0 -8
- data/test/test_helper.rb +0 -63
- data/test/util_test.rb +0 -62
data/lib/jsi/jsi_coder.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module JSI
|
|
2
4
|
# this is an ActiveRecord serialization coder intended to serialize between
|
|
3
5
|
# JSON-compatible objects on the database side, and a JSI instance loaded on
|
|
@@ -9,34 +11,37 @@ module JSI
|
|
|
9
11
|
# `preferences_json` which is an actual json column, and `preferences_txt`
|
|
10
12
|
# which is a string:
|
|
11
13
|
#
|
|
12
|
-
# Preferences = JSI.
|
|
14
|
+
# Preferences = JSI.new_schema_module(preferences_json_schema)
|
|
13
15
|
# class Foo < ActiveRecord::Base
|
|
14
16
|
# # as a single serializer, loads a Preferences instance from a json column
|
|
15
|
-
# serialize '
|
|
17
|
+
# serialize 'preferences_json', JSI::JSICoder.new(Preferences)
|
|
16
18
|
#
|
|
17
19
|
# # for a text column, arms_serialize will go from JSI to JSON-compatible
|
|
18
20
|
# # objects to a string. the symbol `:jsi` is a shortcut for JSI::JSICoder.
|
|
19
|
-
# arms_serialize '
|
|
21
|
+
# arms_serialize 'preferences_txt', [:jsi, Preferences], :json
|
|
20
22
|
# end
|
|
21
23
|
#
|
|
22
|
-
# the column data may be either a single instance of the
|
|
24
|
+
# the column data may be either a single instance of the schema class
|
|
23
25
|
# (represented as one json object) or an array of them (represented as a json
|
|
24
26
|
# array of json objects), indicated by the keyword argument `array`.
|
|
25
27
|
class JSICoder
|
|
26
|
-
# @param
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# an array
|
|
30
|
-
def initialize(
|
|
31
|
-
|
|
28
|
+
# @param schema [#new_jsi] a Schema, SchemaSet, or JSI schema module. #load
|
|
29
|
+
# will instantiate column data using the JSI schemas represented.
|
|
30
|
+
# @param array [Boolean] whether the dumped data represent one instance of the schema,
|
|
31
|
+
# or an array of them. note that it may be preferable to simply use an array schema.
|
|
32
|
+
def initialize(schema, array: false)
|
|
33
|
+
unless schema.respond_to?(:new_jsi)
|
|
34
|
+
raise(ArgumentError, "schema param does not respond to #new_jsi: #{schema.inspect}")
|
|
35
|
+
end
|
|
36
|
+
@schema = schema
|
|
32
37
|
@array = array
|
|
33
38
|
end
|
|
34
39
|
|
|
35
|
-
# loads the database column to instances of
|
|
40
|
+
# loads the database column to JSI instances of our schema
|
|
36
41
|
#
|
|
37
42
|
# @param data [Object, Array, nil] the dumped schema instance(s) of the JSI(s)
|
|
38
|
-
# @return [
|
|
39
|
-
#
|
|
43
|
+
# @return [JSI::Base, Array<JSI::Base>, nil] the JSI or JSIs containing the schema
|
|
44
|
+
# instance(s), or nil if data is nil
|
|
40
45
|
def load(data)
|
|
41
46
|
return nil if data.nil?
|
|
42
47
|
object = if @array
|
|
@@ -50,8 +55,9 @@ module JSI
|
|
|
50
55
|
object
|
|
51
56
|
end
|
|
52
57
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
58
|
+
# dumps the object for the database
|
|
59
|
+
# @param object [JSI::Base, Array<JSI::Base>, nil] the JSI or array of JSIs containing
|
|
60
|
+
# the schema instance(s)
|
|
55
61
|
# @return [Object, Array, nil] the schema instance(s) of the JSI(s), or nil if object is nil
|
|
56
62
|
def dump(object)
|
|
57
63
|
return nil if object.nil?
|
|
@@ -72,12 +78,12 @@ module JSI
|
|
|
72
78
|
|
|
73
79
|
private
|
|
74
80
|
# @param data [Object]
|
|
75
|
-
# @return [
|
|
81
|
+
# @return [JSI::Base]
|
|
76
82
|
def load_object(data)
|
|
77
|
-
@
|
|
83
|
+
@schema.new_jsi(data)
|
|
78
84
|
end
|
|
79
85
|
|
|
80
|
-
# @param object [
|
|
86
|
+
# @param object [JSI::Base, Object]
|
|
81
87
|
# @return [Object]
|
|
82
88
|
def dump_object(object)
|
|
83
89
|
JSI::Typelike.as_json(object)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
# @private
|
|
5
|
+
# internal class to bootstrap a metaschema. represents a schema without the complexity of JSI::Base. the
|
|
6
|
+
# schema is represented but schemas describing the schema are not.
|
|
7
|
+
#
|
|
8
|
+
# this class is to only be instantiated on nodes in the document that are known to be schemas.
|
|
9
|
+
# Schema#subschema and Schema#resource_root_subschema are the intended mechanisms to instantiate subschemas
|
|
10
|
+
# and resolve references. #[] and #jsi_root_node are not implemented.
|
|
11
|
+
#
|
|
12
|
+
# metaschema instance modules are attached to generated subclasses of BootstrapSchema by
|
|
13
|
+
# {SchemaClasses.bootstrap_schema_class}. that subclass is instantiated with a document and
|
|
14
|
+
# pointer, representing a schema.
|
|
15
|
+
class MetaschemaNode::BootstrapSchema
|
|
16
|
+
include Util::Memoize
|
|
17
|
+
include Util::FingerprintHash
|
|
18
|
+
include Schema::SchemaAncestorNode
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
def inspect
|
|
22
|
+
if self == MetaschemaNode::BootstrapSchema
|
|
23
|
+
name
|
|
24
|
+
else
|
|
25
|
+
"#{name || MetaschemaNode::BootstrapSchema.name} (#{metaschema_instance_modules.map(&:inspect).join(', ')})"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
alias_method :to_s, :inspect
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @param jsi_ptr [JSI::Ptr] pointer to the schema in the document
|
|
33
|
+
# @param jsi_document [#to_hash, #to_ary, Boolean, Object] document containing the schema
|
|
34
|
+
def initialize(
|
|
35
|
+
jsi_document,
|
|
36
|
+
jsi_ptr: Ptr[],
|
|
37
|
+
jsi_schema_base_uri: nil
|
|
38
|
+
)
|
|
39
|
+
unless respond_to?(:metaschema_instance_modules)
|
|
40
|
+
raise(TypeError, "cannot instantiate #{self.class.inspect} which has no method #metaschema_instance_modules")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
jsi_initialize_memos
|
|
44
|
+
|
|
45
|
+
self.jsi_ptr = jsi_ptr
|
|
46
|
+
self.jsi_document = jsi_document
|
|
47
|
+
self.jsi_schema_base_uri = jsi_schema_base_uri
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# document containing the schema content
|
|
51
|
+
attr_reader :jsi_document
|
|
52
|
+
|
|
53
|
+
# JSI::Ptr pointing to this schema within the document
|
|
54
|
+
attr_reader :jsi_ptr
|
|
55
|
+
|
|
56
|
+
def jsi_node_content
|
|
57
|
+
jsi_ptr.evaluate(jsi_document)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [String]
|
|
61
|
+
def inspect
|
|
62
|
+
"\#<#{jsi_object_group_text.join(' ')} #{schema_content.inspect}>"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# pretty-prints a representation of self to the given printer
|
|
66
|
+
# @return [void]
|
|
67
|
+
def pretty_print(q)
|
|
68
|
+
q.text '#<'
|
|
69
|
+
q.text jsi_object_group_text.join(' ')
|
|
70
|
+
q.group_sub {
|
|
71
|
+
q.nest(2) {
|
|
72
|
+
q.breakable ' '
|
|
73
|
+
q.pp schema_content
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
q.breakable ''
|
|
77
|
+
q.text '>'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @private
|
|
81
|
+
# @return [Array<String>]
|
|
82
|
+
def jsi_object_group_text
|
|
83
|
+
[
|
|
84
|
+
self.class.name || MetaschemaNode::BootstrapSchema.name,
|
|
85
|
+
"(#{metaschema_instance_modules.map(&:inspect).join(', ')})",
|
|
86
|
+
jsi_ptr.uri,
|
|
87
|
+
]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @private
|
|
91
|
+
def jsi_fingerprint
|
|
92
|
+
{
|
|
93
|
+
class: self.class,
|
|
94
|
+
jsi_ptr: @jsi_ptr,
|
|
95
|
+
jsi_document: @jsi_document,
|
|
96
|
+
metaschema_instance_modules: metaschema_instance_modules,
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
# a MetaschemaNode is a JSI instance representing a node in a document which contains a metaschema.
|
|
5
|
+
# the root of the metaschema is pointed to by metaschema_root_ptr.
|
|
6
|
+
# the schema describing the root of the document is pointed to by root_schema_ptr.
|
|
7
|
+
#
|
|
8
|
+
# like JSI::Base's normal subclasses, this class represents an instance of a schema set, an instance
|
|
9
|
+
# which may itself be a schema. unlike JSI::Base, the document containing the instance and its schemas
|
|
10
|
+
# is the same, and a schema (the metaschema) may be an instance of itself.
|
|
11
|
+
#
|
|
12
|
+
# unlike JSI::Base's normal subclasses, the schemas describing the instance are not part of the class.
|
|
13
|
+
# since the metaschema describes itself, attempting to construct a class from the JSI Schema Module of a
|
|
14
|
+
# schema which is itself an instance of that class results in a causality loop.
|
|
15
|
+
# instead, a MetaschemaNode calculates its {#jsi_schemas} and extends itself with their JSI Schema
|
|
16
|
+
# modules during initialization.
|
|
17
|
+
# the MetaschemaNode of the metaschema is extended with its own JSI Schema Module.
|
|
18
|
+
#
|
|
19
|
+
# if the MetaschemaNode's schemas include its self, it is extended with JSI::Metaschema.
|
|
20
|
+
#
|
|
21
|
+
# a MetaschemaNode is extended with JSI::Schema when it represents a schema - this is the case when
|
|
22
|
+
# the metaschema is one of its schemas.
|
|
23
|
+
class MetaschemaNode < Base
|
|
24
|
+
autoload :BootstrapSchema, 'jsi/metaschema_node/bootstrap_schema'
|
|
25
|
+
|
|
26
|
+
# @param jsi_document the document containing the metaschema
|
|
27
|
+
# @param jsi_ptr [JSI::Ptr] ptr to this MetaschemaNode in jsi_document
|
|
28
|
+
# @param metaschema_instance_modules [Enumerable<Module>] modules which implement the functionality of the
|
|
29
|
+
# schema, to be applied to every schema which is an instance of the metaschema. this must include
|
|
30
|
+
# JSI::Schema directly or indirectly. these are the {Schema#jsi_schema_instance_modules} of the
|
|
31
|
+
# metaschema.
|
|
32
|
+
# @param metaschema_root_ptr [JSI::Ptr] ptr to the root of the metaschema in the jsi_document
|
|
33
|
+
# @param root_schema_ptr [JSI::Ptr] ptr to the schema describing the root of the jsi_document
|
|
34
|
+
def initialize(
|
|
35
|
+
jsi_document,
|
|
36
|
+
jsi_ptr: Ptr[],
|
|
37
|
+
metaschema_instance_modules: ,
|
|
38
|
+
metaschema_root_ptr: Ptr[],
|
|
39
|
+
root_schema_ptr: Ptr[],
|
|
40
|
+
jsi_schema_base_uri: nil
|
|
41
|
+
)
|
|
42
|
+
jsi_initialize_memos
|
|
43
|
+
|
|
44
|
+
self.jsi_document = jsi_document
|
|
45
|
+
self.jsi_ptr = jsi_ptr
|
|
46
|
+
@metaschema_instance_modules = Util.ensure_module_set(metaschema_instance_modules)
|
|
47
|
+
@metaschema_root_ptr = metaschema_root_ptr
|
|
48
|
+
@root_schema_ptr = root_schema_ptr
|
|
49
|
+
|
|
50
|
+
if jsi_ptr.root? && jsi_schema_base_uri
|
|
51
|
+
raise(NotImplementedError, "unsupported jsi_schema_base_uri on metaschema document root")
|
|
52
|
+
end
|
|
53
|
+
self.jsi_schema_base_uri = jsi_schema_base_uri
|
|
54
|
+
|
|
55
|
+
jsi_node_content = self.jsi_node_content
|
|
56
|
+
|
|
57
|
+
if jsi_node_content.respond_to?(:to_hash)
|
|
58
|
+
extend PathedHashNode
|
|
59
|
+
end
|
|
60
|
+
if jsi_node_content.respond_to?(:to_ary)
|
|
61
|
+
extend PathedArrayNode
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
instance_for_schemas = jsi_document
|
|
65
|
+
bootstrap_schema_class = JSI::SchemaClasses.bootstrap_schema_class(metaschema_instance_modules)
|
|
66
|
+
root_bootstrap_schema = bootstrap_schema_class.new(
|
|
67
|
+
jsi_document,
|
|
68
|
+
jsi_ptr: root_schema_ptr,
|
|
69
|
+
jsi_schema_base_uri: nil, # supplying jsi_schema_base_uri on root bootstrap schema is not supported
|
|
70
|
+
)
|
|
71
|
+
our_bootstrap_schemas = jsi_ptr.tokens.inject(SchemaSet[root_bootstrap_schema]) do |bootstrap_schemas, tok|
|
|
72
|
+
child_instance_for_schemas = instance_for_schemas[tok]
|
|
73
|
+
bootstrap_schemas_for_instance = SchemaSet.build do |schemas|
|
|
74
|
+
bootstrap_schemas.each do |bootstrap_schema|
|
|
75
|
+
bootstrap_schema.each_child_applicator_schema(tok, instance_for_schemas) do |child_app_schema|
|
|
76
|
+
child_app_schema.each_inplace_applicator_schema(child_instance_for_schemas) do |child_inpl_app_schema|
|
|
77
|
+
schemas << child_inpl_app_schema
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
instance_for_schemas = child_instance_for_schemas
|
|
83
|
+
bootstrap_schemas_for_instance
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
our_bootstrap_schemas.each do |bootstrap_schema|
|
|
87
|
+
if bootstrap_schema.jsi_ptr == metaschema_root_ptr
|
|
88
|
+
metaschema_instance_modules.each do |metaschema_instance_module|
|
|
89
|
+
extend metaschema_instance_module
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
if bootstrap_schema.jsi_ptr == jsi_ptr
|
|
93
|
+
extend Metaschema
|
|
94
|
+
self.jsi_schema_instance_modules = metaschema_instance_modules
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
@jsi_schemas = SchemaSet.new(our_bootstrap_schemas) do |bootstrap_schema|
|
|
99
|
+
if bootstrap_schema.jsi_ptr == jsi_ptr
|
|
100
|
+
self
|
|
101
|
+
else
|
|
102
|
+
new_node(
|
|
103
|
+
jsi_ptr: bootstrap_schema.jsi_ptr,
|
|
104
|
+
jsi_schema_base_uri: bootstrap_schema.jsi_schema_base_uri,
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@jsi_schemas.each do |schema|
|
|
110
|
+
extend schema.jsi_schema_module
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# workarounds
|
|
114
|
+
begin # draft 4 boolean schema workaround
|
|
115
|
+
# in draft 4, boolean schemas are not described in the root, but on anyOf schemas on
|
|
116
|
+
# properties/additionalProperties and properties/additionalItems.
|
|
117
|
+
# since these describe schemas, their jsi_schema_instance_modules are the metaschema_instance_modules.
|
|
118
|
+
addtlPropsanyOf = metaschema_root_ptr["properties"]["additionalProperties"]["anyOf"]
|
|
119
|
+
addtlItemsanyOf = metaschema_root_ptr["properties"]["additionalItems"]["anyOf"]
|
|
120
|
+
|
|
121
|
+
if !jsi_ptr.root? && [addtlPropsanyOf, addtlItemsanyOf].include?(jsi_ptr.parent)
|
|
122
|
+
self.jsi_schema_instance_modules = metaschema_instance_modules
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Set of modules to apply to schemas which are instances of (described by) the metaschema
|
|
128
|
+
# @return [Set<Module>]
|
|
129
|
+
attr_reader :metaschema_instance_modules
|
|
130
|
+
|
|
131
|
+
# ptr to the root of the metaschema in the jsi_document
|
|
132
|
+
# @return [JSI::Ptr]
|
|
133
|
+
attr_reader :metaschema_root_ptr
|
|
134
|
+
|
|
135
|
+
# ptr to the schema of the root of the jsi_document
|
|
136
|
+
# @return [JSI::Ptr]
|
|
137
|
+
attr_reader :root_schema_ptr
|
|
138
|
+
|
|
139
|
+
# JSI Schemas describing this MetaschemaNode
|
|
140
|
+
# @return [JSI::SchemaSet]
|
|
141
|
+
attr_reader :jsi_schemas
|
|
142
|
+
|
|
143
|
+
# document root MetaschemaNode
|
|
144
|
+
# @return [MetaschemaNode]
|
|
145
|
+
def jsi_root_node
|
|
146
|
+
if jsi_ptr.root?
|
|
147
|
+
self
|
|
148
|
+
else
|
|
149
|
+
new_node(
|
|
150
|
+
jsi_ptr: Ptr[],
|
|
151
|
+
jsi_schema_base_uri: nil,
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# parent MetaschemaNode
|
|
157
|
+
# @return [MetaschemaNode]
|
|
158
|
+
def jsi_parent_node
|
|
159
|
+
jsi_ptr.parent.evaluate(jsi_root_node)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# subscripts to return a child value identified by the given token.
|
|
163
|
+
#
|
|
164
|
+
# @param token (see JSI::Base#[])
|
|
165
|
+
# @param as_jsi (see JSI::Base#[])
|
|
166
|
+
# @return (see JSI::Base#[])
|
|
167
|
+
def [](token, as_jsi: :auto)
|
|
168
|
+
if respond_to?(:to_hash)
|
|
169
|
+
token_in_range = jsi_node_content_hash_pubsend(:key?, token)
|
|
170
|
+
value = jsi_node_content_hash_pubsend(:[], token)
|
|
171
|
+
elsif respond_to?(:to_ary)
|
|
172
|
+
token_in_range = jsi_node_content_ary_pubsend(:each_index).include?(token)
|
|
173
|
+
value = jsi_node_content_ary_pubsend(:[], token)
|
|
174
|
+
else
|
|
175
|
+
raise(NoMethodError, "cannot subscript (using token: #{token.inspect}) from content: #{jsi_node_content.pretty_inspect.chomp}")
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
begin
|
|
179
|
+
if token_in_range
|
|
180
|
+
value_node = jsi_subinstance_memos[token]
|
|
181
|
+
|
|
182
|
+
jsi_subinstance_as_jsi(value, value_node.jsi_schemas, as_jsi) do
|
|
183
|
+
value_node
|
|
184
|
+
end
|
|
185
|
+
else
|
|
186
|
+
# I think I will not support Hash#default/#default_proc in this case.
|
|
187
|
+
nil
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# instantiates a new MetaschemaNode whose instance is a modified copy of this MetaschemaNode's instance
|
|
193
|
+
# @yield [Object] the node content of the instance. the block should result
|
|
194
|
+
# in a (nondestructively) modified copy of this.
|
|
195
|
+
# @return [MetaschemaNode] modified copy of self
|
|
196
|
+
def jsi_modified_copy(&block)
|
|
197
|
+
MetaschemaNode.new(jsi_ptr.modified_document_copy(jsi_document, &block), **our_initialize_params)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# @private
|
|
201
|
+
# @return [Array<String>]
|
|
202
|
+
def jsi_object_group_text
|
|
203
|
+
if jsi_schemas && jsi_schemas.any?
|
|
204
|
+
class_n_schemas = "#{self.class} (#{jsi_schemas.map { |s| s.jsi_ptr.uri }.join(' ')})"
|
|
205
|
+
else
|
|
206
|
+
class_n_schemas = self.class.to_s
|
|
207
|
+
end
|
|
208
|
+
[
|
|
209
|
+
class_n_schemas,
|
|
210
|
+
is_a?(Metaschema) ? "Metaschema" : is_a?(Schema) ? "Schema" : nil,
|
|
211
|
+
*(jsi_node_content.respond_to?(:jsi_object_group_text) ? jsi_node_content.jsi_object_group_text : []),
|
|
212
|
+
].compact
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# an opaque fingerprint of this MetaschemaNode for FingerprintHash
|
|
216
|
+
def jsi_fingerprint
|
|
217
|
+
{class: self.class, jsi_document: jsi_document}.merge(our_initialize_params)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
def our_initialize_params
|
|
223
|
+
{
|
|
224
|
+
jsi_ptr: jsi_ptr,
|
|
225
|
+
metaschema_instance_modules: metaschema_instance_modules,
|
|
226
|
+
metaschema_root_ptr: metaschema_root_ptr,
|
|
227
|
+
root_schema_ptr: root_schema_ptr,
|
|
228
|
+
jsi_schema_base_uri: jsi_schema_base_uri,
|
|
229
|
+
}
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def new_node(params)
|
|
233
|
+
MetaschemaNode.new(jsi_document, **our_initialize_params.merge(params))
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def jsi_subinstance_memos
|
|
237
|
+
jsi_memomap(:subinstance) do |token|
|
|
238
|
+
new_node(
|
|
239
|
+
jsi_ptr: jsi_ptr[token],
|
|
240
|
+
jsi_schema_base_uri: jsi_resource_ancestor_uri,
|
|
241
|
+
)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
data/lib/jsi/pathed_node.rb
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module JSI
|
|
2
|
-
#
|
|
3
|
-
# - #node_document [Object] returning the document
|
|
4
|
-
# - #node_ptr [JSI::JSON::Pointer] returning a pointer for the node path in the document
|
|
5
|
-
# - #document_root_node [JSI::PathedNode] returning a PathedNode pointing at the document root
|
|
6
|
-
# - #parent_node [JSI::PathedNode] returning the parent node of this PathedNode
|
|
7
|
-
# - #deref [JSI::PathedNode] following a $ref
|
|
4
|
+
# this module represents a node in a document.
|
|
8
5
|
#
|
|
9
|
-
#
|
|
6
|
+
# including class MUST define
|
|
10
7
|
#
|
|
11
|
-
#
|
|
8
|
+
# - `#jsi_document` [Object] the document
|
|
9
|
+
# - `#jsi_ptr` [JSI::Ptr] a pointer to the node in the document
|
|
12
10
|
module PathedNode
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
# the content of this node
|
|
12
|
+
def jsi_node_content
|
|
13
|
+
content = jsi_ptr.evaluate(jsi_document)
|
|
15
14
|
content
|
|
16
15
|
end
|
|
17
|
-
|
|
18
|
-
def node_ptr_deref(&block)
|
|
19
|
-
node_ptr.deref(node_document, &block)
|
|
20
|
-
end
|
|
21
16
|
end
|
|
22
17
|
|
|
23
|
-
# module extending a {JSI::PathedNode} object when its
|
|
18
|
+
# module extending a {JSI::PathedNode} object when its jsi_node_content is Hash-like (responds to #to_hash)
|
|
24
19
|
module PathedHashNode
|
|
25
20
|
# yields each hash key and value of this node.
|
|
26
21
|
#
|
|
@@ -29,41 +24,45 @@ module JSI
|
|
|
29
24
|
#
|
|
30
25
|
# returns an Enumerator if no block is given.
|
|
31
26
|
#
|
|
27
|
+
# @param a arguments are passed to `#[]`
|
|
32
28
|
# @yield [Object, Object] each key and value of this hash node
|
|
33
|
-
# @return [self, Enumerator]
|
|
34
|
-
def each(&block)
|
|
35
|
-
return to_enum(__method__) {
|
|
29
|
+
# @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
|
|
30
|
+
def each(*a, &block)
|
|
31
|
+
return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block
|
|
36
32
|
if block.arity > 1
|
|
37
|
-
|
|
33
|
+
jsi_node_content_hash_pubsend(:each_key) { |k| yield k, self[k, *a] }
|
|
38
34
|
else
|
|
39
|
-
|
|
35
|
+
jsi_node_content_hash_pubsend(:each_key) { |k| yield [k, self[k, *a]] }
|
|
40
36
|
end
|
|
41
37
|
self
|
|
42
38
|
end
|
|
43
39
|
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
# a hash in which each key is a key of the jsi_node_content hash and each value is the
|
|
41
|
+
# result of `self[key]`
|
|
42
|
+
# @param a arguments are passed to `#[]`
|
|
43
|
+
# @return [Hash]
|
|
44
|
+
def to_hash(*a)
|
|
45
|
+
{}.tap { |h| jsi_node_content_hash_pubsend(:each_key) { |k| h[k] = self[k, *a] } }
|
|
48
46
|
end
|
|
49
47
|
|
|
50
48
|
include Hashlike
|
|
51
49
|
|
|
50
|
+
# invokes the method with the given name on the jsi_node_content (if defined) or its #to_hash
|
|
52
51
|
# @param method_name [String, Symbol]
|
|
53
|
-
# @param
|
|
54
|
-
# @return [Object] the result of calling method method_name on the
|
|
55
|
-
def
|
|
56
|
-
if
|
|
57
|
-
|
|
52
|
+
# @param a arguments and block are passed to the invocation of method_name
|
|
53
|
+
# @return [Object] the result of calling method method_name on the jsi_node_content or its #to_hash
|
|
54
|
+
def jsi_node_content_hash_pubsend(method_name, *a, &b)
|
|
55
|
+
if jsi_node_content.respond_to?(method_name)
|
|
56
|
+
jsi_node_content.public_send(method_name, *a, &b)
|
|
58
57
|
else
|
|
59
|
-
|
|
58
|
+
jsi_node_content.to_hash.public_send(method_name, *a, &b)
|
|
60
59
|
end
|
|
61
60
|
end
|
|
62
61
|
|
|
63
62
|
# methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash)
|
|
64
63
|
SAFE_KEY_ONLY_METHODS.each do |method_name|
|
|
65
64
|
define_method(method_name) do |*a, &b|
|
|
66
|
-
|
|
65
|
+
jsi_node_content_hash_pubsend(method_name, *a, &b)
|
|
67
66
|
end
|
|
68
67
|
end
|
|
69
68
|
end
|
|
@@ -75,30 +74,34 @@ module JSI
|
|
|
75
74
|
#
|
|
76
75
|
# returns an Enumerator if no block is given.
|
|
77
76
|
#
|
|
77
|
+
# @param a arguments are passed to `#[]`
|
|
78
78
|
# @yield [Object] each element of this array node
|
|
79
|
-
# @return [self, Enumerator]
|
|
80
|
-
def each
|
|
81
|
-
return to_enum(__method__) {
|
|
82
|
-
|
|
79
|
+
# @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
|
|
80
|
+
def each(*a, &block)
|
|
81
|
+
return to_enum(__method__) { jsi_node_content_ary_pubsend(:size) } unless block
|
|
82
|
+
jsi_node_content_ary_pubsend(:each_index) { |i| yield(self[i, *a]) }
|
|
83
83
|
self
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
# an array, the same size as the jsi_node_content, in which the element at each index is the
|
|
87
|
+
# result of `self[index]`
|
|
88
|
+
# @param a arguments are passed to `#[]`
|
|
89
|
+
# @return [Array]
|
|
90
|
+
def to_ary(*a)
|
|
91
|
+
to_a(*a)
|
|
90
92
|
end
|
|
91
93
|
|
|
92
94
|
include Arraylike
|
|
93
95
|
|
|
96
|
+
# invokes the method with the given name on the jsi_node_content (if defined) or its #to_ary
|
|
94
97
|
# @param method_name [String, Symbol]
|
|
95
|
-
# @param
|
|
96
|
-
# @return [Object] the result of calling method method_name on the
|
|
97
|
-
def
|
|
98
|
-
if
|
|
99
|
-
|
|
98
|
+
# @param a arguments and block are passed to the invocation of method_name
|
|
99
|
+
# @return [Object] the result of calling method method_name on the jsi_node_content or its #to_ary
|
|
100
|
+
def jsi_node_content_ary_pubsend(method_name, *a, &b)
|
|
101
|
+
if jsi_node_content.respond_to?(method_name)
|
|
102
|
+
jsi_node_content.public_send(method_name, *a, &b)
|
|
100
103
|
else
|
|
101
|
-
|
|
104
|
+
jsi_node_content.to_ary.public_send(method_name, *a, &b)
|
|
102
105
|
end
|
|
103
106
|
end
|
|
104
107
|
|
|
@@ -106,7 +109,7 @@ module JSI
|
|
|
106
109
|
# we override these methods from Arraylike
|
|
107
110
|
SAFE_INDEX_ONLY_METHODS.each do |method_name|
|
|
108
111
|
define_method(method_name) do |*a, &b|
|
|
109
|
-
|
|
112
|
+
jsi_node_content_ary_pubsend(method_name, *a, &b)
|
|
110
113
|
end
|
|
111
114
|
end
|
|
112
115
|
end
|