jsonapi-deserializable 0.1.1.beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +116 -0
- data/lib/jsonapi/deserializable.rb +3 -0
- data/lib/jsonapi/deserializable/document_validator.rb +200 -0
- data/lib/jsonapi/deserializable/exceptions.rb +7 -0
- data/lib/jsonapi/deserializable/relationship.rb +38 -0
- data/lib/jsonapi/deserializable/relationship_dsl.rb +31 -0
- data/lib/jsonapi/deserializable/resource.rb +44 -0
- data/lib/jsonapi/deserializable/resource/dsl.rb +77 -0
- data/lib/jsonapi/deserializable/resource_dsl.rb +74 -0
- data/lib/jsonapi/deserializable/validations.rb +101 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 93ceb39a5f3d032a99748170ac818e096e691d05
|
4
|
+
data.tar.gz: adfdd698f25de948e225c87087f850376d900f09
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 52f05a26b475cf2afa912ab7e3e207f4d925c32e58c5ba99edfc8a52685c5c536ecdc8d8e0e7e2d06d44916be0b1af383ba035339c87754824a0988cf93ce48c
|
7
|
+
data.tar.gz: aa7bfa1f75f1ec8bd00dc69025dffbc7127874b86f946dd1489798837727630df4a15a9410cb8047a26c195b6b6082e185bb3d9770cc586668d9b52f274c5769
|
data/README.md
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# jsonapi-deserializable
|
2
|
+
Ruby gem for validating and deserializing [JSON API](http://jsonapi.org)
|
3
|
+
payloads into custom hashes.
|
4
|
+
Built upon the [jsonapi-validations](https://github.com/beauby/jsonapi/tree/master/validations)
|
5
|
+
gem.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
```ruby
|
9
|
+
# In Gemfile
|
10
|
+
gem 'jsonapi-deserializable'
|
11
|
+
```
|
12
|
+
then
|
13
|
+
```
|
14
|
+
$ bundle
|
15
|
+
```
|
16
|
+
or manually via
|
17
|
+
```
|
18
|
+
$ gem install jsonapi-deserializable
|
19
|
+
```
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
First, require the gem:
|
24
|
+
```ruby
|
25
|
+
require 'jsonapi/deserializable'
|
26
|
+
```
|
27
|
+
|
28
|
+
Then, define some resource/relationship classes:
|
29
|
+
```ruby
|
30
|
+
class DeserializableUser < JSONAPI::Deserializable::Resource
|
31
|
+
# List of required attributes / has_many/has_one relationships.
|
32
|
+
# This directive is not mandatory. If not declared, no field
|
33
|
+
# will be required.
|
34
|
+
required do
|
35
|
+
id # Optional, require an id for the primary resource.
|
36
|
+
|
37
|
+
type :users # Optional, force a type for the primary resource.
|
38
|
+
# or, still optional, force a set of allowed types for the primary resource:
|
39
|
+
types [:users, :superusers] # Optional,
|
40
|
+
|
41
|
+
attribute :name
|
42
|
+
has_one :sponsor
|
43
|
+
# or, optionally, spcecify a type for the relationship target:
|
44
|
+
has_one :sponsor, :users
|
45
|
+
end
|
46
|
+
|
47
|
+
# List of optional attributes / has_many/has_one relationships.
|
48
|
+
# This directive is not mandatory. If not declared, all fields
|
49
|
+
# will be allowed. If declared, all fields that are not within
|
50
|
+
# eitheroptional or required will be rejected.
|
51
|
+
optional do
|
52
|
+
attribute :address
|
53
|
+
has_many :posts
|
54
|
+
# or, optionally, specify a set of allowed types for the primary resource:
|
55
|
+
has_many :posts, [:posts, :blogs]
|
56
|
+
end
|
57
|
+
|
58
|
+
## The actual fields of the generated hash.
|
59
|
+
# `attribute` is a shorthand for `field(key) { @attributes.send(key) }`.
|
60
|
+
attribute :address
|
61
|
+
|
62
|
+
field :id do
|
63
|
+
@data.id
|
64
|
+
end
|
65
|
+
|
66
|
+
# `field` is the standard method for defining a key on the result hash.
|
67
|
+
field :username do
|
68
|
+
@document.data.attributes.name
|
69
|
+
end
|
70
|
+
|
71
|
+
field :post_ids do
|
72
|
+
@relationships.posts.data.map(&:id)
|
73
|
+
end
|
74
|
+
|
75
|
+
field :sponsor_id do
|
76
|
+
@relationships.sponsor.data && @relationships.sponsor.data.id
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
Finally, build your hash from the deserializable resource:
|
81
|
+
```ruby
|
82
|
+
payload = {
|
83
|
+
'data' => {
|
84
|
+
'id' => '1',
|
85
|
+
'type' => 'users',
|
86
|
+
'attributes' => {
|
87
|
+
'name' => 'Name',
|
88
|
+
'address' => 'Address'
|
89
|
+
},
|
90
|
+
'relationships' => {
|
91
|
+
'sponsor' => {
|
92
|
+
'data' => { 'type' => 'users', 'id' => '1337' }
|
93
|
+
},
|
94
|
+
'posts' => {
|
95
|
+
'data' => [
|
96
|
+
{ 'type' => 'posts', 'id' => '123' },
|
97
|
+
{ 'type' => 'posts', 'id' => '234' },
|
98
|
+
{ 'type' => 'posts', 'id' => '345' }
|
99
|
+
]
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
DeserializableUser.new(payload).to_h
|
106
|
+
# => {
|
107
|
+
# username: 'Name',
|
108
|
+
# address: 'Address',
|
109
|
+
# sponsor_id: '1337',
|
110
|
+
# post_ids: ['123', '234', '345']
|
111
|
+
# }
|
112
|
+
```
|
113
|
+
|
114
|
+
## License
|
115
|
+
|
116
|
+
jsonapi-deserializable is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'jsonapi/deserializable/field_list'
|
2
|
+
|
3
|
+
module JSONAPI
|
4
|
+
module Deserializable
|
5
|
+
class DocumentValidator
|
6
|
+
def self.validate!(payload, required_list, optional_list)
|
7
|
+
new(payload, required_list, optional_list).validate!
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(document, required, optional)
|
11
|
+
whitelist = {
|
12
|
+
id: :optional,
|
13
|
+
types: [],
|
14
|
+
attributes: {
|
15
|
+
foo: :required,
|
16
|
+
bar: :optional
|
17
|
+
},
|
18
|
+
relationships: {
|
19
|
+
foobar: :required,
|
20
|
+
barfoo: {
|
21
|
+
required: true,
|
22
|
+
types: [:baz],
|
23
|
+
arity: :to_many
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
arities = {
|
30
|
+
foobar: :to_many,
|
31
|
+
barfoo: :to_one
|
32
|
+
}
|
33
|
+
types = {
|
34
|
+
primary: [],
|
35
|
+
relationships: {
|
36
|
+
foobar: [:baz],
|
37
|
+
barfoo: [:bazbaz]
|
38
|
+
}
|
39
|
+
}
|
40
|
+
required: true,
|
41
|
+
types: [:baz],
|
42
|
+
arity: :to_many
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
@document = document
|
47
|
+
@required = required || {}
|
48
|
+
@optional = optional
|
49
|
+
@fields = {}
|
50
|
+
@fields[:primary_id] = true if @required[:primary_id]
|
51
|
+
primary_types = @required[:primary_types]
|
52
|
+
@fields[:primary_types] = primary_types if primary_types
|
53
|
+
@fields[:attributes] = @required[:attributes].dup
|
54
|
+
@fields[:relationships] = @required[:relationships].dup
|
55
|
+
if @optional
|
56
|
+
@fields[:primary_id] = true if @optional[:primary_id]
|
57
|
+
primary_types = @optional[:primary_types]
|
58
|
+
@fields[:primary_types] = primary_types if primary_types
|
59
|
+
@fields[:attributes].merge
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate!
|
64
|
+
raise INVALID_DOCUMENT unless @document.data
|
65
|
+
@data = @document.data
|
66
|
+
if @data.respond_to?(:each)
|
67
|
+
raise INVALID_DOCUMENT, 'The request MUST include a single resource' \
|
68
|
+
' object as primary data'
|
69
|
+
end
|
70
|
+
@attributes = @data.attributes
|
71
|
+
@relationships = @data.relationships
|
72
|
+
validate_primary!
|
73
|
+
validate_fields!
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_primary!
|
77
|
+
validate_id!
|
78
|
+
validate_type!
|
79
|
+
end
|
80
|
+
|
81
|
+
def id_required?
|
82
|
+
@required[:primary_id]
|
83
|
+
end
|
84
|
+
|
85
|
+
def id_permitted?
|
86
|
+
@fields[:primary_id]
|
87
|
+
end
|
88
|
+
|
89
|
+
def validate_id!
|
90
|
+
if @data.id.nil?
|
91
|
+
raise INVALID_DOCUMENT,
|
92
|
+
'Expected id for primary resource' if id_required?
|
93
|
+
else
|
94
|
+
raise INVALID_DOCUMENT,
|
95
|
+
'Unexpected id for primary resource' unless id_permitted?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def type_valid?
|
100
|
+
return false if @required &&
|
101
|
+
@required[:primary_types] &&
|
102
|
+
!@required[:primary_types].include?(@data.type.to_sym)
|
103
|
+
return false if @optional &&
|
104
|
+
@optional[:primary_types] &&
|
105
|
+
!@optional[:primary_types].include?(@data.type.to_sym)
|
106
|
+
true
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate_type!
|
110
|
+
return if type_valid?
|
111
|
+
raise INVALID_DOCUMENT,
|
112
|
+
"Unexpected type #{@data.type} for primary resource"
|
113
|
+
end
|
114
|
+
|
115
|
+
def validate_fields!
|
116
|
+
validate_attributes!
|
117
|
+
validate_relationships!
|
118
|
+
end
|
119
|
+
|
120
|
+
def attr_permitted?(attr_key)
|
121
|
+
return true unless @optional
|
122
|
+
return true if @optional[:attributes].include?(attr_key.to_sym)
|
123
|
+
@required && @required[:attributes].include?(attr_key.to_sym)
|
124
|
+
end
|
125
|
+
|
126
|
+
def validate_attributes!
|
127
|
+
@attributes.keys.each do |attr_key|
|
128
|
+
next if attr_permitted?(attr_key)
|
129
|
+
raise INVALID_DOCUMENT, "Unexpected attribute #{attr_key}"
|
130
|
+
end
|
131
|
+
return unless @required
|
132
|
+
@required[:attributes].each do |attr_key|
|
133
|
+
next if @attributes.defined?(attr_key)
|
134
|
+
raise INVALID_DOCUMENT, "Expected attribute #{attr_key}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def rel_permitted?(rel_key)
|
139
|
+
return true unless @optional
|
140
|
+
return true if @optional[:relationships].key?(rel_key.to_sym)
|
141
|
+
@required && @required[:relationships].key?(rel_key.to_sym)
|
142
|
+
end
|
143
|
+
|
144
|
+
def validate_relationships!
|
145
|
+
@relationships.keys.each do |rel_key|
|
146
|
+
next if rel_permitted?(rel_key)
|
147
|
+
raise INVALID_DOCUMENT, "Unexpected relationship #{rel_key}"
|
148
|
+
end
|
149
|
+
return unless @required
|
150
|
+
@required[:relationships].keys.each do |rel_key|
|
151
|
+
next if @relationships.defined?(rel_key)
|
152
|
+
raise INVALID_DOCUMENT, "Expected relationship #{rel_key}"
|
153
|
+
end
|
154
|
+
validate_relationship_types!
|
155
|
+
end
|
156
|
+
|
157
|
+
def validate_relationship_types!
|
158
|
+
rels = {}
|
159
|
+
rels.merge!(@required.relationships) if @required
|
160
|
+
rels.merge(@optional.relationships) if @optional
|
161
|
+
rels.each do |key, hash|
|
162
|
+
rel = @relationships[key.to_s]
|
163
|
+
unless rel.data
|
164
|
+
raise INVALID_DOCUMENT, "Expected data for relationship #{key}"
|
165
|
+
end
|
166
|
+
|
167
|
+
if hash[:arity] == :to_one
|
168
|
+
validate_to_one_relationship_type!(rel, hash[:types])
|
169
|
+
else
|
170
|
+
validate_to_many_relationship_type!(rel, hash[:types])
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def validate_to_one_relationship_type!(rel, types)
|
176
|
+
if rel.collection?
|
177
|
+
raise INVALID_DOCUMENT,
|
178
|
+
"Expected relationship #{key} to be has_one"
|
179
|
+
end
|
180
|
+
return unless types && !types.include?(rel.data.type.to_sym)
|
181
|
+
raise INVALID_DOCUMENT, "Unexpected type: #{rel.data.type} for " \
|
182
|
+
"relationship #{key}"
|
183
|
+
end
|
184
|
+
|
185
|
+
def validate_to_many_relationship_type!(rel, types)
|
186
|
+
unless rel.collection?
|
187
|
+
raise INVALID_DOCUMENT,
|
188
|
+
"Expected relationship #{key} to be has_many"
|
189
|
+
end
|
190
|
+
return unless types
|
191
|
+
rel.data.each do |ri|
|
192
|
+
unless types.include?(ri.type.to_sym)
|
193
|
+
raise INVALID_DOCUMENT, "Unexpected type: #{ri.type} for " \
|
194
|
+
"relationship #{key}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'jsonapi/deserializable/exceptions'
|
2
|
+
require 'jsonapi/deserializable/relationship_dsl'
|
3
|
+
|
4
|
+
module JSONAPI
|
5
|
+
module Deserializable
|
6
|
+
class Relationship
|
7
|
+
include RelationshipDSL
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :field_blocks, :validations
|
11
|
+
end
|
12
|
+
|
13
|
+
self.field_blocks = {}
|
14
|
+
self.validations = {}
|
15
|
+
|
16
|
+
def self.inherited(klass)
|
17
|
+
klass.field_blocks = field_blocks.dup
|
18
|
+
klass.validations = Marshal.load(Marshal.dump(validations))
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(payload)
|
22
|
+
JSONAPI.validate_relationship!(payload, validations)
|
23
|
+
@document = payload
|
24
|
+
@data = payload['data']
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_h
|
28
|
+
return nil if @data.nil?
|
29
|
+
return @_hash if @_hash
|
30
|
+
@_hash = {}
|
31
|
+
self.class.field_blocks.each do |key, block|
|
32
|
+
@_hash[key] = instance_eval(&block)
|
33
|
+
end
|
34
|
+
@_hash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Deserializable
|
3
|
+
module RelationshipDSL
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def has_one
|
10
|
+
validations[:kind] = :has_one
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_many
|
14
|
+
validations[:kind] = :has_many
|
15
|
+
end
|
16
|
+
|
17
|
+
def type(value)
|
18
|
+
(validations[:types] ||= []) << value
|
19
|
+
end
|
20
|
+
|
21
|
+
def types(values)
|
22
|
+
(validations[:types] ||= []).concat(values)
|
23
|
+
end
|
24
|
+
|
25
|
+
def field(key, &block)
|
26
|
+
field_blocks[key] = block
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'jsonapi/validations'
|
2
|
+
require 'jsonapi/deserializable/exceptions'
|
3
|
+
require 'jsonapi/deserializable/resource_dsl'
|
4
|
+
|
5
|
+
module JSONAPI
|
6
|
+
module Deserializable
|
7
|
+
class Resource
|
8
|
+
include ResourceDSL
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :field_blocks, :validations
|
12
|
+
end
|
13
|
+
|
14
|
+
self.field_blocks = {}
|
15
|
+
self.validations = {}
|
16
|
+
|
17
|
+
def self.inherited(klass)
|
18
|
+
super
|
19
|
+
klass.field_blocks = field_blocks.dup
|
20
|
+
klass.validations = Marshal.load(Marshal.dump(validations))
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(payload)
|
24
|
+
JSONAPI.validate_resource!(payload, self.class.validations)
|
25
|
+
@document = payload
|
26
|
+
@data = @document['data']
|
27
|
+
@attributes = @data['attributes']
|
28
|
+
@relationships = @data['relationships']
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_h
|
32
|
+
return @_hash if @_hash
|
33
|
+
|
34
|
+
@_hash = {}
|
35
|
+
@_hash[:_payload] = @document
|
36
|
+
self.class.field_blocks.map do |k, v|
|
37
|
+
@_hash[k] = instance_eval(&v)
|
38
|
+
end
|
39
|
+
|
40
|
+
@_hash
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Deserializable
|
3
|
+
module ResourceDSL
|
4
|
+
attr_accessor :field_blocks, :validations
|
5
|
+
|
6
|
+
def required(&block)
|
7
|
+
add_validations!(FieldList.new(:required, &block).to_h)
|
8
|
+
end
|
9
|
+
|
10
|
+
def optional(&block)
|
11
|
+
add_validations!(FieldList.new(:optional, &block).to_h)
|
12
|
+
end
|
13
|
+
|
14
|
+
def id
|
15
|
+
field_blocks[:id] = proc { @data['id'] }
|
16
|
+
end
|
17
|
+
|
18
|
+
def field(key, &block)
|
19
|
+
field_blocks[key] = block
|
20
|
+
end
|
21
|
+
|
22
|
+
def attribute(key, opts = {})
|
23
|
+
hash_key = (opts[:key] || key).to_s
|
24
|
+
field_blocks[key] = proc { @attributes[hash_key] }
|
25
|
+
end
|
26
|
+
|
27
|
+
def has_many_ids(key, opts = {})
|
28
|
+
hash_key = (opts[:key] || key).to_s
|
29
|
+
field_blocks[key] = proc do
|
30
|
+
@relationships[hash_key]['data'].map { |ri| ri['id'] }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_many_types(key, opts = {})
|
35
|
+
hash_key = (opts[:key] || key).to_s
|
36
|
+
field_blocks[key] = proc do
|
37
|
+
@relationships[hash_key]['data'].map { |ri| ri['type'] }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def has_many_ids(key, opts = {})
|
42
|
+
hash_key = (opts[:key] || key).to_s
|
43
|
+
@relationships[hash_key]['data']['id']
|
44
|
+
end
|
45
|
+
|
46
|
+
def has_many_ids(key, opts = {})
|
47
|
+
hash_key = (opts[:key] || key).to_s
|
48
|
+
@relationships[hash_key]['data']['type']
|
49
|
+
end
|
50
|
+
|
51
|
+
self.field_blocks = {}
|
52
|
+
self.validations = {}
|
53
|
+
|
54
|
+
def self.inherited(klass)
|
55
|
+
super
|
56
|
+
klass.field_blocks = field_blocks.dup
|
57
|
+
klass.validations = Marshal.load(Marshal.dump(validations))
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def add_validations!(hash)
|
63
|
+
validations[:permitted] = hash[:permitted] if hash[:permitted]
|
64
|
+
validations[:required] = hash[:required] if hash[:required]
|
65
|
+
return unless hash[:types]
|
66
|
+
validations[:types] ||= {}
|
67
|
+
if hash[:types][:primary]
|
68
|
+
validations[:types][:primary] = hash[:types][:primary]
|
69
|
+
end
|
70
|
+
return unless hash[:types][:relationships]
|
71
|
+
validations[:types][:relationships] ||= {}
|
72
|
+
validations[:types][:relationships]
|
73
|
+
.merge!(hash[:types][:relationships])
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'jsonapi/deserializable/validations'
|
2
|
+
|
3
|
+
module JSONAPI
|
4
|
+
module Deserializable
|
5
|
+
module ResourceDSL
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def required(&block)
|
12
|
+
add_validations!(Validations.new(:required, &block).to_h)
|
13
|
+
end
|
14
|
+
|
15
|
+
def optional(&block)
|
16
|
+
add_validations!(Validations.new(:optional, &block).to_h)
|
17
|
+
end
|
18
|
+
|
19
|
+
def field(key, &block)
|
20
|
+
field_blocks[key] = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def id
|
24
|
+
field(:id) { @data['id'] }
|
25
|
+
end
|
26
|
+
|
27
|
+
def attribute(key, opts = {})
|
28
|
+
hash_key = (opts[:key] || key).to_s
|
29
|
+
field(key) { @attributes[hash_key] }
|
30
|
+
end
|
31
|
+
|
32
|
+
def has_many_ids(key, opts = {})
|
33
|
+
hash_key = (opts[:key] || key).to_s
|
34
|
+
field(key) do
|
35
|
+
@relationships[hash_key]['data'].map { |ri| ri['id'] }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_many_types(key, opts = {})
|
40
|
+
hash_key = (opts[:key] || key).to_s
|
41
|
+
field(key) do
|
42
|
+
@relationships[hash_key]['data'].map { |ri| ri['type'] }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def has_one_id(key, opts = {})
|
47
|
+
hash_key = (opts[:key] || key).to_s
|
48
|
+
field(key) { @relationships[hash_key]['data']['id'] }
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_one_type(key, opts = {})
|
52
|
+
hash_key = (opts[:key] || key).to_s
|
53
|
+
field(key) { @relationships[hash_key]['data']['type'] }
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def add_validations!(hash)
|
59
|
+
validations[:permitted] = hash[:permitted] if hash[:permitted]
|
60
|
+
validations[:required] = hash[:required] if hash[:required]
|
61
|
+
return unless hash[:types]
|
62
|
+
validations[:types] ||= {}
|
63
|
+
if hash[:types][:primary]
|
64
|
+
validations[:types][:primary] = hash[:types][:primary]
|
65
|
+
end
|
66
|
+
return unless hash[:types][:relationships]
|
67
|
+
validations[:types][:relationships] ||= {}
|
68
|
+
validations[:types][:relationships]
|
69
|
+
.merge!(hash[:types][:relationships])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Deserializable
|
3
|
+
class Validations
|
4
|
+
def initialize(validation_type, &block)
|
5
|
+
@validation_type = validation_type
|
6
|
+
@hash = {
|
7
|
+
@validation_type => {
|
8
|
+
attributes: [],
|
9
|
+
relationships: []
|
10
|
+
},
|
11
|
+
types: {
|
12
|
+
relationships: {}
|
13
|
+
}
|
14
|
+
}
|
15
|
+
instance_eval(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_h
|
19
|
+
@hash
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Define whether the +id+ of the primary resource should be part of
|
25
|
+
# this list.
|
26
|
+
def id
|
27
|
+
validations_hash[:id] = true
|
28
|
+
end
|
29
|
+
|
30
|
+
# Define the allowed type for the primary resource.
|
31
|
+
# @param [Symbol] value The value of the type.
|
32
|
+
def type(value)
|
33
|
+
types_hash[:primary] = Array(value)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Define the allowed type for the primary resource.
|
37
|
+
# @param [Array<Symbol>] values List of allowed values of the type.
|
38
|
+
def types(values)
|
39
|
+
types_hash[:primary] = values
|
40
|
+
end
|
41
|
+
|
42
|
+
# Define an attribute with given key.
|
43
|
+
# @param [Symbol] key The key of the attribute in the payload.
|
44
|
+
def attribute(key)
|
45
|
+
validations_hash[:attributes] << key
|
46
|
+
end
|
47
|
+
|
48
|
+
# TODO(beauby): Decide whether type: 'users' / types: [...] is better.
|
49
|
+
#
|
50
|
+
# @overload has_one(key)
|
51
|
+
# Define a has_one relationship with given key.
|
52
|
+
# @param [Symbol] key The key of the relationship in the payload.
|
53
|
+
#
|
54
|
+
# @overload has_one(key, type)
|
55
|
+
# Define a has_one relationship with given key.
|
56
|
+
# @param [Symbol] key The key of the relationship in the payload.
|
57
|
+
# @param [Symbol] type The expected type of the relationship value.
|
58
|
+
#
|
59
|
+
# @overload has_one(key, types)
|
60
|
+
# Define a has_one relationship with given key.
|
61
|
+
# @param [Symbol] key The key of the relationship in the payload.
|
62
|
+
# @param [Array<Symbol>] type List of acceptable types for the
|
63
|
+
# relationship value.
|
64
|
+
def has_many(key, types = nil)
|
65
|
+
validations_hash[:relationships] << key
|
66
|
+
types_hash[:relationships][key] = { kind: :has_many }
|
67
|
+
return unless types
|
68
|
+
types_hash[:relationships][key][:types] = Array(types)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @overload has_one(key)
|
72
|
+
# Define a has_one relationship with given key.
|
73
|
+
# @param [Symbol] key The key of the relationship in the payload.
|
74
|
+
#
|
75
|
+
# @overload has_one(key, type)
|
76
|
+
# Define a has_one relationship with given key.
|
77
|
+
# @param [Symbol] key The key of the relationship in the payload.
|
78
|
+
# @param [Symbol] type The expected type of the relationship value.
|
79
|
+
#
|
80
|
+
# @overload has_one(key, types)
|
81
|
+
# Define a has_one relationship with given key.
|
82
|
+
# @param [Symbol] key The key of the relationship in the payload.
|
83
|
+
# @param [Array<Symbol>] type List of acceptable types for the
|
84
|
+
# relationship value.
|
85
|
+
def has_one(key, types = nil)
|
86
|
+
validations_hash[:relationships] << key
|
87
|
+
types_hash[:relationships][key] = { kind: :has_one }
|
88
|
+
return unless types
|
89
|
+
types_hash[:relationships][key][:types] = Array(types)
|
90
|
+
end
|
91
|
+
|
92
|
+
def validations_hash
|
93
|
+
@hash[@validation_type]
|
94
|
+
end
|
95
|
+
|
96
|
+
def types_hash
|
97
|
+
@hash[:types]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jsonapi-deserializable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1.beta2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lucas Hosseini
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jsonapi-validations
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.9'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.9'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.4'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.4'
|
55
|
+
description: DSL for validating incoming JSON API payloads and building custom objects
|
56
|
+
out of them.
|
57
|
+
email: lucas.hosseini@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- README.md
|
63
|
+
- lib/jsonapi/deserializable.rb
|
64
|
+
- lib/jsonapi/deserializable/document_validator.rb
|
65
|
+
- lib/jsonapi/deserializable/exceptions.rb
|
66
|
+
- lib/jsonapi/deserializable/relationship.rb
|
67
|
+
- lib/jsonapi/deserializable/relationship_dsl.rb
|
68
|
+
- lib/jsonapi/deserializable/resource.rb
|
69
|
+
- lib/jsonapi/deserializable/resource/dsl.rb
|
70
|
+
- lib/jsonapi/deserializable/resource_dsl.rb
|
71
|
+
- lib/jsonapi/deserializable/validations.rb
|
72
|
+
homepage: https://github.com/beauby/jsonapi-deserializable
|
73
|
+
licenses:
|
74
|
+
- MIT
|
75
|
+
metadata: {}
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.3.1
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 2.5.1
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: Deserialization of JSONAPI payloads.
|
96
|
+
test_files: []
|