collection_json_serializer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +137 -0
- data/Rakefile +10 -0
- data/collection_json_serializer.gemspec +26 -0
- data/lib/collection_json_serializer/builder.rb +56 -0
- data/lib/collection_json_serializer/core_ext/hash.rb +9 -0
- data/lib/collection_json_serializer/core_ext/symbol.rb +8 -0
- data/lib/collection_json_serializer/objects/item.rb +70 -0
- data/lib/collection_json_serializer/objects/template.rb +25 -0
- data/lib/collection_json_serializer/serializer.rb +71 -0
- data/lib/collection_json_serializer/support.rb +36 -0
- data/lib/collection_json_serializer/validator/url.rb +22 -0
- data/lib/collection_json_serializer/validator/value.rb +28 -0
- data/lib/collection_json_serializer/validator.rb +131 -0
- data/lib/collection_json_serializer/version.rb +5 -0
- data/lib/collection_json_serializer.rb +18 -0
- data/test/builder/builder_test.rb +157 -0
- data/test/fixtures/models.rb +3 -0
- data/test/fixtures/poro.rb +26 -0
- data/test/fixtures/serializers/custom_item_links_serializer.rb +7 -0
- data/test/fixtures/serializers/custom_item_serializer.rb +3 -0
- data/test/fixtures/serializers/custom_template_serializer.rb +5 -0
- data/test/fixtures/serializers/invalid_serializer.rb +4 -0
- data/test/fixtures/serializers/multiple_href_serializer.rb +3 -0
- data/test/fixtures/serializers/unknown_attribute_serializer.rb +3 -0
- data/test/fixtures/serializers/user_serializer.rb +7 -0
- data/test/fixtures/serializers/valid_serializer.rb +5 -0
- data/test/minitest_helper.rb +11 -0
- data/test/objects/item_test.rb +82 -0
- data/test/objects/template_test.rb +40 -0
- data/test/serializer/data_test.rb +29 -0
- data/test/serializer/href_test.rb +39 -0
- data/test/serializer/links_test.rb +23 -0
- data/test/serializer/template_test.rb +30 -0
- data/test/support/ext_test.rb +23 -0
- data/test/support/support_test.rb +29 -0
- data/test/validator/invalid_test.rb +138 -0
- data/test/validator/url_validator_test.rb +24 -0
- data/test/validator/validator_test.rb +64 -0
- data/test/validator/value_validator_test.rb +42 -0
- metadata +169 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
module CollectionJson
|
2
|
+
class Serializer
|
3
|
+
module Support
|
4
|
+
def extract_value_from(resource, method)
|
5
|
+
begin
|
6
|
+
value = resource.send(method)
|
7
|
+
rescue NoMethodError
|
8
|
+
# ignore unknown attributes
|
9
|
+
end
|
10
|
+
|
11
|
+
value if value
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse_url(url, object)
|
15
|
+
segments = url.split("/")
|
16
|
+
|
17
|
+
segments.each_with_index do |segment, index|
|
18
|
+
if has_placeholder?(segment)
|
19
|
+
action = segment.gsub(/[{}]/, "")
|
20
|
+
segments[index] = object.send(action)
|
21
|
+
end
|
22
|
+
end if segments_with_placeholder?(segments)
|
23
|
+
|
24
|
+
segments.join("/")
|
25
|
+
end
|
26
|
+
|
27
|
+
def segments_with_placeholder?(segments)
|
28
|
+
segments.any? { |s| has_placeholder?(s) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_placeholder?(string)
|
32
|
+
string.chars.first.eql?("{") && string.chars.last.eql?("}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module CollectionJson
|
2
|
+
class Serializer
|
3
|
+
class Validator
|
4
|
+
class Url
|
5
|
+
# Stolen from https://github.com/eparreno/ruby_regex/blob/master/lib/ruby_regex.rb
|
6
|
+
VALID = /(\A\z)|(\A(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?\z)/ix
|
7
|
+
|
8
|
+
def initialize(value)
|
9
|
+
@uri = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid?
|
13
|
+
true unless VALID.match(@uri).nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def invalid?
|
17
|
+
true unless valid?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module CollectionJson
|
2
|
+
class Serializer
|
3
|
+
class Validator
|
4
|
+
class Value
|
5
|
+
VALID = %w(
|
6
|
+
String
|
7
|
+
Fixnum
|
8
|
+
Float
|
9
|
+
TrueClass
|
10
|
+
FalseClass
|
11
|
+
NilClass
|
12
|
+
)
|
13
|
+
|
14
|
+
def initialize(value)
|
15
|
+
@value = value
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
VALID.include? @value.class.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
def invalid?
|
23
|
+
!valid?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module CollectionJson
|
2
|
+
class Serializer
|
3
|
+
class Validator
|
4
|
+
include CollectionJson::Serializer::Support
|
5
|
+
|
6
|
+
attr_accessor :errors
|
7
|
+
|
8
|
+
def initialize(serializer)
|
9
|
+
@serializer = serializer
|
10
|
+
@errors = {}
|
11
|
+
validate
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
!invalid?
|
16
|
+
end
|
17
|
+
|
18
|
+
def invalid?
|
19
|
+
@errors.any?
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def validate
|
25
|
+
[
|
26
|
+
:attributes,
|
27
|
+
:href,
|
28
|
+
:links,
|
29
|
+
:template
|
30
|
+
].each { |m| send("validate_#{m}") }
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_attributes
|
34
|
+
@serializer.attributes.each do |attr|
|
35
|
+
params = attr.extract_params
|
36
|
+
|
37
|
+
@serializer.resources.each do |resource|
|
38
|
+
val = extract_value_from(resource, params[:name])
|
39
|
+
if value_is_invalid? val
|
40
|
+
error_for :value, root: :attributes, path: [params[:name]]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
params[:properties].each do |key, value|
|
45
|
+
if value_is_invalid? value
|
46
|
+
error_for :value, root: :attributes, path: [params[:name], key]
|
47
|
+
end
|
48
|
+
end if params[:properties]
|
49
|
+
|
50
|
+
end if @serializer.attributes.any?
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate_href
|
54
|
+
href = @serializer.href
|
55
|
+
case href
|
56
|
+
when String
|
57
|
+
error_for :url, root: :href if url_is_invalid? href
|
58
|
+
when Hash
|
59
|
+
href.each do |key, value|
|
60
|
+
error_for :url, root: :href, path: [key] if url_is_invalid? value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def validate_links
|
66
|
+
@serializer.links.first.each do |k, link|
|
67
|
+
unless link.key? :href
|
68
|
+
error_for :missing_attribute, root: :links, path: [k, :href]
|
69
|
+
|
70
|
+
next
|
71
|
+
end
|
72
|
+
|
73
|
+
link.each do |key, value|
|
74
|
+
case key
|
75
|
+
when :href
|
76
|
+
if url_is_invalid? link[:href]
|
77
|
+
error_for :url, root: :links, path: [k, key]
|
78
|
+
end
|
79
|
+
else
|
80
|
+
if value_is_invalid? value
|
81
|
+
error_for :value, root: :links, path: [k, key]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end if @serializer.links.present?
|
86
|
+
end
|
87
|
+
|
88
|
+
def validate_template
|
89
|
+
@serializer.template.each do |attr|
|
90
|
+
params = attr.extract_params
|
91
|
+
|
92
|
+
params[:properties].each do |key, value|
|
93
|
+
if value_is_invalid? value
|
94
|
+
error_for :value, root: :template, path: [params[:name], key]
|
95
|
+
end
|
96
|
+
end if params[:properties]
|
97
|
+
|
98
|
+
end if @serializer.template.any?
|
99
|
+
end
|
100
|
+
|
101
|
+
def value_is_invalid?(value)
|
102
|
+
v = CollectionJson::Serializer::Validator::Value.new(value)
|
103
|
+
v.invalid?
|
104
|
+
end
|
105
|
+
|
106
|
+
def url_is_invalid?(value)
|
107
|
+
v = CollectionJson::Serializer::Validator::Url.new(value)
|
108
|
+
v.invalid?
|
109
|
+
end
|
110
|
+
|
111
|
+
def error_for(kind, root:, path: [])
|
112
|
+
case kind.to_sym
|
113
|
+
when :url
|
114
|
+
ending = " is an invalid URL"
|
115
|
+
when :value
|
116
|
+
ending = " is an invalid value"
|
117
|
+
when :missing_attribute
|
118
|
+
ending = " is missing"
|
119
|
+
else
|
120
|
+
ending = " is an invalid value"
|
121
|
+
end
|
122
|
+
|
123
|
+
@errors[root] = [] unless @errors.key? root
|
124
|
+
e = "#{@serializer.class} #{root}"
|
125
|
+
e << ":" + path.join(":") if path.any?
|
126
|
+
e << ending
|
127
|
+
@errors[root] << e
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "collection_json_serializer/version"
|
2
|
+
|
3
|
+
require "collection_json_serializer/core_ext/hash"
|
4
|
+
require "collection_json_serializer/core_ext/symbol"
|
5
|
+
|
6
|
+
require "collection_json_serializer/support"
|
7
|
+
|
8
|
+
require "collection_json_serializer/serializer"
|
9
|
+
|
10
|
+
require "collection_json_serializer/builder"
|
11
|
+
|
12
|
+
require "collection_json_serializer/validator"
|
13
|
+
require "collection_json_serializer/validator/url"
|
14
|
+
require "collection_json_serializer/validator/value"
|
15
|
+
|
16
|
+
require "collection_json_serializer/objects/item"
|
17
|
+
|
18
|
+
require "collection_json_serializer/objects/template"
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require "minitest_helper"
|
2
|
+
|
3
|
+
module CollectionJson
|
4
|
+
class Serializer
|
5
|
+
class Builder
|
6
|
+
class TestBuilder < Minitest::Test
|
7
|
+
def setup
|
8
|
+
@user1 = User.new(name: "Carles Jove", email: "hola@carlus.cat")
|
9
|
+
@user2 = User.new(name: "Aina Jove", email: "hola@example.com")
|
10
|
+
@account = Account.new(id: 1, name: "My Account", created_at: Time.now)
|
11
|
+
@user1.account = @account
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_response_format_with_one_resource
|
15
|
+
user_serializer = UserSerializer.new(@user1)
|
16
|
+
builder = Builder.new(user_serializer)
|
17
|
+
expected = {
|
18
|
+
collection: {
|
19
|
+
version: "1.0",
|
20
|
+
href: "http://example.com/users",
|
21
|
+
items: [
|
22
|
+
{
|
23
|
+
href: "http://example.com/users/#{@user1.id}",
|
24
|
+
data: [
|
25
|
+
{
|
26
|
+
name: "name",
|
27
|
+
value: "Carles Jove"
|
28
|
+
},
|
29
|
+
{
|
30
|
+
name: "email",
|
31
|
+
value: "hola@carlus.cat"
|
32
|
+
}
|
33
|
+
],
|
34
|
+
links: [
|
35
|
+
{
|
36
|
+
name: "dashboard",
|
37
|
+
href: "http://example.com/my-dashboard"
|
38
|
+
}
|
39
|
+
]
|
40
|
+
}
|
41
|
+
],
|
42
|
+
template: {
|
43
|
+
data: [
|
44
|
+
{
|
45
|
+
name: "name",
|
46
|
+
value: ""
|
47
|
+
},
|
48
|
+
{
|
49
|
+
name: "email",
|
50
|
+
value: "",
|
51
|
+
prompt: "My email"
|
52
|
+
}
|
53
|
+
]
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
assert_equal expected.to_json, builder.to_json
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_response_format_with_multiple_resources
|
62
|
+
user_serializer = UserSerializer.new([@user1, @user2])
|
63
|
+
builder = Builder.new(user_serializer)
|
64
|
+
expected = {
|
65
|
+
collection: {
|
66
|
+
version: "1.0",
|
67
|
+
href: "http://example.com/users",
|
68
|
+
items: [
|
69
|
+
{
|
70
|
+
href: "http://example.com/users/#{@user1.id}",
|
71
|
+
data: [
|
72
|
+
{
|
73
|
+
name: "name",
|
74
|
+
value: "Carles Jove"
|
75
|
+
},
|
76
|
+
{
|
77
|
+
name: "email",
|
78
|
+
value: "hola@carlus.cat"
|
79
|
+
}
|
80
|
+
],
|
81
|
+
links: [
|
82
|
+
{
|
83
|
+
name: "dashboard",
|
84
|
+
href: "http://example.com/my-dashboard"
|
85
|
+
}
|
86
|
+
]
|
87
|
+
},
|
88
|
+
{
|
89
|
+
href: "http://example.com/users/#{@user2.id}",
|
90
|
+
data: [
|
91
|
+
{
|
92
|
+
name: "name",
|
93
|
+
value: "Aina Jove"
|
94
|
+
},
|
95
|
+
{
|
96
|
+
name: "email",
|
97
|
+
value: "hola@example.com"
|
98
|
+
}
|
99
|
+
],
|
100
|
+
links: [
|
101
|
+
{
|
102
|
+
name: "dashboard",
|
103
|
+
href: "http://example.com/my-dashboard"
|
104
|
+
}
|
105
|
+
]
|
106
|
+
}
|
107
|
+
],
|
108
|
+
template: {
|
109
|
+
data: [
|
110
|
+
{
|
111
|
+
name: "name",
|
112
|
+
value: ""
|
113
|
+
},
|
114
|
+
{
|
115
|
+
name: "email",
|
116
|
+
value: "",
|
117
|
+
prompt: "My email"
|
118
|
+
}
|
119
|
+
]
|
120
|
+
}
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
assert_equal expected.to_json, builder.to_json
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_that_any_attributes_can_be_passed_to_template
|
128
|
+
custom_serializer = CustomTemplateSerializer.new(@user1)
|
129
|
+
builder = Builder.new(custom_serializer)
|
130
|
+
|
131
|
+
expected = {
|
132
|
+
collection: {
|
133
|
+
version: "1.0",
|
134
|
+
template: {
|
135
|
+
data: [
|
136
|
+
{ name: "name", value: "" },
|
137
|
+
{ name: "email", value: "", prompt: "My email", anything: "at all", whatever: "really" }
|
138
|
+
]
|
139
|
+
}
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
assert_equal expected.to_json, builder.to_json
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_that_an_invalid_serializer_raises_an_error
|
147
|
+
invalid_serializer = InvalidSerializer.new(@user1)
|
148
|
+
builder = Builder.new(invalid_serializer)
|
149
|
+
|
150
|
+
assert_raises Exception do
|
151
|
+
builder.pack
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Base class for pseudo Models
|
2
|
+
#
|
3
|
+
# Create a model:
|
4
|
+
# User = Class.new(Model)
|
5
|
+
#
|
6
|
+
# and then populate it in your tests:
|
7
|
+
# @user = User.new(name: "Carles Jove", email: "hola@carlus.cat")
|
8
|
+
class Model
|
9
|
+
def initialize(hash = {})
|
10
|
+
@attributes = hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def id
|
14
|
+
@attributes[:id] || @attributes["id"] || object_id
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(method, *args)
|
18
|
+
if method.to_s =~ /^(.*)=$/
|
19
|
+
@attributes[$1.to_sym] = args[0]
|
20
|
+
elsif @attributes.key?(method)
|
21
|
+
@attributes[method]
|
22
|
+
else
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|