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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +36 -0
  4. data/LICENSE.md +613 -0
  5. data/README.md +153 -52
  6. data/lib/jsi/base.rb +485 -338
  7. data/lib/jsi/jsi_coder.rb +24 -18
  8. data/lib/jsi/metaschema.rb +7 -0
  9. data/lib/jsi/metaschema_node/bootstrap_schema.rb +100 -0
  10. data/lib/jsi/metaschema_node.rb +245 -0
  11. data/lib/jsi/pathed_node.rb +49 -46
  12. data/lib/jsi/ptr.rb +292 -0
  13. data/lib/jsi/schema/application/child_application/contains.rb +16 -0
  14. data/lib/jsi/schema/application/child_application/draft04.rb +22 -0
  15. data/lib/jsi/schema/application/child_application/draft06.rb +29 -0
  16. data/lib/jsi/schema/application/child_application/draft07.rb +29 -0
  17. data/lib/jsi/schema/application/child_application/items.rb +18 -0
  18. data/lib/jsi/schema/application/child_application/properties.rb +25 -0
  19. data/lib/jsi/schema/application/child_application.rb +40 -0
  20. data/lib/jsi/schema/application/draft04.rb +8 -0
  21. data/lib/jsi/schema/application/draft06.rb +8 -0
  22. data/lib/jsi/schema/application/draft07.rb +8 -0
  23. data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
  24. data/lib/jsi/schema/application/inplace_application/draft04.rb +26 -0
  25. data/lib/jsi/schema/application/inplace_application/draft06.rb +27 -0
  26. data/lib/jsi/schema/application/inplace_application/draft07.rb +33 -0
  27. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
  28. data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
  29. data/lib/jsi/schema/application/inplace_application/someof.rb +29 -0
  30. data/lib/jsi/schema/application/inplace_application.rb +46 -0
  31. data/lib/jsi/schema/application.rb +12 -0
  32. data/lib/jsi/schema/draft04.rb +14 -0
  33. data/lib/jsi/schema/draft06.rb +14 -0
  34. data/lib/jsi/schema/draft07.rb +14 -0
  35. data/lib/jsi/schema/issue.rb +36 -0
  36. data/lib/jsi/schema/ref.rb +159 -0
  37. data/lib/jsi/schema/schema_ancestor_node.rb +119 -0
  38. data/lib/jsi/schema/validation/array.rb +69 -0
  39. data/lib/jsi/schema/validation/const.rb +20 -0
  40. data/lib/jsi/schema/validation/contains.rb +25 -0
  41. data/lib/jsi/schema/validation/core.rb +39 -0
  42. data/lib/jsi/schema/validation/dependencies.rb +49 -0
  43. data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
  44. data/lib/jsi/schema/validation/draft04.rb +112 -0
  45. data/lib/jsi/schema/validation/draft06.rb +122 -0
  46. data/lib/jsi/schema/validation/draft07.rb +159 -0
  47. data/lib/jsi/schema/validation/enum.rb +25 -0
  48. data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
  49. data/lib/jsi/schema/validation/items.rb +54 -0
  50. data/lib/jsi/schema/validation/not.rb +20 -0
  51. data/lib/jsi/schema/validation/numeric.rb +121 -0
  52. data/lib/jsi/schema/validation/object.rb +45 -0
  53. data/lib/jsi/schema/validation/pattern.rb +34 -0
  54. data/lib/jsi/schema/validation/properties.rb +101 -0
  55. data/lib/jsi/schema/validation/property_names.rb +32 -0
  56. data/lib/jsi/schema/validation/ref.rb +40 -0
  57. data/lib/jsi/schema/validation/required.rb +27 -0
  58. data/lib/jsi/schema/validation/someof.rb +90 -0
  59. data/lib/jsi/schema/validation/string.rb +47 -0
  60. data/lib/jsi/schema/validation/type.rb +49 -0
  61. data/lib/jsi/schema/validation.rb +51 -0
  62. data/lib/jsi/schema.rb +528 -233
  63. data/lib/jsi/schema_classes.rb +238 -51
  64. data/lib/jsi/schema_registry.rb +141 -0
  65. data/lib/jsi/schema_set.rb +141 -0
  66. data/lib/jsi/simple_wrap.rb +8 -3
  67. data/lib/jsi/typelike_modules.rb +75 -68
  68. data/lib/jsi/util/attr_struct.rb +106 -0
  69. data/lib/jsi/util.rb +167 -64
  70. data/lib/jsi/validation/error.rb +34 -0
  71. data/lib/jsi/validation/result.rb +210 -0
  72. data/lib/jsi/validation.rb +15 -0
  73. data/lib/jsi/version.rb +3 -1
  74. data/lib/jsi.rb +72 -9
  75. data/lib/schemas/json-schema.org/draft-04/schema.rb +12 -0
  76. data/lib/schemas/json-schema.org/draft-06/schema.rb +12 -0
  77. data/lib/schemas/json-schema.org/draft-07/schema.rb +12 -0
  78. data/readme.rb +138 -0
  79. data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
  80. data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
  81. data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
  82. metadata +80 -107
  83. data/.simplecov +0 -1
  84. data/LICENSE.txt +0 -21
  85. data/Rakefile.rb +0 -9
  86. data/jsi.gemspec +0 -31
  87. data/lib/jsi/base/to_rb.rb +0 -126
  88. data/lib/jsi/json/node.rb +0 -243
  89. data/lib/jsi/json/pointer.rb +0 -330
  90. data/lib/jsi/json-schema-fragments.rb +0 -59
  91. data/lib/jsi/json.rb +0 -8
  92. data/test/base_array_test.rb +0 -209
  93. data/test/base_hash_test.rb +0 -204
  94. data/test/base_test.rb +0 -422
  95. data/test/jsi_coder_test.rb +0 -85
  96. data/test/jsi_json_arraynode_test.rb +0 -150
  97. data/test/jsi_json_hashnode_test.rb +0 -132
  98. data/test/jsi_json_node_test.rb +0 -310
  99. data/test/jsi_json_pointer_test.rb +0 -106
  100. data/test/jsi_test.rb +0 -11
  101. data/test/jsi_typelike_as_json_test.rb +0 -53
  102. data/test/schema_test.rb +0 -196
  103. data/test/spreedly_openapi_test.rb +0 -8
  104. data/test/test_helper.rb +0 -63
  105. data/test/util_test.rb +0 -62
@@ -1,126 +0,0 @@
1
- module JSI
2
- class Base
3
- class << self
4
- def class_comment
5
- lines = []
6
-
7
- description = schema &&
8
- schema['description'].respond_to?(:to_str) &&
9
- schema['description'].to_str
10
- if description
11
- description.split("\n", -1).each do |descline|
12
- lines << "# " + descline
13
- end
14
- lines << "#"
15
- end
16
-
17
- schema.described_object_property_names.each_with_index do |propname, i|
18
- lines << "#" unless i == 0
19
- lines << "# @!attribute [rw] #{propname}"
20
-
21
- property_schema = schema['properties'].respond_to?(:to_hash) &&
22
- schema['properties'][propname].respond_to?(:to_hash) &&
23
- schema['properties'][propname]
24
-
25
- required = property_schema && property_schema['required']
26
- required ||= schema['required'].respond_to?(:to_ary) && schema['required'].include?(propname)
27
- lines << "# @required" if required
28
-
29
- type = property_schema &&
30
- property_schema['type'].respond_to?(:to_str) &&
31
- property_schema['type'].to_str
32
- simple = {'string' => 'String', 'number' => 'Numeric', 'boolean' => 'Boolean', 'null' => 'nil'}
33
- rettypes = []
34
- if simple.key?(type)
35
- rettypes << simple[type]
36
- elsif type == 'object' || type == 'array'
37
- rettypes = []
38
- schema_class = JSI.class_for_schema(property_schema)
39
- unless schema_class.name =~ /\AJSI::SchemaClasses::/
40
- rettypes << schema_class.name
41
- end
42
- rettypes << {'object' => '#to_hash', 'array' => '#to_ary'}[type]
43
- elsif type
44
- # not really valid, but there's some information in there. whatever it is.
45
- rettypes << type
46
- end
47
- # we'll add Object to all because the accessor methods have no enforcement that their value is
48
- # of the specified type, and may return anything really. TODO: consider if this is of any value?
49
- rettypes << 'Object'
50
- lines << "# @return [#{rettypes.join(', ')}]"
51
-
52
- description = property_schema &&
53
- property_schema['description'].respond_to?(:to_str) &&
54
- property_schema['description'].to_str
55
- if description
56
- description.split("\n", -1).each do |descline|
57
- lines << "# " + descline
58
- end
59
- end
60
- end
61
- lines.join("\n")
62
- end
63
-
64
- def to_rb
65
- lines = []
66
- description = schema &&
67
- schema['description'].respond_to?(:to_str) &&
68
- schema['description'].to_str
69
- if description
70
- description.split("\n", -1).each do |descline|
71
- lines << "# " + descline
72
- end
73
- end
74
- lines << "class #{name}"
75
- schema.described_object_property_names.each_with_index do |propname, i|
76
- lines << "" unless i == 0
77
- property_schema = schema['properties'].respond_to?(:to_hash) &&
78
- schema['properties'][propname].respond_to?(:to_hash) &&
79
- schema['properties'][propname]
80
- description = property_schema &&
81
- property_schema['description'].respond_to?(:to_str) &&
82
- property_schema['description'].to_str
83
- if description
84
- description.split("\n", -1).each do |descline|
85
- lines << " # " + descline
86
- end
87
- lines << " #" # blank comment line between description and @return
88
- end
89
-
90
- required = property_schema && property_schema['required']
91
- required ||= schema['required'].respond_to?(:to_ary) && schema['required'].include?(propname)
92
- lines << " # @required" if required
93
-
94
- type = property_schema &&
95
- property_schema['type'].respond_to?(:to_str) &&
96
- property_schema['type'].to_str
97
- simple = {'string' => 'String', 'number' => 'Numeric', 'boolean' => 'Boolean', 'null' => 'nil'}
98
- rettypes = []
99
- if simple.key?(type)
100
- rettypes << simple[type]
101
- elsif type == 'object' || type == 'array'
102
- rettypes = []
103
- schema_class = JSI.class_for_schema(property_schema)
104
- unless schema_class.name =~ /\AJSI::SchemaClasses::/
105
- rettypes << schema_class.name
106
- end
107
- rettypes << {'object' => '#to_hash', 'array' => '#to_ary'}[type]
108
- elsif type
109
- # not really valid, but there's some information in there. whatever it is.
110
- rettypes << type
111
- end
112
- # we'll add Object to all because the accessor methods have no enforcement that their value is
113
- # of the specified type, and may return anything really. TODO: consider if this is of any value?
114
- rettypes << 'Object'
115
- lines << " # @return [#{rettypes.join(', ')}]"
116
-
117
- lines << " def #{propname}"
118
- lines << " super"
119
- lines << " end"
120
- end
121
- lines << "end"
122
- lines.join("\n")
123
- end
124
- end
125
- end
126
- end
data/lib/jsi/json/node.rb DELETED
@@ -1,243 +0,0 @@
1
- module JSI
2
- module JSON
3
- # JSI::JSON::Node is an abstraction of a node within a JSON document.
4
- # it aims to act like the underlying data type of the node's content
5
- # (generally Hash or Array-like) in most cases.
6
- #
7
- # the main advantage offered by using a Node over the underlying data
8
- # is in dereferencing. if a Node consists of a hash with a $ref property
9
- # pointing within the same document, then the Node will transparently
10
- # follow the ref and return the referenced data.
11
- #
12
- # in most other respects, a Node aims to act like a Hash when the content
13
- # is Hash-like, an Array when the content is Array-like. methods of Hash
14
- # and Array are defined and delegated to the node's content.
15
- #
16
- # however, destructive methods are for the most part not implemented.
17
- # at the moment only #[]= is implemented. since Node thinly wraps the
18
- # underlying data, you can change the data and it will be reflected in
19
- # the node. implementations of destructive methods are planned.
20
- #
21
- # methods that return a modified copy such as #merge are defined, and
22
- # return a copy of the document with the content of the node modified.
23
- # the original node's document and content are untouched.
24
- class Node
25
- include PathedNode
26
-
27
- def self.new_doc(document)
28
- new_by_type(document, JSI::JSON::Pointer.new([]))
29
- end
30
-
31
- # if the content of the document at the given pointer is Hash-like, returns
32
- # a HashNode; if Array-like, returns ArrayNode. otherwise returns a
33
- # regular Node, although Nodes are for the most part instantiated from
34
- # Hash or Array-like content.
35
- def self.new_by_type(document, pointer)
36
- content = pointer.evaluate(document)
37
- if content.respond_to?(:to_hash)
38
- HashNode.new(document, pointer)
39
- elsif content.respond_to?(:to_ary)
40
- ArrayNode.new(document, pointer)
41
- else
42
- Node.new(document, pointer)
43
- end
44
- end
45
-
46
- # a Node represents the content of a document at a given pointer.
47
- def initialize(document, pointer)
48
- unless pointer.is_a?(JSI::JSON::Pointer)
49
- raise(TypeError, "pointer must be a JSI::JSON::Pointer. got: #{pointer.pretty_inspect.chomp} (#{pointer.class})")
50
- end
51
- if document.is_a?(JSI::JSON::Node)
52
- raise(TypeError, "document of a Node should not be another JSI::JSON::Node: #{document.inspect}")
53
- end
54
- @document = document
55
- @pointer = pointer
56
- end
57
-
58
- # the document containing this Node at our pointer
59
- attr_reader :document
60
-
61
- # JSI::JSON::Pointer pointing to this node within its document
62
- attr_reader :pointer
63
-
64
- # @return [Array<Object>] the path of this node; an array of reference_tokens of the pointer
65
- def path
66
- pointer.reference_tokens
67
- end
68
-
69
- alias_method :node_document, :document
70
- alias_method :node_ptr, :pointer
71
-
72
- # the raw content of this Node from the underlying document at this Node's pointer.
73
- alias_method :content, :node_content
74
-
75
- # returns content at the given subscript - call this the subcontent.
76
- #
77
- # if the content cannot be subscripted, raises TypeError.
78
- #
79
- # if the subcontent is Hash-like, it is wrapped as a JSI::JSON::HashNode before being returned.
80
- # if the subcontent is Array-like, it is wrapped as a JSI::JSON::ArrayNode before being returned.
81
- #
82
- # if this node's content is a $ref - that is, a hash with a $ref attribute - and the subscript is
83
- # not a key of the hash, then the $ref is followed before returning the subcontent.
84
- def [](subscript)
85
- ptr = self.pointer
86
- content = self.content
87
- if content.respond_to?(:to_hash) && !(content.respond_to?(:key?) ? content : content.to_hash).key?(subscript)
88
- pointer.deref(document) do |deref_ptr|
89
- ptr = deref_ptr
90
- content = ptr.evaluate(document)
91
- end
92
- end
93
- unless content.respond_to?(:[])
94
- if content.respond_to?(:to_hash)
95
- content = content.to_hash
96
- elsif content.respond_to?(:to_ary)
97
- content = content.to_ary
98
- else
99
- raise(NoMethodError, "undefined method `[]`\nsubscripting with #{subscript.pretty_inspect.chomp} (#{subscript.class}) from #{content.class.inspect}. content is: #{content.pretty_inspect.chomp}")
100
- end
101
- end
102
- begin
103
- subcontent = content[subscript]
104
- rescue TypeError => e
105
- raise(e.class, e.message + "\nsubscripting with #{subscript.pretty_inspect.chomp} (#{subscript.class}) from #{content.class.inspect}. content is: #{content.pretty_inspect.chomp}", e.backtrace)
106
- end
107
- if subcontent.respond_to?(:to_hash)
108
- HashNode.new(document, ptr[subscript])
109
- elsif subcontent.respond_to?(:to_ary)
110
- ArrayNode.new(document, ptr[subscript])
111
- else
112
- subcontent
113
- end
114
- end
115
-
116
- # assigns the given subscript of the content to the given value. the document is modified in place.
117
- def []=(subscript, value)
118
- if value.is_a?(Node)
119
- content[subscript] = value.content
120
- else
121
- content[subscript] = value
122
- end
123
- end
124
-
125
- # returns a Node, dereferencing a $ref attribute if possible. if this node is not hash-like,
126
- # does not have a $ref, or if what its $ref cannot be found, this node is returned.
127
- #
128
- # currently only $refs pointing within the same document are followed.
129
- #
130
- # @yield [Node] if a block is given (optional), this will yield a deref'd node. if this
131
- # node is not a $ref object, the block is not called. if we are a $ref which cannot be followed
132
- # (e.g. a $ref to an external document, which is not yet supported), the block is not called.
133
- # @return [JSI::JSON::Node] dereferenced node, or this node
134
- def deref(&block)
135
- pointer.deref(document) do |deref_ptr|
136
- return Node.new_by_type(document, deref_ptr).tap(&(block || Util::NOOP))
137
- end
138
- return self
139
- end
140
-
141
- # a Node at the root of the document
142
- def document_node
143
- Node.new_doc(document)
144
- end
145
-
146
- alias_method :document_root_node, :document_node
147
-
148
- # @return [Boolean] whether this node is the root of its document
149
- def root_node?
150
- pointer.root?
151
- end
152
-
153
- # the parent of this node. if this node is the document root, raises
154
- # JSI::JSON::Pointer::ReferenceError.
155
- def parent_node
156
- Node.new_by_type(document, pointer.parent)
157
- end
158
-
159
- # the pointer path to this node within the document, per RFC 6901 https://tools.ietf.org/html/rfc6901
160
- def pointer_path
161
- pointer.pointer
162
- end
163
-
164
- # the pointer fragment to this node within the document, per RFC 6901 https://tools.ietf.org/html/rfc6901
165
- def fragment
166
- pointer.fragment
167
- end
168
-
169
- # returns a jsonifiable representation of this node's content
170
- def as_json(*opt)
171
- Typelike.as_json(content, *opt)
172
- end
173
-
174
- # takes a block. the block is yielded the content of this node. the block MUST return a modified
175
- # copy of that content (and NOT modify the object it is given).
176
- def modified_copy(&block)
177
- Node.new_by_type(pointer.modified_document_copy(document, &block), pointer)
178
- end
179
-
180
- def dup
181
- modified_copy(&:dup)
182
- end
183
-
184
- # meta-information about the object, outside the content. used by #inspect / #pretty_print
185
- def object_group_text
186
- "fragment=#{fragment.inspect}" + (content.respond_to?(:object_group_text) ? ' ' + content.object_group_text : '')
187
- end
188
-
189
- # a string representing this node
190
- def inspect
191
- "\#<#{self.class.inspect} #{object_group_text} #{content.inspect}>"
192
- end
193
-
194
- # pretty-prints a representation this node to the given printer
195
- def pretty_print(q)
196
- q.instance_exec(self) do |obj|
197
- text "\#<#{obj.class.inspect} #{obj.object_group_text}"
198
- group_sub {
199
- nest(2) {
200
- breakable ' '
201
- pp obj.content
202
- }
203
- }
204
- breakable ''
205
- text '>'
206
- end
207
- end
208
-
209
- # fingerprint for equality (see FingerprintHash). two nodes are equal if they are both nodes
210
- # (regardless of type, e.g. one may be a Node and the other may be a HashNode) within equal
211
- # documents at equal pointers. note that this means two nodes with the same content may not be
212
- # considered equal.
213
- def fingerprint
214
- {class: JSI::JSON::Node, document: document, pointer: pointer}
215
- end
216
- include FingerprintHash
217
- end
218
-
219
- # a JSI::JSON::Node whose content is Array-like (responds to #to_ary)
220
- # and includes Array methods from Arraylike
221
- class ArrayNode < Node
222
- include Enumerable
223
- include PathedArrayNode
224
-
225
- # returns a jsonifiable representation of this node's content
226
- def as_json(*opt) # needs redefined after including Enumerable
227
- Typelike.as_json(content, *opt)
228
- end
229
- end
230
-
231
- # a JSI::JSON::Node whose content is Hash-like (responds to #to_hash)
232
- # and includes Hash methods from Hashlike
233
- class HashNode < Node
234
- include Enumerable
235
- include PathedHashNode
236
-
237
- # returns a jsonifiable representation of this node's content
238
- def as_json(*opt) # needs redefined after including Enumerable
239
- Typelike.as_json(content, *opt)
240
- end
241
- end
242
- end
243
- end