jsi 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 898a2aae157baa1fc01f6e3e28286f4b6fc9be61b7e47689529acdfdbc8c9c17
4
- data.tar.gz: 9328c06ac1cc07993f27644b3c94b87b1667be06b6404af2ce5753fb7dc3da1a
3
+ metadata.gz: e9899a9ae6a661558974001eba14223c9e497849d3bf3d8e3c6ee9fffd4fcc6d
4
+ data.tar.gz: 8cfcc01a0167bc251235d7376e793e50bdba92e39f706edbb7bd05386a035352
5
5
  SHA512:
6
- metadata.gz: 874021cfce6aa05353a897d8519cf319ce0de3fc1a2176cefcc7505eddd8960a2525bbb484a0a3fcaa81ac030768a0d64337f1a3f86e6fbb13403ffbdb13eadc
7
- data.tar.gz: e67feab1a13a2a23ed51ad75d7622ab9f7093b3dbf0cb79d896f1bd56e6f1f0deb322151b8b7529226a111124de01dceeb4f9981759b358ab78d50b337e2cbef
6
+ metadata.gz: e4ed68443ce9a25db4ddeb18c80f4aea51b4dbb819600afc98c30ab6453ddf8dbc7589079cb7e6743802a31cc4ec53bc32b1da6796a10334e8cc2b6182d37cd4
7
+ data.tar.gz: 64e8c93a243bc54e0226b8020e20b475c151d349d0b2769bf745b0ba6019f31cf6de63c4edaa36baea89da790814654e92c68154c5a63ade881bef4a18fac133
@@ -1,3 +1,10 @@
1
+ # v0.1.0
2
+
3
+ - JSI::JSON::Pointer replaces monkey-patched-in ::JSON::Schema::Pointer
4
+ - JSI::JSICoder replaces JSI::SchemaInstanceJSONCoder / ObjectJSONCoder
5
+ - remove JSI::StructJSONCoder
6
+ - misc improvements to code, doc, tests
7
+
1
8
  # v0.0.4
2
9
 
3
10
  - minor bugfixes / improvements
data/README.md CHANGED
@@ -48,7 +48,7 @@ bill = Contact.new('name' => 'bill', 'phone' => [{'location' => 'home', 'number'
48
48
 
49
49
  Note that the keys are strings. JSI, being designed with JSON in mind, is geared toward string keys. Symbol keys will not match to schema properties, and so act the same as any other key not recognized from the schema.
50
50
 
51
- The nested classes can be seen as `JSI::SchemaClasses[schema_id]` where schema_id is a generated value.
51
+ The nested classes can be seen in the #inspect output as `JSI::SchemaClasses[schema_id]` where schema_id is a generated value.
52
52
 
53
53
  We get accessors for the Contact:
54
54
 
@@ -169,16 +169,18 @@ Let's say you're sticking to json types in the database - you have to do so if y
169
169
 
170
170
  But if your database contains json, then your deserialized objects in ruby are likewise Hash / Array / basic types. You have to use subscripts instead of accessors, and you don't have any way to add methods to your data types.
171
171
 
172
- JSI gives you the best of both with SchemaInstanceJSONCoder. The objects in your database are simple json types, and your ruby classes are extensible and have the accessors you get from a JSI class hierarchy. Here's an example:
172
+ JSI gives you the best of both with JSICoder. This coder dumps objects which are simple json types, and loads instances of a specified JSI class. Here's an example:
173
173
 
174
174
  ```ruby
175
175
  class User < ActiveRecord::Base
176
- serialize :contacts, JSI::SchemaInstanceJSONCoder.new(Contact, array: true)
176
+ serialize :contact_info, JSI::JSICoder.new(Contact)
177
177
  end
178
178
  ```
179
179
 
180
180
  Now `user.contacts` will return an array of Contact instances, from the json type in the database, with Contact's accessors, validations, and user-defined instance methods.
181
181
 
182
+ See the gem [`arms`](https://github.com/notEthan/arms) if you wish to serialize the dumped JSON-compatible objects further as text.
183
+
182
184
  ## Keying Hashes (JSON Objects)
183
185
 
184
186
  Unlike Ruby, JSON only supports string keys. It is recommended to use strings as hash keys for all JSI instances, but JSI does not enforce this, nor does it do any key conversion. It should be possible to use ActiveSupport::HashWithIndifferentAccess as the instance of a JSI in order to gain the benefits that offers over a plain hash. This is not tested behavior, but JSI should behave correctly with any instance that responds to #to_hash.
data/lib/jsi.rb CHANGED
@@ -18,9 +18,7 @@ module JSI
18
18
  autoload :BaseArray, 'jsi/base'
19
19
  autoload :BaseHash, 'jsi/base'
20
20
  autoload :SchemaClasses, 'jsi/base'
21
- autoload :ObjectJSONCoder, 'jsi/schema_instance_json_coder'
22
- autoload :StructJSONCoder, 'jsi/struct_json_coder'
23
- autoload :SchemaInstanceJSONCoder, 'jsi/schema_instance_json_coder'
21
+ autoload :JSICoder, 'jsi/jsi_coder'
24
22
 
25
23
  # @return [Class subclassing JSI::Base] a JSI class which represents the
26
24
  # given schema. instances of the class represent JSON Schema instances
@@ -15,6 +15,8 @@ module JSI
15
15
  include Enumerable
16
16
 
17
17
  class << self
18
+ attr_accessor :in_schema_classes
19
+
18
20
  # @return [String] absolute schema_id of the schema this class represents.
19
21
  # see {Schema#schema_id}.
20
22
  def schema_id
@@ -23,10 +25,13 @@ module JSI
23
25
 
24
26
  # @return [String] a string representing the class, with schema_id
25
27
  def inspect
28
+ name # see #name for side effects
26
29
  if !respond_to?(:schema)
27
30
  super
28
- elsif !name || name =~ /\AJSI::SchemaClasses::/
31
+ elsif in_schema_classes
29
32
  %Q(#{SchemaClasses.inspect}[#{schema_id.inspect}])
33
+ elsif !name
34
+ %Q(#<Class for Schema: #{schema_id}>)
30
35
  else
31
36
  %Q(#{name} (#{schema_id}))
32
37
  end
@@ -57,6 +62,7 @@ module JSI
57
62
  def name
58
63
  unless super || SchemaClasses.const_defined?(schema_classes_const_name)
59
64
  SchemaClasses.const_set(schema_classes_const_name, self)
65
+ self.in_schema_classes = true
60
66
  end
61
67
  super
62
68
  end
@@ -0,0 +1,86 @@
1
+ module JSI
2
+ # this is an ActiveRecord serialization coder intended to serialize between
3
+ # JSON-compatible objects on the database side, and a JSI instance loaded on
4
+ # the model attribute.
5
+ #
6
+ # on its own this coder is useful with a JSON database column. in order to
7
+ # serialize further to a string of JSON, or to YAML, the gem `arms` allows
8
+ # coders to be chained together. for example, for a table `foos` and a column
9
+ # `preferences_json` which is an actual json column, and `preferences_txt`
10
+ # which is a string:
11
+ #
12
+ # Preferences = JSI.class_for_schema(preferences_json_schema)
13
+ # class Foo < ActiveRecord::Base
14
+ # # as a single serializer, loads a Preferences instance from a json column
15
+ # serialize 'preferences', JSI::JSICoder.new(Preferences)
16
+ #
17
+ # # for a text column, arms_serialize will go from JSI to JSON-compatible
18
+ # # objects to a string. the symbol `:jsi` is a shortcut for JSI::JSICoder.
19
+ # arms_serialize 'preferences', [:jsi, Preferences], :json
20
+ # end
21
+ #
22
+ # the column data may be either a single instance of the loaded class
23
+ # (represented as one json object) or an array of them (represented as a json
24
+ # array of json objects), indicated by the keyword argument `array`.
25
+ class JSICoder
26
+ # @param loaded_class [Class] the JSI::Base subclass which #load will instantiate
27
+ # @param array [Boolean] whether the dumped data represent one instance of loaded_class,
28
+ # or an array of them. note that it may be preferable to have loaded_class simply be
29
+ # an array schema class.
30
+ def initialize(loaded_class, array: false)
31
+ @loaded_class = loaded_class
32
+ @array = array
33
+ end
34
+
35
+ # loads the database column to instances of #loaded_class
36
+ #
37
+ # @param data [Object, Array, nil] the dumped schema instance(s) of the JSI(s)
38
+ # @return [loaded_class instance, Array<loaded_class instance>, nil] the JSI or JSIs
39
+ # containing the schema instance(s), or nil if data is nil
40
+ def load(data)
41
+ return nil if data.nil?
42
+ object = if @array
43
+ unless data.respond_to?(:to_ary)
44
+ raise TypeError, "expected array-like column data; got: #{data.class}: #{data.inspect}"
45
+ end
46
+ data.map { |el| load_object(el) }
47
+ else
48
+ load_object(data)
49
+ end
50
+ object
51
+ end
52
+
53
+ # @param object [loaded_class instance, Array<loaded_class instance>, nil] the JSI or array
54
+ # of JSIs containing the schema instance(s)
55
+ # @return [Object, Array, nil] the schema instance(s) of the JSI(s), or nil if object is nil
56
+ def dump(object)
57
+ return nil if object.nil?
58
+ jsonifiable = begin
59
+ if @array
60
+ unless object.respond_to?(:to_ary)
61
+ raise(TypeError, "expected array-like attribute; got: #{object.class}: #{object.inspect}")
62
+ end
63
+ object.map do |el|
64
+ dump_object(el)
65
+ end
66
+ else
67
+ dump_object(object)
68
+ end
69
+ end
70
+ jsonifiable
71
+ end
72
+
73
+ private
74
+ # @param data [Object]
75
+ # @return [loaded_class]
76
+ def load_object(data)
77
+ @loaded_class.new(data)
78
+ end
79
+
80
+ # @param object [loaded_class]
81
+ # @return [Object]
82
+ def dump_object(object)
83
+ JSI::Typelike.as_json(object)
84
+ end
85
+ end
86
+ end
@@ -2,140 +2,6 @@ require "json-schema"
2
2
 
3
3
  # apply the changes from https://github.com/ruby-json-schema/json-schema/pull/382
4
4
 
5
- # json-schema/pointer.rb
6
- require 'addressable/uri'
7
-
8
- module JSON
9
- class Schema
10
- # a JSON Pointer, as described by RFC 6901 https://tools.ietf.org/html/rfc6901
11
- class Pointer
12
- class Error < JSON::Schema::SchemaError
13
- end
14
- class PointerSyntaxError < Error
15
- end
16
- class ReferenceError < Error
17
- end
18
-
19
- # parse a fragment to an array of reference tokens
20
- #
21
- # #/foo/bar
22
- #
23
- # => ['foo', 'bar']
24
- #
25
- # #/foo%20bar
26
- #
27
- # => ['foo bar']
28
- def self.parse_fragment(fragment)
29
- fragment = Addressable::URI.unescape(fragment)
30
- match = fragment.match(/\A#/)
31
- if match
32
- parse_pointer(match.post_match)
33
- else
34
- raise(PointerSyntaxError, "Invalid fragment syntax in #{fragment.inspect}: fragment must begin with #")
35
- end
36
- end
37
-
38
- # parse a pointer to an array of reference tokens
39
- #
40
- # /foo
41
- #
42
- # => ['foo']
43
- #
44
- # /foo~0bar/baz~1qux
45
- #
46
- # => ['foo~bar', 'baz/qux']
47
- def self.parse_pointer(pointer_string)
48
- tokens = pointer_string.split('/', -1).map! do |piece|
49
- piece.gsub('~1', '/').gsub('~0', '~')
50
- end
51
- if tokens[0] == ''
52
- tokens[1..-1]
53
- elsif tokens.empty?
54
- tokens
55
- else
56
- raise(PointerSyntaxError, "Invalid pointer syntax in #{pointer_string.inspect}: pointer must begin with /")
57
- end
58
- end
59
-
60
- # initializes a JSON::Schema::Pointer from the given representation.
61
- #
62
- # type may be one of:
63
- #
64
- # - :fragment - the representation is a fragment containing a pointer (starting with #)
65
- # - :pointer - the representation is a pointer (starting with /)
66
- # - :reference_tokens - the representation is an array of tokens referencing a path in a document
67
- def initialize(type, representation)
68
- @type = type
69
- if type == :reference_tokens
70
- reference_tokens = representation
71
- elsif type == :fragment
72
- reference_tokens = self.class.parse_fragment(representation)
73
- elsif type == :pointer
74
- reference_tokens = self.class.parse_pointer(representation)
75
- else
76
- raise ArgumentError, "invalid initialization type: #{type.inspect} with representation #{representation.inspect}"
77
- end
78
- @reference_tokens = reference_tokens.map(&:freeze).freeze
79
- end
80
-
81
- attr_reader :reference_tokens
82
-
83
- # takes a root json document and evaluates this pointer through the document, returning the value
84
- # pointed to by this pointer.
85
- def evaluate(document)
86
- res = reference_tokens.inject(document) do |value, token|
87
- if value.respond_to?(:to_ary)
88
- if token.is_a?(String) && token =~ /\A\d|[1-9]\d+\z/
89
- token = token.to_i
90
- end
91
- unless token.is_a?(Integer)
92
- raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not an integer and cannot be resolved in array #{value.inspect}")
93
- end
94
- unless (0...(value.respond_to?(:size) ? value : value.to_ary).size).include?(token)
95
- raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid index of #{value.inspect}")
96
- end
97
- (value.respond_to?(:[]) ? value : value.to_ary)[token]
98
- elsif value.respond_to?(:to_hash)
99
- unless (value.respond_to?(:key?) ? value : value.to_hash).key?(token)
100
- raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid key of #{value.inspect}")
101
- end
102
- (value.respond_to?(:[]) ? value : value.to_hash)[token]
103
- else
104
- raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} cannot be resolved in #{value.inspect}")
105
- end
106
- end
107
- res
108
- end
109
-
110
- # the pointer string representation of this Pointer
111
- def pointer
112
- reference_tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('')
113
- end
114
-
115
- # the fragment string representation of this Pointer
116
- def fragment
117
- '#' + Addressable::URI.escape(pointer)
118
- end
119
-
120
- def to_s
121
- "#<#{self.class.inspect} #{@type} = #{representation_s}>"
122
- end
123
-
124
- private
125
-
126
- def representation_s
127
- if @type == :fragment
128
- fragment
129
- elsif @type == :pointer
130
- pointer
131
- else
132
- reference_tokens.inspect
133
- end
134
- end
135
- end
136
- end
137
- end
138
-
139
5
  # json-schema/validator.rb
140
6
 
141
7
  module JSON
@@ -177,7 +43,7 @@ module JSON
177
43
  def schema_from_fragment(base_schema, fragment)
178
44
  schema_uri = base_schema.uri
179
45
 
180
- pointer = JSON::Schema::Pointer.new(:fragment, fragment)
46
+ pointer = JSI::JSON::Pointer.new(:fragment, fragment)
181
47
 
182
48
  base_schema = JSON::Schema.new(pointer.evaluate(base_schema.schema), schema_uri, @options[:version])
183
49
 
@@ -3,5 +3,6 @@ module JSI
3
3
  autoload :Node, 'jsi/json/node'
4
4
  autoload :ArrayNode, 'jsi/json/node'
5
5
  autoload :HashNode, 'jsi/json/node'
6
+ autoload :Pointer, 'jsi/json/pointer'
6
7
  end
7
8
  end
@@ -52,14 +52,14 @@ module JSI
52
52
  end
53
53
  @document = document
54
54
  @path = path.to_ary.dup.freeze
55
- @pointer = ::JSON::Schema::Pointer.new(:reference_tokens, path)
55
+ @pointer = JSI::JSON::Pointer.new(:reference_tokens, path)
56
56
  end
57
57
 
58
58
  # the path of this Node within its document
59
59
  attr_reader :path
60
60
  # the document containing this Node at is path
61
61
  attr_reader :document
62
- # ::JSON::Schema::Pointer representing the path to this node within its document
62
+ # JSI::JSON::Pointer representing the path to this node within its document
63
63
  attr_reader :pointer
64
64
 
65
65
  # the raw content of this Node from the underlying document at this Node's path.
@@ -129,7 +129,7 @@ module JSI
129
129
  return self unless ref.is_a?(String)
130
130
 
131
131
  if ref[/\A#/]
132
- return self.class.new_by_type(document, ::JSON::Schema::Pointer.parse_fragment(ref)).deref
132
+ return self.class.new_by_type(document, JSI::JSON::Pointer.parse_fragment(ref)).deref
133
133
  end
134
134
 
135
135
  # HAX for how google does refs and ids
@@ -153,10 +153,10 @@ module JSI
153
153
  end
154
154
 
155
155
  # the parent of this node. if this node is the document root (its path is empty), raises
156
- # ::JSON::Schema::Pointer::ReferenceError.
156
+ # JSI::JSON::Pointer::ReferenceError.
157
157
  def parent_node
158
158
  if path.empty?
159
- raise(::JSON::Schema::Pointer::ReferenceError, "cannot access parent of root node: #{pretty_inspect.chomp}")
159
+ raise(JSI::JSON::Pointer::ReferenceError, "cannot access parent of root node: #{pretty_inspect.chomp}")
160
160
  else
161
161
  Node.new_by_type(document, path[0...-1])
162
162
  end
@@ -0,0 +1,136 @@
1
+ require 'addressable/uri'
2
+
3
+ module JSI
4
+ module JSON
5
+ # a JSON Pointer, as described by RFC 6901 https://tools.ietf.org/html/rfc6901
6
+ class Pointer
7
+ class Error < StandardError
8
+ end
9
+ class PointerSyntaxError < Error
10
+ end
11
+ class ReferenceError < Error
12
+ end
13
+
14
+ # parse a fragment to an array of reference tokens
15
+ #
16
+ # #/foo/bar
17
+ #
18
+ # => ['foo', 'bar']
19
+ #
20
+ # #/foo%20bar
21
+ #
22
+ # => ['foo bar']
23
+ def self.parse_fragment(fragment)
24
+ fragment = Addressable::URI.unescape(fragment)
25
+ match = fragment.match(/\A#/)
26
+ if match
27
+ parse_pointer(match.post_match)
28
+ else
29
+ raise(PointerSyntaxError, "Invalid fragment syntax in #{fragment.inspect}: fragment must begin with #")
30
+ end
31
+ end
32
+
33
+ # parse a pointer to an array of reference tokens
34
+ #
35
+ # /foo
36
+ #
37
+ # => ['foo']
38
+ #
39
+ # /foo~0bar/baz~1qux
40
+ #
41
+ # => ['foo~bar', 'baz/qux']
42
+ def self.parse_pointer(pointer_string)
43
+ tokens = pointer_string.split('/', -1).map! do |piece|
44
+ piece.gsub('~1', '/').gsub('~0', '~')
45
+ end
46
+ if tokens[0] == ''
47
+ tokens[1..-1]
48
+ elsif tokens.empty?
49
+ tokens
50
+ else
51
+ raise(PointerSyntaxError, "Invalid pointer syntax in #{pointer_string.inspect}: pointer must begin with /")
52
+ end
53
+ end
54
+
55
+ # initializes a JSI::JSON::Pointer from the given representation.
56
+ #
57
+ # type may be one of:
58
+ #
59
+ # - :fragment - the representation is a fragment containing a pointer (starting with #)
60
+ # - :pointer - the representation is a pointer (starting with /)
61
+ # - :reference_tokens - the representation is an array of tokens referencing a path in a document
62
+ def initialize(type, representation)
63
+ @type = type
64
+ if type == :reference_tokens
65
+ reference_tokens = representation
66
+ elsif type == :fragment
67
+ reference_tokens = self.class.parse_fragment(representation)
68
+ elsif type == :pointer
69
+ reference_tokens = self.class.parse_pointer(representation)
70
+ else
71
+ raise ArgumentError, "invalid initialization type: #{type.inspect} with representation #{representation.inspect}"
72
+ end
73
+ @reference_tokens = reference_tokens.map(&:freeze).freeze
74
+ end
75
+
76
+ attr_reader :reference_tokens
77
+
78
+ # takes a root json document and evaluates this pointer through the document, returning the value
79
+ # pointed to by this pointer.
80
+ #
81
+ # @param document [#to_ary, #to_hash] the document against which we will evaluate this pointer
82
+ # @return [Object] the content of the document pointed to by this pointer
83
+ # @raise [JSI::JSON::Pointer::ReferenceError] the document does not contain the path this pointer references
84
+ def evaluate(document)
85
+ res = reference_tokens.inject(document) do |value, token|
86
+ if value.respond_to?(:to_ary)
87
+ if token.is_a?(String) && token =~ /\A\d|[1-9]\d+\z/
88
+ token = token.to_i
89
+ end
90
+ unless token.is_a?(Integer)
91
+ raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not an integer and cannot be resolved in array #{value.inspect}")
92
+ end
93
+ unless (0...(value.respond_to?(:size) ? value : value.to_ary).size).include?(token)
94
+ raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid index of #{value.inspect}")
95
+ end
96
+ (value.respond_to?(:[]) ? value : value.to_ary)[token]
97
+ elsif value.respond_to?(:to_hash)
98
+ unless (value.respond_to?(:key?) ? value : value.to_hash).key?(token)
99
+ raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} is not a valid key of #{value.inspect}")
100
+ end
101
+ (value.respond_to?(:[]) ? value : value.to_hash)[token]
102
+ else
103
+ raise(ReferenceError, "Invalid resolution for #{to_s}: #{token.inspect} cannot be resolved in #{value.inspect}")
104
+ end
105
+ end
106
+ res
107
+ end
108
+
109
+ # the pointer string representation of this Pointer
110
+ def pointer
111
+ reference_tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('')
112
+ end
113
+
114
+ # the fragment string representation of this Pointer
115
+ def fragment
116
+ '#' + Addressable::URI.escape(pointer)
117
+ end
118
+
119
+ def to_s
120
+ "#<#{self.class.inspect} #{@type} = #{representation_s}>"
121
+ end
122
+
123
+ private
124
+
125
+ def representation_s
126
+ if @type == :fragment
127
+ fragment
128
+ elsif @type == :pointer
129
+ pointer
130
+ else
131
+ reference_tokens.inspect
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -97,13 +97,13 @@ module JSI
97
97
  end
98
98
  if parent_auri.fragment
99
99
  # add onto the fragment
100
- parent_id_path = ::JSON::Schema::Pointer.new(:fragment, '#' + parent_auri.fragment).reference_tokens
100
+ parent_id_path = JSI::JSON::Pointer.new(:fragment, '#' + parent_auri.fragment).reference_tokens
101
101
  path_from_id_node = parent_id_path + path_from_id_node
102
102
  parent_auri.fragment = nil
103
103
  #else: no fragment so parent_id good as is
104
104
  end
105
105
 
106
- fragment = ::JSON::Schema::Pointer.new(:reference_tokens, path_from_id_node).fragment
106
+ fragment = JSI::JSON::Pointer.new(:reference_tokens, path_from_id_node).fragment
107
107
  schema_id = parent_auri.to_s + fragment
108
108
 
109
109
  schema_id
@@ -124,12 +124,9 @@ module JSI
124
124
  def match_to_instance(instance)
125
125
  # matching oneOf is good here. one schema for one instance.
126
126
  # matching anyOf is okay. there could be more than one schema matched. it's often just one. if more
127
- # than one is a match, the problems of allOf occur.
128
- # matching allOf is questionable. all of the schemas must be matched but we just return the first match.
129
- # there isn't really a better answer with the current implementation. merging the schemas together
130
- # is a thought but is not practical.
127
+ # than one is a match, you just get the first one.
131
128
  instance = instance.deref if instance.is_a?(JSI::JSON::Node)
132
- %w(oneOf allOf anyOf).select { |k| schema_node[k].respond_to?(:to_ary) }.each do |someof_key|
129
+ %w(oneOf anyOf).select { |k| schema_node[k].respond_to?(:to_ary) }.each do |someof_key|
133
130
  schema_node[someof_key].map(&:deref).map do |someof_node|
134
131
  someof_schema = self.class.new(someof_node)
135
132
  if someof_schema.validate(instance)
@@ -36,7 +36,13 @@ module JSI
36
36
  # @raise [TypeError] when the object (or an object nested with a hash or
37
37
  # array of object) cannot be expressed as json
38
38
  def self.as_json(object, *opt)
39
- if object.respond_to?(:to_hash)
39
+ if object.is_a?(JSI::Schema)
40
+ as_json(object.schema_object, *opt)
41
+ elsif object.is_a?(JSI::Base)
42
+ as_json(object.instance, *opt)
43
+ elsif object.is_a?(JSI::JSON::Node)
44
+ as_json(object.content, *opt)
45
+ elsif object.respond_to?(:to_hash)
40
46
  (object.respond_to?(:map) ? object : object.to_hash).map do |k, v|
41
47
  unless k.is_a?(Symbol) || k.respond_to?(:to_str)
42
48
  raise(TypeError, "json object (hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
@@ -1,3 +1,3 @@
1
1
  module JSI
2
- VERSION = "0.0.4".freeze
2
+ VERSION = "0.1.0".freeze
3
3
  end
@@ -0,0 +1,85 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe JSI::JSICoder do
4
+ let(:schema) do
5
+ {properties: {foo: {}, bar: {}}}
6
+ end
7
+ let(:schema_instance_class) { JSI.class_for_schema(schema) }
8
+ let(:options) { {} }
9
+ let(:schema_instance_json_coder) { JSI::JSICoder.new(schema_instance_class, options) }
10
+ describe 'json' do
11
+ describe 'load' do
12
+ it 'loads nil' do
13
+ assert_nil(schema_instance_json_coder.load(nil))
14
+ end
15
+ it 'loads a hash' do
16
+ assert_equal(schema_instance_class.new('foo' => 'bar'), schema_instance_json_coder.load({"foo" => "bar"}))
17
+ end
18
+ it 'loads something else' do
19
+ assert_equal(schema_instance_class.new([[]]), schema_instance_json_coder.load([[]]))
20
+ end
21
+ describe 'array' do
22
+ let(:options) { {array: true} }
23
+ it 'loads an array of hashes' do
24
+ data = [{"foo" => "bar"}, {"foo" => "baz"}]
25
+ assert_equal([schema_instance_class.new('foo' => 'bar'), schema_instance_class.new('foo' => 'baz')], schema_instance_json_coder.load(data))
26
+ end
27
+ it 'loads an empty array' do
28
+ assert_equal([], schema_instance_json_coder.load([]))
29
+ end
30
+ it 'loads a not an array' do
31
+ assert_raises(TypeError) do
32
+ schema_instance_json_coder.load(Object.new)
33
+ end
34
+ end
35
+ end
36
+ describe 'array schema' do
37
+ let(:schema) { {items: {properties: {foo: {}, bar: {}}}} }
38
+ it 'loads an array of hashes' do
39
+ data = [{"foo" => "bar"}, {"foo" => "baz"}]
40
+ assert_equal(schema_instance_class.new([{'foo' => 'bar'}, {'foo' => 'baz'}]), schema_instance_json_coder.load(data))
41
+ end
42
+ it 'loads an empty array' do
43
+ assert_equal(schema_instance_class.new([]), schema_instance_json_coder.load([]))
44
+ end
45
+ it 'loads a not an array' do
46
+ instance = Object.new
47
+ assert_equal(schema_instance_class.new(instance), schema_instance_json_coder.load(instance))
48
+ end
49
+ end
50
+ end
51
+ describe 'dump' do
52
+ it 'dumps nil' do
53
+ assert_nil(schema_instance_json_coder.dump(nil))
54
+ end
55
+ it 'dumps a schema_instance_class' do
56
+ assert_equal({"foo" => "x", "bar" => "y"}, schema_instance_json_coder.dump(schema_instance_class.new(foo: 'x', bar: 'y')))
57
+ end
58
+ it 'dumps something else' do
59
+ assert_raises(TypeError) do
60
+ schema_instance_json_coder.dump(Object.new)
61
+ end
62
+ end
63
+ it 'dumps some of the keys of a schema_instance_class after loading in a partial one' do
64
+ schema_instance_class = schema_instance_json_coder.load({'foo' => 'who'})
65
+ assert_equal({'foo' => 'who'}, schema_instance_json_coder.dump(schema_instance_class))
66
+ schema_instance_class.bar = 'whar'
67
+ assert_equal({'foo' => 'who', 'bar' => 'whar'}, schema_instance_json_coder.dump(schema_instance_class))
68
+ end
69
+ describe 'array' do
70
+ let(:options) { {array: true} }
71
+ it 'dumps an array of schema_instances' do
72
+ schema_instances = [schema_instance_class.new(foo: 'x', bar: 'y'), schema_instance_class.new(foo: 'z', bar: 'q')]
73
+ assert_equal([{"foo" => "x", "bar" => "y"}, {"foo" => "z", "bar" => "q"}], schema_instance_json_coder.dump(schema_instances))
74
+ end
75
+ end
76
+ describe 'array schema' do
77
+ let(:schema) { {items: {properties: {foo: {}, bar: {}}}} }
78
+ it 'dumps a schema_instance array' do
79
+ schema_instances = schema_instance_class.new([{foo: 'x', bar: 'y'}, {foo: 'z', bar: 'q'}])
80
+ assert_equal([{"foo" => "x", "bar" => "y"}, {"foo" => "z", "bar" => "q"}], schema_instance_json_coder.dump(schema_instances))
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -30,8 +30,8 @@ describe JSI::JSON::Node do
30
30
  end
31
31
  end
32
32
  describe '#pointer' do
33
- it 'is a ::JSON::Schema::Pointer' do
34
- assert_instance_of(::JSON::Schema::Pointer, JSI::JSON::Node.new({}, []).pointer)
33
+ it 'is a JSI::JSON::Pointer' do
34
+ assert_instance_of(JSI::JSON::Pointer, JSI::JSON::Node.new({}, []).pointer)
35
35
  end
36
36
  end
37
37
  describe '#content' do
@@ -170,7 +170,7 @@ describe JSI::JSON::Node do
170
170
  assert_equal([], root_from_sub.path)
171
171
  assert_equal({'a' => {'b' => []}}, root_from_sub.content)
172
172
  assert_equal(node, root_from_sub)
173
- err = assert_raises(::JSON::Schema::Pointer::ReferenceError) do
173
+ err = assert_raises(JSI::JSON::Pointer::ReferenceError) do
174
174
  root_from_sub.parent_node
175
175
  end
176
176
  assert_match(/\Acannot access parent of root node: #\{<JSI::JSON::HashNode/, err.message)
@@ -0,0 +1,98 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe JSI::JSON::Pointer do
4
+ # For example, given the document
5
+ let(:document) do
6
+ {
7
+ "foo" => ["bar", "baz"],
8
+ "" => 0,
9
+ "a/b" => 1,
10
+ "c%d" => 2,
11
+ "e^f" => 3,
12
+ "g|h" => 4,
13
+ "i\\j" => 5,
14
+ "k\"l" => 6,
15
+ " " => 7,
16
+ "m~n" => 8,
17
+ }
18
+ end
19
+
20
+ describe 'initialize from pointer' do
21
+ it 'parses' do
22
+ # The following strings evaluate to the accompanying values:
23
+ evaluations = [
24
+ "" , document,
25
+ "/foo" , ["bar", "baz"],
26
+ "/foo/0", "bar",
27
+ "/" , 0,
28
+ "/a~1b" , 1,
29
+ "/c%d" , 2,
30
+ "/e^f" , 3,
31
+ "/g|h" , 4,
32
+ "/i\\j" , 5,
33
+ "/k\"l" , 6,
34
+ "/ " , 7,
35
+ "/m~0n" , 8,
36
+ ]
37
+ evaluations.each_slice(2) do |pointer, value|
38
+ assert_equal(value, JSI::JSON::Pointer.new(:pointer, pointer).evaluate(document))
39
+ end
40
+ end
41
+
42
+ it 'raises for invalid syntax' do
43
+ err = assert_raises(JSI::JSON::Pointer::PointerSyntaxError) do
44
+ JSI::JSON::Pointer.new(:pointer, "this does not begin with slash").evaluate(document)
45
+ end
46
+ assert_equal("Invalid pointer syntax in \"this does not begin with slash\": pointer must begin with /", err.message)
47
+ end
48
+ end
49
+ describe 'initialize from fragment' do
50
+ # For example, given the document
51
+ let(:document) do
52
+ {
53
+ "foo" => ["bar", "baz"],
54
+ "" => 0,
55
+ "a/b" => 1,
56
+ "c%d" => 2,
57
+ "e^f" => 3,
58
+ "g|h" => 4,
59
+ "i\\j" => 5,
60
+ "k\"l" => 6,
61
+ " " => 7,
62
+ "m~n" => 8,
63
+ }
64
+ end
65
+
66
+ it 'parses' do
67
+ # the following URI fragment identifiers evaluate to the accompanying values:
68
+ evaluations = [
69
+ '#', document,
70
+ '#/foo', ["bar", "baz"],
71
+ '#/foo/0', "bar",
72
+ '#/', 0,
73
+ '#/a~1b', 1,
74
+ '#/c%25d', 2,
75
+ '#/e%5Ef', 3,
76
+ '#/g%7Ch', 4,
77
+ '#/i%5Cj', 5,
78
+ '#/k%22l', 6,
79
+ '#/%20', 7,
80
+ '#/m~0n', 8,
81
+ ]
82
+ evaluations.each_slice(2) do |fragment, value|
83
+ assert_equal(value, JSI::JSON::Pointer.new(:fragment, fragment).evaluate(document))
84
+ end
85
+ end
86
+
87
+ it 'raises for invalid syntax' do
88
+ err = assert_raises(JSI::JSON::Pointer::PointerSyntaxError) do
89
+ JSI::JSON::Pointer.new(:fragment, "this does not begin with #").evaluate(document)
90
+ end
91
+ assert_equal("Invalid fragment syntax in \"this does not begin with #\": fragment must begin with #", err.message)
92
+ err = assert_raises(JSI::JSON::Pointer::PointerSyntaxError) do
93
+ JSI::JSON::Pointer.new(:fragment, "#this does not begin with slash").evaluate(document)
94
+ end
95
+ assert_equal("Invalid pointer syntax in \"this does not begin with slash\": pointer must begin with /", err.message)
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,53 @@
1
+ require_relative 'test_helper'
2
+
3
+ class JSONifiable
4
+ def initialize(object)
5
+ @object = object
6
+ end
7
+ def as_json
8
+ @object
9
+ end
10
+ end
11
+
12
+ describe JSI::Typelike do
13
+ describe 'as_json' do
14
+ it 'expresses as json' do
15
+ assert_equal({}, JSI::Typelike.as_json({}))
16
+ assert_equal([], JSI::Typelike.as_json([]))
17
+
18
+ # symbols to string
19
+ assert_equal(['a'], JSI::Typelike.as_json([:a]))
20
+
21
+ # set
22
+ assert_equal(['a'], JSI::Typelike.as_json(Set.new(['a'])))
23
+
24
+ # responds to #to_hash / #to_ary but naught else
25
+ assert_equal({'a' => 'b'}, JSI::Typelike.as_json(SortOfHash.new({'a' => 'b'})))
26
+ assert_equal(['a'], JSI::Typelike.as_json(SortOfArray.new(['a'])))
27
+
28
+ # symbol keys to string
29
+ assert_equal({'a' => 'b'}, JSI::Typelike.as_json({a: 'b'}))
30
+ # non string/symbol key
31
+ err = assert_raises(TypeError) { JSI::Typelike.as_json({nil => 0}) }
32
+ assert_equal('json object (hash) cannot be keyed with: nil', err.message)
33
+
34
+ # schema
35
+ schema = JSI::Schema.from_object({'type' => 'array'})
36
+ assert_equal({'type' => 'array'}, JSI::Typelike.as_json(schema))
37
+
38
+ # JSI
39
+ assert_equal(['a'], JSI::Typelike.as_json(JSI.class_for_schema(schema).new(['a'])))
40
+
41
+ # JSON::Node
42
+ assert_equal(['a'], JSI::Typelike.as_json(JSI::JSON::Node.new_doc(['a'])))
43
+
44
+ # #as_json
45
+ assert_equal(['a'], JSI::Typelike.as_json(JSONifiable.new(['a'])))
46
+
47
+ # not jsonifiable
48
+ object = Object.new
49
+ err = assert_raises(TypeError) { JSI::Typelike.as_json(object) }
50
+ assert_equal("cannot express object as json: #{object.pretty_inspect.chomp}", err.message)
51
+ end
52
+ end
53
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-27 00:00:00.000000000 Z
11
+ date: 2019-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json-schema
@@ -98,25 +98,26 @@ files:
98
98
  - lib/jsi.rb
99
99
  - lib/jsi/base.rb
100
100
  - lib/jsi/base/to_rb.rb
101
+ - lib/jsi/jsi_coder.rb
101
102
  - lib/jsi/json-schema-fragments.rb
102
103
  - lib/jsi/json.rb
103
104
  - lib/jsi/json/node.rb
105
+ - lib/jsi/json/pointer.rb
104
106
  - lib/jsi/schema.rb
105
- - lib/jsi/schema_instance_json_coder.rb
106
- - lib/jsi/struct_json_coder.rb
107
107
  - lib/jsi/typelike_modules.rb
108
108
  - lib/jsi/util.rb
109
109
  - lib/jsi/version.rb
110
110
  - test/base_array_test.rb
111
111
  - test/base_hash_test.rb
112
112
  - test/base_test.rb
113
+ - test/jsi_coder_test.rb
113
114
  - test/jsi_json_arraynode_test.rb
114
115
  - test/jsi_json_hashnode_test.rb
115
116
  - test/jsi_json_node_test.rb
117
+ - test/jsi_json_pointer_test.rb
116
118
  - test/jsi_test.rb
117
- - test/schema_instance_json_coder_test.rb
119
+ - test/jsi_typelike_as_json_test.rb
118
120
  - test/schema_test.rb
119
- - test/struct_json_coder_test.rb
120
121
  - test/test_helper.rb
121
122
  - test/util_test.rb
122
123
  homepage: https://github.com/notEthan/jsi
@@ -147,12 +148,13 @@ test_files:
147
148
  - test/base_array_test.rb
148
149
  - test/base_hash_test.rb
149
150
  - test/base_test.rb
151
+ - test/jsi_coder_test.rb
150
152
  - test/jsi_json_arraynode_test.rb
151
153
  - test/jsi_json_hashnode_test.rb
152
154
  - test/jsi_json_node_test.rb
155
+ - test/jsi_json_pointer_test.rb
153
156
  - test/jsi_test.rb
154
- - test/schema_instance_json_coder_test.rb
157
+ - test/jsi_typelike_as_json_test.rb
155
158
  - test/schema_test.rb
156
- - test/struct_json_coder_test.rb
157
159
  - test/test_helper.rb
158
160
  - test/util_test.rb
@@ -1,83 +0,0 @@
1
- module JSI
2
- # this is a ActiveRecord serialization class intended to store JSON in the
3
- # database column and expose a ruby class once loaded on a model instance.
4
- # this allows for better ruby idioms to access to properties, and definition
5
- # of related methods on the loaded class.
6
- #
7
- # the first argument, `loaded_class`, is the class which will be used to
8
- # instantiate the column data. properties of the loaded class will correspond
9
- # to keys of the json object in the database.
10
- #
11
- # the column data may be either a single instance of the loaded class
12
- # (represented as one json object) or an array of them (represented as a json
13
- # array of json objects), indicated by the keyword argument `array`.
14
- #
15
- # the column behind the attribute may be an actual JSON column (postgres json
16
- # or jsonb - hstore should work too if you only have string attributes) or a
17
- # serialized string, indicated by the keyword argument `string`.
18
- class ObjectJSONCoder
19
- class Error < StandardError
20
- end
21
- class LoadError < Error
22
- end
23
- class DumpError < Error
24
- end
25
-
26
- def initialize(loaded_class, string: false, array: false, next_coder: nil)
27
- @loaded_class = loaded_class
28
- # this notes the order of the keys as they were in the json, used by dump_object to generate
29
- # json that is equivalent to the json/jsonifiable that came in, so that AR's #changed_attributes
30
- # can tell whether the attribute has been changed.
31
- @loaded_class.send(:attr_accessor, :object_json_coder_keys_order)
32
- @string = string
33
- @array = array
34
- @next_coder = next_coder
35
- end
36
-
37
- def load(column_data)
38
- return nil if column_data.nil?
39
- data = @string ? ::JSON.parse(column_data) : column_data
40
- object = if @array
41
- unless data.respond_to?(:to_ary)
42
- raise TypeError, "expected array-like column data; got: #{data.class}: #{data.inspect}"
43
- end
44
- data.map { |el| load_object(el) }
45
- else
46
- load_object(data)
47
- end
48
- object = @next_coder.load(object) if @next_coder
49
- object
50
- end
51
-
52
- def dump(object)
53
- object = @next_coder.dump(object) if @next_coder
54
- return nil if object.nil?
55
- jsonifiable = begin
56
- if @array
57
- unless object.respond_to?(:to_ary)
58
- raise DumpError, "expected array-like attribute; got: #{object.class}: #{object.inspect}"
59
- end
60
- object.map do |el|
61
- dump_object(el)
62
- end
63
- else
64
- dump_object(object)
65
- end
66
- end
67
- @string ? ::JSON.generate(jsonifiable) : jsonifiable
68
- end
69
- end
70
- # this is a ActiveRecord serialization class intended to store JSON in the
71
- # database column and expose a given JSI::Base subclass once loaded
72
- # on a model instance.
73
- class SchemaInstanceJSONCoder < ObjectJSONCoder
74
- private
75
- def load_object(data)
76
- @loaded_class.new(data)
77
- end
78
-
79
- def dump_object(object)
80
- JSI::Typelike.as_json(object)
81
- end
82
- end
83
- end
@@ -1,30 +0,0 @@
1
- module JSI
2
- # this is a ActiveRecord serialization class intended to store JSON in the
3
- # database column and expose a Struct subclass once loaded on a model instance.
4
- class StructJSONCoder < ObjectJSONCoder
5
- private
6
- def load_object(data)
7
- if data.is_a?(Hash)
8
- good_keys = @loaded_class.members.map(&:to_s)
9
- bad_keys = data.keys - good_keys
10
- unless bad_keys.empty?
11
- raise LoadError, "expected keys #{good_keys}; got unrecognized keys: #{bad_keys}"
12
- end
13
- instance = @loaded_class.new(*@loaded_class.members.map { |m| data[m.to_s] })
14
- instance.object_json_coder_keys_order = data.keys
15
- instance
16
- else
17
- raise LoadError, "expected instance(s) of #{Hash}; got: #{data.class}: #{data.inspect}"
18
- end
19
- end
20
-
21
- def dump_object(object)
22
- if object.is_a?(@loaded_class)
23
- keys = (object.object_json_coder_keys_order || []) | @loaded_class.members.map(&:to_s)
24
- keys.map { |member| {member => object[member]} }.inject({}, &:update)
25
- else
26
- raise TypeError, "expected instance(s) of #{@loaded_class}; got: #{object.class}: #{object.inspect}"
27
- end
28
- end
29
- end
30
- end
@@ -1,121 +0,0 @@
1
- require_relative 'test_helper'
2
-
3
- describe JSI::SchemaInstanceJSONCoder do
4
- let(:schema_instance_class) { JSI.class_for_schema(properties: {foo: {}, bar: {}}) }
5
- let(:options) { {} }
6
- let(:schema_instance_json_coder) { JSI::SchemaInstanceJSONCoder.new(schema_instance_class, options) }
7
- describe 'json' do
8
- describe 'load' do
9
- it 'loads nil' do
10
- assert_nil(schema_instance_json_coder.load(nil))
11
- end
12
- it 'loads a hash' do
13
- assert_equal(schema_instance_class.new('foo' => 'bar'), schema_instance_json_coder.load({"foo" => "bar"}))
14
- end
15
- it 'loads something else' do
16
- assert_equal(schema_instance_class.new([[]]), schema_instance_json_coder.load([[]]))
17
- end
18
- describe 'array' do
19
- let(:options) { {array: true} }
20
- it 'loads an array of hashes' do
21
- data = [{"foo" => "bar"}, {"foo" => "baz"}]
22
- assert_equal([schema_instance_class.new('foo' => 'bar'), schema_instance_class.new('foo' => 'baz')], schema_instance_json_coder.load(data))
23
- end
24
- it 'loads an empty array' do
25
- assert_equal([], schema_instance_json_coder.load([]))
26
- end
27
- it 'loads a not an array' do
28
- assert_raises(TypeError) do
29
- schema_instance_json_coder.load(Object.new)
30
- end
31
- end
32
- end
33
- end
34
- describe 'dump' do
35
- it 'dumps nil' do
36
- assert_nil(schema_instance_json_coder.dump(nil))
37
- end
38
- it 'dumps a schema_instance_class' do
39
- assert_equal({"foo" => "x", "bar" => "y"}, schema_instance_json_coder.dump(schema_instance_class.new(foo: 'x', bar: 'y')))
40
- end
41
- it 'dumps something else' do
42
- assert_raises(TypeError) do
43
- schema_instance_json_coder.dump(Object.new)
44
- end
45
- end
46
- it 'dumps some of the keys of a schema_instance_class after loading in a partial one' do
47
- schema_instance_class = schema_instance_json_coder.load({'foo' => 'who'})
48
- assert_equal({'foo' => 'who'}, schema_instance_json_coder.dump(schema_instance_class))
49
- schema_instance_class.bar = 'whar'
50
- assert_equal({'foo' => 'who', 'bar' => 'whar'}, schema_instance_json_coder.dump(schema_instance_class))
51
- end
52
- describe 'array' do
53
- let(:options) { {array: true} }
54
- it 'dumps an array of schema_instances' do
55
- schema_instances = [schema_instance_class.new(foo: 'x', bar: 'y'), schema_instance_class.new(foo: 'z', bar: 'q')]
56
- assert_equal([{"foo" => "x", "bar" => "y"}, {"foo" => "z", "bar" => "q"}], schema_instance_json_coder.dump(schema_instances))
57
- end
58
- end
59
- end
60
- end
61
- describe 'string' do
62
- let(:options) { {string: true} }
63
- describe 'load' do
64
- it 'loads nil' do
65
- assert_nil(schema_instance_json_coder.load(nil))
66
- end
67
- it 'loads a hash' do
68
- assert_equal(schema_instance_class.new('foo' => 'bar'), schema_instance_json_coder.load('{"foo": "bar"}'))
69
- end
70
- it 'loads something else' do
71
- assert_equal(schema_instance_class.new([[]]), schema_instance_json_coder.load('[[]]'))
72
- end
73
- it 'loads something that is not a json string' do
74
- assert_raises(::JSON::ParserError) do
75
- schema_instance_json_coder.load('??')
76
- end
77
- end
78
- describe 'array' do
79
- let(:options) { {string: true, array: true} }
80
- it 'loads an array of hashes' do
81
- data = '[{"foo": "bar"}, {"foo": "baz"}]'
82
- assert_equal([schema_instance_class.new('foo' => 'bar'), schema_instance_class.new('foo' => 'baz')], schema_instance_json_coder.load(data))
83
- end
84
- it 'loads an empty array' do
85
- assert_equal([], schema_instance_json_coder.load('[]'))
86
- end
87
- it 'loads a not an array' do
88
- assert_raises(TypeError) do
89
- schema_instance_json_coder.load('{}')
90
- end
91
- end
92
- end
93
- end
94
- describe 'dump' do
95
- it 'dumps nil' do
96
- assert_nil(schema_instance_json_coder.dump(nil))
97
- end
98
- it 'dumps a schema_instance_class' do
99
- assert_equal('{"foo":"x","bar":"y"}', schema_instance_json_coder.dump(schema_instance_class.new(foo: 'x', bar: 'y')))
100
- end
101
- it 'dumps something else' do
102
- assert_raises(TypeError) do
103
- schema_instance_json_coder.dump(Object.new)
104
- end
105
- end
106
- it 'dumps some of the keys of a schema_instance_class after loading in a partial one' do
107
- schema_instance_class = schema_instance_json_coder.load('{"foo": "who"}')
108
- assert_equal("{\"foo\":\"who\"}", schema_instance_json_coder.dump(schema_instance_class))
109
- schema_instance_class.bar = 'whar'
110
- assert_equal("{\"foo\":\"who\",\"bar\":\"whar\"}", schema_instance_json_coder.dump(schema_instance_class))
111
- end
112
- describe 'array' do
113
- let(:options) { {string: true, array: true} }
114
- it 'dumps an array of schema_instances' do
115
- schema_instances = [schema_instance_class.new(foo: 'x', bar: 'y'), schema_instance_class.new(foo: 'z', bar: 'q')]
116
- assert_equal('[{"foo":"x","bar":"y"},{"foo":"z","bar":"q"}]', schema_instance_json_coder.dump(schema_instances))
117
- end
118
- end
119
- end
120
- end
121
- end
@@ -1,130 +0,0 @@
1
- require_relative 'test_helper'
2
-
3
- describe JSI::StructJSONCoder do
4
- let(:struct) { Struct.new(:foo, :bar) }
5
- let(:options) { {} }
6
- let(:struct_json_coder) { JSI::StructJSONCoder.new(struct, options) }
7
- describe 'json' do
8
- describe 'load' do
9
- it 'loads nil' do
10
- assert_nil(struct_json_coder.load(nil))
11
- end
12
- it 'loads a hash' do
13
- assert_equal(struct.new('bar'), struct_json_coder.load({"foo" => "bar"}))
14
- end
15
- it 'loads something else' do
16
- assert_raises(JSI::StructJSONCoder::LoadError) do
17
- struct_json_coder.load([[]])
18
- end
19
- end
20
- it 'loads unrecognized keys' do
21
- assert_raises(JSI::StructJSONCoder::LoadError) do
22
- struct_json_coder.load({"uhoh" => "spaghettio"})
23
- end
24
- end
25
- describe 'array' do
26
- let(:options) { {array: true} }
27
- it 'loads an array of hashes' do
28
- data = [{"foo" => "bar"}, {"foo" => "baz"}]
29
- assert_equal([struct.new('bar'), struct.new('baz')], struct_json_coder.load(data))
30
- end
31
- it 'loads an empty array' do
32
- assert_equal([], struct_json_coder.load([]))
33
- end
34
- end
35
- end
36
- describe 'dump' do
37
- it 'dumps nil' do
38
- assert_nil(struct_json_coder.dump(nil))
39
- end
40
- it 'dumps a struct' do
41
- assert_equal({"foo" => "x", "bar" => "y"}, struct_json_coder.dump(struct.new('x', 'y')))
42
- end
43
- it 'dumps something else' do
44
- assert_raises(TypeError) do
45
- struct_json_coder.dump(Object.new)
46
- end
47
- end
48
- it 'dumps all the keys of a struct after loading in a partial one' do
49
- struct = struct_json_coder.load({'foo' => 'who'})
50
- assert_equal({'foo' => 'who', 'bar' => nil}, struct_json_coder.dump(struct))
51
- struct.bar = 'whar'
52
- assert_equal({'foo' => 'who', 'bar' => 'whar'}, struct_json_coder.dump(struct))
53
- end
54
- describe 'array' do
55
- let(:options) { {array: true} }
56
- it 'dumps an array of structs' do
57
- structs = [struct.new('x', 'y'), struct.new('z', 'q')]
58
- assert_equal([{"foo" => "x", "bar" => "y"}, {"foo" => "z", "bar" => "q"}], struct_json_coder.dump(structs))
59
- end
60
- end
61
- end
62
- end
63
- describe 'string' do
64
- let(:options) { {string: true} }
65
- describe 'load' do
66
- it 'loads nil' do
67
- assert_nil(struct_json_coder.load(nil))
68
- end
69
- it 'loads a hash' do
70
- assert_equal(struct.new('bar'), struct_json_coder.load('{"foo": "bar"}'))
71
- end
72
- it 'loads something else' do
73
- assert_raises(JSI::StructJSONCoder::LoadError) do
74
- struct_json_coder.load('[[]]')
75
- end
76
- end
77
- it 'loads something that is not a json string' do
78
- assert_raises(JSON::ParserError) do
79
- struct_json_coder.load('??')
80
- end
81
- end
82
- it 'loads unrecognized keys' do
83
- assert_raises(JSI::StructJSONCoder::LoadError) do
84
- struct_json_coder.load('{"uhoh": "spaghettio"}')
85
- end
86
- end
87
- describe 'array' do
88
- let(:options) { {string: true, array: true} }
89
- it 'loads an array of hashes' do
90
- data = '[{"foo": "bar"}, {"foo": "baz"}]'
91
- assert_equal([struct.new('bar'), struct.new('baz')], struct_json_coder.load(data))
92
- end
93
- it 'loads an empty array' do
94
- assert_equal([], struct_json_coder.load('[]'))
95
- end
96
- it 'loads a not an array' do
97
- assert_raises(TypeError) do
98
- struct_json_coder.load('{}')
99
- end
100
- end
101
- end
102
- end
103
- describe 'dump' do
104
- it 'dumps nil' do
105
- assert_nil(struct_json_coder.dump(nil))
106
- end
107
- it 'dumps a struct' do
108
- assert_equal('{"foo":"x","bar":"y"}', struct_json_coder.dump(struct.new('x', 'y')))
109
- end
110
- it 'dumps something else' do
111
- assert_raises(TypeError) do
112
- struct_json_coder.dump(Object.new)
113
- end
114
- end
115
- it 'dumps all the keys of a struct after loading in a partial one' do
116
- struct = struct_json_coder.load('{"foo": "who"}')
117
- assert_equal("{\"foo\":\"who\",\"bar\":null}", struct_json_coder.dump(struct))
118
- struct.bar = 'whar'
119
- assert_equal("{\"foo\":\"who\",\"bar\":\"whar\"}", struct_json_coder.dump(struct))
120
- end
121
- describe 'array' do
122
- let(:options) { {string: true, array: true} }
123
- it 'dumps an array of structs' do
124
- structs = [struct.new('x', 'y'), struct.new('z', 'q')]
125
- assert_equal('[{"foo":"x","bar":"y"},{"foo":"z","bar":"q"}]', struct_json_coder.dump(structs))
126
- end
127
- end
128
- end
129
- end
130
- end