jsonapi-deserializable 0.1.1.beta2 → 0.1.1.beta3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +51 -62
- data/lib/jsonapi/deserializable.rb +0 -1
- data/lib/jsonapi/deserializable/relationship.rb +27 -14
- data/lib/jsonapi/deserializable/relationship_dsl.rb +6 -16
- data/lib/jsonapi/deserializable/resource.rb +82 -16
- data/lib/jsonapi/deserializable/resource_dsl.rb +17 -52
- metadata +2 -19
- data/lib/jsonapi/deserializable/document_validator.rb +0 -200
- data/lib/jsonapi/deserializable/exceptions.rb +0 -7
- data/lib/jsonapi/deserializable/validations.rb +0 -101
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3004871b4ea0f17caaeab2c1f000c632337c486
|
4
|
+
data.tar.gz: 4eb06414674e45c45fc3d276ffa966bc97313ae7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdf14b9d40b30ea331a01b10212be3b5582785e45634ab7e49e2d81957a4408af6ce838d05dc27e9d86828a704cef1a2eb23e5331d2c13d4fa131829c6a4c03e
|
7
|
+
data.tar.gz: ee5342f0d5444e68773e504353000090dc71117fba3eee3a0af21399c32836085eab9f8076dd70a2cfe54385a85f9140c692007199f12b70ef60315ca76cad64
|
data/README.md
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
# jsonapi-deserializable
|
2
|
-
Ruby gem for
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
Ruby gem for deserializing [JSON API](http://jsonapi.org) payloads into custom
|
3
|
+
hashes.
|
4
|
+
|
5
|
+
## Status
|
6
|
+
|
7
|
+
[![Gem Version](https://badge.fury.io/rb/jsonapi-deserializable.svg)](https://badge.fury.io/rb/jsonapi-deserializable)
|
8
|
+
[![Build Status](https://secure.travis-ci.org/beauby/jsonapi-deserializable.svg?branch=master)](http://travis-ci.org/beauby/jsonapi-deserializable?branch=master)
|
6
9
|
|
7
10
|
## Installation
|
8
11
|
```ruby
|
@@ -26,54 +29,23 @@ require 'jsonapi/deserializable'
|
|
26
29
|
```
|
27
30
|
|
28
31
|
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
32
|
|
66
|
-
|
67
|
-
field :username do
|
68
|
-
@document.data.attributes.name
|
69
|
-
end
|
33
|
+
### Resources
|
70
34
|
|
71
|
-
|
72
|
-
|
35
|
+
```ruby
|
36
|
+
class DeserializableCreatePost < JSONAPI::Deserializable::Resource
|
37
|
+
type
|
38
|
+
attribute :title
|
39
|
+
attribute :date { |date| field date: DateTime.parse(date) }
|
40
|
+
has_one :author do |rel, id, type|
|
41
|
+
field author_id: id
|
42
|
+
field author_type: type
|
73
43
|
end
|
74
|
-
|
75
|
-
|
76
|
-
|
44
|
+
has_many :comments do |rel, ids, types|
|
45
|
+
field comment_ids: ids
|
46
|
+
field comment_types: types.map do |type|
|
47
|
+
camelize(singularize(type))
|
48
|
+
end
|
77
49
|
end
|
78
50
|
end
|
79
51
|
```
|
@@ -82,35 +54,52 @@ Finally, build your hash from the deserializable resource:
|
|
82
54
|
payload = {
|
83
55
|
'data' => {
|
84
56
|
'id' => '1',
|
85
|
-
'type' => '
|
57
|
+
'type' => 'posts',
|
86
58
|
'attributes' => {
|
87
|
-
'
|
88
|
-
'
|
59
|
+
'title' => 'Title',
|
60
|
+
'date' => '2016-01-10 02:30:00'
|
89
61
|
},
|
90
62
|
'relationships' => {
|
91
|
-
'
|
63
|
+
'author' => {
|
92
64
|
'data' => { 'type' => 'users', 'id' => '1337' }
|
93
65
|
},
|
94
|
-
'
|
66
|
+
'comments' => {
|
95
67
|
'data' => [
|
96
|
-
{ 'type' => '
|
97
|
-
{ 'type' => '
|
98
|
-
{ 'type' => '
|
68
|
+
{ 'type' => 'comments', 'id' => '123' },
|
69
|
+
{ 'type' => 'comments', 'id' => '234' },
|
70
|
+
{ 'type' => 'comments', 'id' => '345' }
|
99
71
|
]
|
100
72
|
}
|
101
73
|
}
|
102
74
|
}
|
103
75
|
}
|
104
76
|
|
105
|
-
|
77
|
+
DeserializableCreateUser.(payload)
|
106
78
|
# => {
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
79
|
+
# id: '1',
|
80
|
+
# title: 'Title',
|
81
|
+
# date: #<DateTime: 2016-01-10T02:30:00+00:00 ((2457398j,9000s,0n),+0s,2299161j)>,
|
82
|
+
# author_id: '1337',
|
83
|
+
# author_type: 'users',
|
84
|
+
# comment_ids: ['123', '234', '345']
|
85
|
+
# comment_types: ['Comment', 'Comment', 'Comment']
|
111
86
|
# }
|
112
87
|
```
|
113
88
|
|
89
|
+
### Relationships
|
90
|
+
|
91
|
+
```
|
92
|
+
class DeserializablePostComments < JSONAPI::Deserializable::Relationship
|
93
|
+
has_many do |rel, ids, types|
|
94
|
+
field comment_ids: ids
|
95
|
+
field comment_types: types.map do |ri|
|
96
|
+
camelize(singularize(type))
|
97
|
+
end
|
98
|
+
field comments_meta: rel['meta']
|
99
|
+
end
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
114
103
|
## License
|
115
104
|
|
116
105
|
jsonapi-deserializable is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'jsonapi/deserializable/exceptions'
|
2
1
|
require 'jsonapi/deserializable/relationship_dsl'
|
3
2
|
|
4
3
|
module JSONAPI
|
@@ -7,31 +6,45 @@ module JSONAPI
|
|
7
6
|
include RelationshipDSL
|
8
7
|
|
9
8
|
class << self
|
10
|
-
attr_accessor :
|
9
|
+
attr_accessor :has_one_block, :has_many_block
|
11
10
|
end
|
12
11
|
|
13
|
-
self.field_blocks = {}
|
14
|
-
self.validations = {}
|
15
|
-
|
16
12
|
def self.inherited(klass)
|
17
|
-
klass.
|
18
|
-
klass.
|
13
|
+
klass.has_one_block = has_one_block
|
14
|
+
klass.has_many_block = has_many_block
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.call(payload)
|
18
|
+
new(payload).to_h
|
19
19
|
end
|
20
20
|
|
21
21
|
def initialize(payload)
|
22
|
-
JSONAPI.validate_relationship!(payload, validations)
|
23
22
|
@document = payload
|
24
23
|
@data = payload['data']
|
24
|
+
deserialize!
|
25
25
|
end
|
26
26
|
|
27
27
|
def to_h
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
@hash
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def deserialize!
|
34
|
+
@hash = {}
|
35
|
+
if @data.is_a?(Array)
|
36
|
+
ids = @data.map { |ri| ri['id'] }
|
37
|
+
types = @data.map { |ri| ri['type'] }
|
38
|
+
instance_exec(@document, ids, types, &self.class.has_many_block)
|
39
|
+
else
|
40
|
+
id = @data && @data['id']
|
41
|
+
type = @data && @data['type']
|
42
|
+
instance_exec(@document, id, type, &self.class.has_one_block)
|
33
43
|
end
|
34
|
-
|
44
|
+
end
|
45
|
+
|
46
|
+
def field(hash)
|
47
|
+
@hash.merge!(hash)
|
35
48
|
end
|
36
49
|
end
|
37
50
|
end
|
@@ -6,24 +6,14 @@ module JSONAPI
|
|
6
6
|
end
|
7
7
|
|
8
8
|
module ClassMethods
|
9
|
-
def has_one
|
10
|
-
|
9
|
+
def has_one(&block)
|
10
|
+
block ||= proc { |rel| field key.to_sym => rel }
|
11
|
+
self.has_one_block = block
|
11
12
|
end
|
12
13
|
|
13
|
-
def has_many
|
14
|
-
|
15
|
-
|
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
|
14
|
+
def has_many(&block)
|
15
|
+
block ||= proc { |rel| field key.to_sym => rel }
|
16
|
+
self.has_many_block = block
|
27
17
|
end
|
28
18
|
end
|
29
19
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'jsonapi/validations'
|
2
|
-
require 'jsonapi/deserializable/exceptions'
|
3
1
|
require 'jsonapi/deserializable/resource_dsl'
|
4
2
|
|
5
3
|
module JSONAPI
|
@@ -8,36 +6,104 @@ module JSONAPI
|
|
8
6
|
include ResourceDSL
|
9
7
|
|
10
8
|
class << self
|
11
|
-
attr_accessor :
|
9
|
+
attr_accessor :type_block, :id_block
|
10
|
+
attr_accessor :attr_blocks
|
11
|
+
attr_accessor :has_one_rel_blocks, :has_many_rel_blocks
|
12
12
|
end
|
13
13
|
|
14
|
-
self.
|
15
|
-
self.
|
14
|
+
self.attr_blocks = {}
|
15
|
+
self.has_one_rel_blocks = {}
|
16
|
+
self.has_many_rel_blocks = {}
|
16
17
|
|
17
18
|
def self.inherited(klass)
|
18
19
|
super
|
19
|
-
klass.
|
20
|
-
klass.
|
20
|
+
klass.type_block = type_block
|
21
|
+
klass.id_block = id_block
|
22
|
+
klass.attr_blocks = attr_blocks.dup
|
23
|
+
klass.has_one_rel_blocks = has_one_rel_blocks.dup
|
24
|
+
klass.has_many_rel_blocks = has_many_rel_blocks.dup
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.call(payload)
|
28
|
+
new(payload).to_h
|
21
29
|
end
|
22
30
|
|
23
31
|
def initialize(payload)
|
24
|
-
JSONAPI.validate_resource!(payload, self.class.validations)
|
25
32
|
@document = payload
|
26
33
|
@data = @document['data']
|
27
|
-
@
|
28
|
-
@
|
34
|
+
@type = @data['type']
|
35
|
+
@id = @data['id']
|
36
|
+
@attributes = @data['attributes'] || {}
|
37
|
+
@relationships = @data['relationships'] || {}
|
38
|
+
deserialize!
|
29
39
|
end
|
30
40
|
|
31
41
|
def to_h
|
32
|
-
|
42
|
+
@hash
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def deserialize!
|
48
|
+
@hash = {}
|
49
|
+
deserialize_type!
|
50
|
+
deserialize_id!
|
51
|
+
deserialize_attrs!
|
52
|
+
deserialize_rels!
|
53
|
+
end
|
33
54
|
|
34
|
-
|
35
|
-
@
|
36
|
-
self.class.
|
37
|
-
|
55
|
+
def deserialize_type!
|
56
|
+
return unless @type && self.class.type_block
|
57
|
+
instance_exec(@type, &self.class.type_block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def deserialize_id!
|
61
|
+
return unless @id && self.class.id_block
|
62
|
+
instance_exec(@id, &self.class.id_block)
|
63
|
+
end
|
64
|
+
|
65
|
+
def deserialize_attrs!
|
66
|
+
self.class.attr_blocks.each do |attr, block|
|
67
|
+
next unless @attributes.key?(attr)
|
68
|
+
instance_exec(@attributes[attr], &block)
|
38
69
|
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def deserialize_rels!
|
73
|
+
deserialize_has_one_rels!
|
74
|
+
deserialize_has_many_rels!
|
75
|
+
end
|
76
|
+
|
77
|
+
def deserialize_has_one_rels!
|
78
|
+
self.class.has_one_rel_blocks.each do |key, block|
|
79
|
+
rel = @relationships[key]
|
80
|
+
next unless rel && (rel['data'].nil? || rel['data'].is_a?(Hash))
|
81
|
+
deserialize_has_one_rel!(rel, &block)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def deserialize_has_one_rel!(rel, &block)
|
86
|
+
id = rel['data'] && rel['data']['id']
|
87
|
+
type = rel['data'] && rel['data']['type']
|
88
|
+
instance_exec(rel, id, type, &block)
|
89
|
+
end
|
90
|
+
|
91
|
+
def deserialize_has_many_rels!
|
92
|
+
self.class.has_many_rel_blocks.each do |key, block|
|
93
|
+
rel = @relationships[key]
|
94
|
+
next unless rel && rel['data'].is_a?(Array)
|
95
|
+
deserialize_has_many_rel!(rel, &block)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def deserialize_has_many_rel!(rel, &block)
|
100
|
+
ids = rel['data'].map { |ri| ri['id'] }
|
101
|
+
types = rel['data'].map { |ri| ri['type'] }
|
102
|
+
instance_exec(rel, ids, types, &block)
|
103
|
+
end
|
39
104
|
|
40
|
-
|
105
|
+
def field(hash)
|
106
|
+
@hash.merge!(hash)
|
41
107
|
end
|
42
108
|
end
|
43
109
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'jsonapi/deserializable/validations'
|
2
|
-
|
3
1
|
module JSONAPI
|
4
2
|
module Deserializable
|
5
3
|
module ResourceDSL
|
@@ -8,65 +6,32 @@ module JSONAPI
|
|
8
6
|
end
|
9
7
|
|
10
8
|
module ClassMethods
|
11
|
-
def
|
12
|
-
|
13
|
-
|
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'] }
|
9
|
+
def type(&block)
|
10
|
+
block ||= proc { |type| field type: type }
|
11
|
+
self.type_block = block
|
25
12
|
end
|
26
13
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
14
|
+
def id(&block)
|
15
|
+
block ||= proc { |id| field id: id }
|
16
|
+
self.id_block = block
|
30
17
|
end
|
31
18
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
19
|
+
def attribute(key, options = {}, &block)
|
20
|
+
unless block
|
21
|
+
options[:key] ||= key.to_sym
|
22
|
+
block = proc { |attr| field key => attr }
|
36
23
|
end
|
24
|
+
attr_blocks[key.to_s] = block
|
37
25
|
end
|
38
26
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
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'] }
|
27
|
+
def has_one(key, &block)
|
28
|
+
block ||= proc { |rel| field key.to_sym => rel }
|
29
|
+
has_one_rel_blocks[key.to_s] = block
|
49
30
|
end
|
50
31
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
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])
|
32
|
+
def has_many(key, &block)
|
33
|
+
block ||= proc { |rel| field key.to_sym => rel }
|
34
|
+
has_many_rel_blocks[key.to_s] = block
|
70
35
|
end
|
71
36
|
end
|
72
37
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapi-deserializable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.1.
|
4
|
+
version: 0.1.1.beta3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lucas Hosseini
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-25 00:00:00.000000000 Z
|
12
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
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: rake
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -61,14 +47,11 @@ extra_rdoc_files: []
|
|
61
47
|
files:
|
62
48
|
- README.md
|
63
49
|
- lib/jsonapi/deserializable.rb
|
64
|
-
- lib/jsonapi/deserializable/document_validator.rb
|
65
|
-
- lib/jsonapi/deserializable/exceptions.rb
|
66
50
|
- lib/jsonapi/deserializable/relationship.rb
|
67
51
|
- lib/jsonapi/deserializable/relationship_dsl.rb
|
68
52
|
- lib/jsonapi/deserializable/resource.rb
|
69
53
|
- lib/jsonapi/deserializable/resource/dsl.rb
|
70
54
|
- lib/jsonapi/deserializable/resource_dsl.rb
|
71
|
-
- lib/jsonapi/deserializable/validations.rb
|
72
55
|
homepage: https://github.com/beauby/jsonapi-deserializable
|
73
56
|
licenses:
|
74
57
|
- MIT
|
@@ -1,200 +0,0 @@
|
|
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
|
@@ -1,101 +0,0 @@
|
|
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
|