simple_jsonapi 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +131 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +5 -0
- data/Jenkinsfile +92 -0
- data/LICENSE.txt +22 -0
- data/README.md +532 -0
- data/Rakefile +10 -0
- data/lib/simple_jsonapi.rb +112 -0
- data/lib/simple_jsonapi/definition/attribute.rb +45 -0
- data/lib/simple_jsonapi/definition/base.rb +50 -0
- data/lib/simple_jsonapi/definition/concerns/has_links_object.rb +36 -0
- data/lib/simple_jsonapi/definition/concerns/has_meta_object.rb +36 -0
- data/lib/simple_jsonapi/definition/error.rb +70 -0
- data/lib/simple_jsonapi/definition/error_source.rb +29 -0
- data/lib/simple_jsonapi/definition/link.rb +27 -0
- data/lib/simple_jsonapi/definition/meta.rb +27 -0
- data/lib/simple_jsonapi/definition/relationship.rb +60 -0
- data/lib/simple_jsonapi/definition/resource.rb +104 -0
- data/lib/simple_jsonapi/error_serializer.rb +76 -0
- data/lib/simple_jsonapi/errors/bad_request.rb +11 -0
- data/lib/simple_jsonapi/errors/exception_serializer.rb +6 -0
- data/lib/simple_jsonapi/errors/wrapped_error.rb +35 -0
- data/lib/simple_jsonapi/errors/wrapped_error_serializer.rb +35 -0
- data/lib/simple_jsonapi/helpers/exceptions.rb +39 -0
- data/lib/simple_jsonapi/helpers/serializer_inferrer.rb +136 -0
- data/lib/simple_jsonapi/helpers/serializer_methods.rb +36 -0
- data/lib/simple_jsonapi/node/attributes.rb +51 -0
- data/lib/simple_jsonapi/node/base.rb +91 -0
- data/lib/simple_jsonapi/node/data/collection.rb +25 -0
- data/lib/simple_jsonapi/node/data/singular.rb +26 -0
- data/lib/simple_jsonapi/node/document/base.rb +62 -0
- data/lib/simple_jsonapi/node/document/collection.rb +17 -0
- data/lib/simple_jsonapi/node/document/errors.rb +17 -0
- data/lib/simple_jsonapi/node/document/singular.rb +17 -0
- data/lib/simple_jsonapi/node/error.rb +55 -0
- data/lib/simple_jsonapi/node/error_source.rb +40 -0
- data/lib/simple_jsonapi/node/errors.rb +28 -0
- data/lib/simple_jsonapi/node/included.rb +45 -0
- data/lib/simple_jsonapi/node/object_links.rb +40 -0
- data/lib/simple_jsonapi/node/object_meta.rb +40 -0
- data/lib/simple_jsonapi/node/relationship.rb +79 -0
- data/lib/simple_jsonapi/node/relationship_data/base.rb +53 -0
- data/lib/simple_jsonapi/node/relationship_data/collection.rb +32 -0
- data/lib/simple_jsonapi/node/relationship_data/singular.rb +33 -0
- data/lib/simple_jsonapi/node/relationships.rb +60 -0
- data/lib/simple_jsonapi/node/resource/base.rb +21 -0
- data/lib/simple_jsonapi/node/resource/full.rb +49 -0
- data/lib/simple_jsonapi/node/resource/linkage.rb +25 -0
- data/lib/simple_jsonapi/parameters/fields_spec.rb +45 -0
- data/lib/simple_jsonapi/parameters/include_spec.rb +57 -0
- data/lib/simple_jsonapi/parameters/sort_spec.rb +107 -0
- data/lib/simple_jsonapi/serializer.rb +89 -0
- data/lib/simple_jsonapi/version.rb +3 -0
- data/simple_jsonapi.gemspec +29 -0
- data/test/errors/bad_request_test.rb +34 -0
- data/test/errors/error_serializer_test.rb +229 -0
- data/test/errors/exception_serializer_test.rb +25 -0
- data/test/errors/wrapped_error_serializer_test.rb +91 -0
- data/test/errors/wrapped_error_test.rb +44 -0
- data/test/parameters/fields_spec_test.rb +56 -0
- data/test/parameters/include_spec_test.rb +58 -0
- data/test/parameters/sort_spec_test.rb +65 -0
- data/test/resources/attributes_test.rb +109 -0
- data/test/resources/extras_test.rb +70 -0
- data/test/resources/id_and_type_test.rb +76 -0
- data/test/resources/inclusion_test.rb +134 -0
- data/test/resources/links_test.rb +63 -0
- data/test/resources/meta_test.rb +49 -0
- data/test/resources/relationships_test.rb +262 -0
- data/test/resources/sorting_test.rb +79 -0
- data/test/resources/sparse_fieldset_test.rb +160 -0
- data/test/root_objects_test.rb +165 -0
- data/test/test_helper.rb +31 -0
- metadata +235 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module SimpleJsonapi::Node
|
2
|
+
# @!attribute [r] errors
|
3
|
+
# @return [Array<Object>]
|
4
|
+
class Errors < Base
|
5
|
+
attr_reader :errors
|
6
|
+
|
7
|
+
# @param errors [Array<Object>]
|
8
|
+
# @param options see {Node::Base#initialize} for additional parameters
|
9
|
+
def initialize(errors:, **options)
|
10
|
+
super(options)
|
11
|
+
|
12
|
+
@errors = Array.wrap(errors)
|
13
|
+
|
14
|
+
@error_nodes = @errors.map do |error|
|
15
|
+
build_child_node(SimpleJsonapi::Node::Error, error: error)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Hash{Symbol => Hash}]
|
20
|
+
def as_jsonapi
|
21
|
+
if @error_nodes.any?
|
22
|
+
{ errors: @error_nodes.map(&:as_jsonapi) }
|
23
|
+
else
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module SimpleJsonapi::Node
|
2
|
+
class Included < Base
|
3
|
+
# @param options see {Node::Base#initialize} for additional parameters
|
4
|
+
def initialize(**options)
|
5
|
+
super
|
6
|
+
|
7
|
+
@resource_nodes = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param resource_node [Node::Resource::Base]
|
11
|
+
def included_resource?(resource_node)
|
12
|
+
@resource_nodes.key?(hash_key(resource_node))
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param resource_node [Node::Resource::Full]
|
16
|
+
# @return [Node::Resource::Full]
|
17
|
+
def append_included_resource(resource_node)
|
18
|
+
if included_resource?(resource_node)
|
19
|
+
raise DuplicateResourceError.new(resource_node.resource_type, resource_node.resource_id)
|
20
|
+
end
|
21
|
+
|
22
|
+
@resource_nodes[hash_key(resource_node)] = resource_node
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Hash{Symbol => Hash}]
|
26
|
+
def as_jsonapi
|
27
|
+
if @resource_nodes.any?
|
28
|
+
resources_json = @resource_nodes
|
29
|
+
.each_value
|
30
|
+
.sort_by { |node| [node.resource_type, node.resource_id] }
|
31
|
+
.map(&:as_jsonapi)
|
32
|
+
|
33
|
+
{ included: resources_json }
|
34
|
+
else
|
35
|
+
{}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def hash_key(resource_node)
|
42
|
+
[resource_node.resource_type, resource_node.resource_id]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module SimpleJsonapi::Node
|
2
|
+
# Represents a +links+ object (a collection of links).
|
3
|
+
#
|
4
|
+
# @!attribute [r] object
|
5
|
+
# @return [Object]
|
6
|
+
# @!attribute [r] link_definitions
|
7
|
+
# @return [Hash{Symbol => Definition::Link}]
|
8
|
+
class ObjectLinks < Base
|
9
|
+
attr_reader :object, :link_definitions
|
10
|
+
|
11
|
+
# @param object [Object]
|
12
|
+
# @param link_definitions [Hash{Symbol => Definition::Link}]
|
13
|
+
# @param options see {Node::Base#initialize} for additional parameters
|
14
|
+
def initialize(object:, link_definitions:, **options)
|
15
|
+
super(options)
|
16
|
+
|
17
|
+
@object = object
|
18
|
+
@link_definitions = link_definitions
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Hash{Symbol => Hash}]
|
22
|
+
def as_jsonapi
|
23
|
+
if link_definitions_to_render.any?
|
24
|
+
json = {}
|
25
|
+
link_definitions_to_render.each do |name, defn|
|
26
|
+
json[name] = evaluate(defn.value_proc, object)
|
27
|
+
end
|
28
|
+
{ links: json }
|
29
|
+
else
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def link_definitions_to_render
|
37
|
+
@link_definitions_to_render ||= link_definitions.select { |_, defn| render?(defn, object) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module SimpleJsonapi::Node
|
2
|
+
# Represents a +meta+ object (a collection of meta members).
|
3
|
+
#
|
4
|
+
# @!attribute [r] object
|
5
|
+
# @return [Object]
|
6
|
+
# @!attribute [r] meta_definitions
|
7
|
+
# @return [Hash{Symbol => Definition::Meta}]
|
8
|
+
class ObjectMeta < Base
|
9
|
+
attr_reader :object, :meta_definitions
|
10
|
+
|
11
|
+
# @param object [Object]
|
12
|
+
# @param meta_definitions [Hash{Symbol => Definition::Meta}]
|
13
|
+
# @param options see {Node::Base#initialize} for additional parameters
|
14
|
+
def initialize(object:, meta_definitions:, **options)
|
15
|
+
super(options)
|
16
|
+
|
17
|
+
@object = object
|
18
|
+
@meta_definitions = meta_definitions || {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Hash{Symbol => Hash}]
|
22
|
+
def as_jsonapi
|
23
|
+
if meta_definitions_to_render.any?
|
24
|
+
json = {}
|
25
|
+
meta_definitions_to_render.each do |name, defn|
|
26
|
+
json[name] = evaluate(defn.value_proc, object)
|
27
|
+
end
|
28
|
+
{ meta: json }
|
29
|
+
else
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def meta_definitions_to_render
|
37
|
+
@meta_definitions_to_render ||= @meta_definitions.select { |_, defn| render?(defn, object) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module SimpleJsonapi::Node
|
2
|
+
# Represents a single +relationship+ object.
|
3
|
+
#
|
4
|
+
# @!attribute [r] resource
|
5
|
+
# @return [Object]
|
6
|
+
# @!attribute [r] relationship_definition
|
7
|
+
# @return [Definition::Relationship]
|
8
|
+
class Relationship < Base
|
9
|
+
attr_reader :resource, :relationship_definition
|
10
|
+
|
11
|
+
# @param resource [Object]
|
12
|
+
# @param relationship_definition [Definition::Relationship]
|
13
|
+
# @param options see {Node::Base#initialize} for additional parameters
|
14
|
+
def initialize(resource:, relationship_definition:, **options)
|
15
|
+
super(options)
|
16
|
+
|
17
|
+
@resource = resource
|
18
|
+
@relationship_definition = relationship_definition
|
19
|
+
|
20
|
+
if relationship_definition.singular?
|
21
|
+
@data_node = build_child_node(
|
22
|
+
SimpleJsonapi::Node::RelationshipData::Singular,
|
23
|
+
relationship_definition: relationship_definition,
|
24
|
+
resource: related_data,
|
25
|
+
add_to_included: add_to_included?,
|
26
|
+
)
|
27
|
+
elsif relationship_definition.collection?
|
28
|
+
@data_node = build_child_node(
|
29
|
+
SimpleJsonapi::Node::RelationshipData::Collection,
|
30
|
+
relationship_definition: relationship_definition,
|
31
|
+
resources: related_data,
|
32
|
+
add_to_included: add_to_included?,
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
@links_node = build_child_node(
|
37
|
+
SimpleJsonapi::Node::ObjectLinks,
|
38
|
+
object: resource,
|
39
|
+
link_definitions: relationship_definition.link_definitions,
|
40
|
+
)
|
41
|
+
|
42
|
+
@meta_node = build_child_node(
|
43
|
+
SimpleJsonapi::Node::ObjectMeta,
|
44
|
+
object: resource,
|
45
|
+
meta_definitions: relationship_definition.meta_definitions,
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [String]
|
50
|
+
def relationship_name
|
51
|
+
relationship_definition.name
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Hash{Symbol => Hash}]
|
55
|
+
def as_jsonapi
|
56
|
+
rel_json = {}
|
57
|
+
|
58
|
+
rel_json.merge!(@data_node.as_jsonapi) if @data_node
|
59
|
+
rel_json.merge!(@links_node.as_jsonapi) if @links_node
|
60
|
+
rel_json.merge!(@meta_node.as_jsonapi) if @meta_node
|
61
|
+
|
62
|
+
rel_json
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def related_data
|
68
|
+
@related_data ||= evaluate(
|
69
|
+
relationship_definition.data_definition,
|
70
|
+
resource,
|
71
|
+
sort: sort_spec[relationship_name.to_sym],
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_to_included?
|
76
|
+
include_spec.include?(relationship_name)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module SimpleJsonapi::Node::RelationshipData
|
2
|
+
# Represents a relationship's +data+ object.
|
3
|
+
#
|
4
|
+
# @abstract
|
5
|
+
# @!attribute [r] relationship_definition
|
6
|
+
# @return [Definition::Relationship]
|
7
|
+
# @!attribute [r] add_to_included
|
8
|
+
# @return [Boolean]
|
9
|
+
class Base < SimpleJsonapi::Node::Base
|
10
|
+
attr_reader :relationship_definition, :add_to_included
|
11
|
+
|
12
|
+
# @param relationship_definition [Definition::Relationship]
|
13
|
+
# @param add_to_included [Boolean]
|
14
|
+
# @param options see {Node::Base#initialize} for additional parameters
|
15
|
+
def initialize(relationship_definition:, add_to_included:, **options)
|
16
|
+
super(options)
|
17
|
+
|
18
|
+
@relationship_definition = relationship_definition
|
19
|
+
@add_to_included = add_to_included
|
20
|
+
|
21
|
+
@serializer_inferrer = relationship_definition.serializer_inferrer
|
22
|
+
@include_spec = include_spec[relationship_name]
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def build_linkage_node(related_resource)
|
28
|
+
build_child_node(
|
29
|
+
SimpleJsonapi::Node::Resource::Linkage,
|
30
|
+
resource: related_resource,
|
31
|
+
meta: { included: add_to_included },
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_resource_to_included(related_resource, linkage_node)
|
36
|
+
if add_to_included && !root_node.included_resource?(linkage_node)
|
37
|
+
full_node = build_child_node(
|
38
|
+
SimpleJsonapi::Node::Resource::Full,
|
39
|
+
# Only support sorting on first-level relationships because
|
40
|
+
# 1. supporting more levels requires more tracking of how the document is being built
|
41
|
+
# 2. letting the API client sort a deeper relationship seems unnecessary
|
42
|
+
sort_related: nil,
|
43
|
+
resource: related_resource,
|
44
|
+
)
|
45
|
+
root_node.append_included_resource(full_node)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def relationship_name
|
50
|
+
relationship_definition.name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module SimpleJsonapi::Node::RelationshipData
|
2
|
+
# Represents a relationship's +data+ object containing a collection of
|
3
|
+
# resource linkage objects.
|
4
|
+
#
|
5
|
+
# @!attribute [r] resources
|
6
|
+
# @return [Array<Object>]
|
7
|
+
class Collection < Base
|
8
|
+
attr_reader :resources
|
9
|
+
|
10
|
+
# @param resources [Array<Object>]
|
11
|
+
# @param options see {Node::RelationshipData::Base#initialize} for additional parameters
|
12
|
+
def initialize(resources:, **options)
|
13
|
+
super
|
14
|
+
|
15
|
+
@resources = Array.wrap(resources)
|
16
|
+
@linkage_nodes = []
|
17
|
+
|
18
|
+
@resources.each do |resource|
|
19
|
+
linkage_node = build_linkage_node(resource)
|
20
|
+
|
21
|
+
@linkage_nodes << linkage_node
|
22
|
+
|
23
|
+
add_resource_to_included(resource, linkage_node)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Hash{Symbol => Hash}]
|
28
|
+
def as_jsonapi
|
29
|
+
{ data: @linkage_nodes.map(&:as_jsonapi) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module SimpleJsonapi::Node::RelationshipData
|
2
|
+
# Represents a relationship's +data+ object containing a single resource
|
3
|
+
# linkage object.
|
4
|
+
#
|
5
|
+
# @!attribute [r] resource
|
6
|
+
# @return [Object]
|
7
|
+
class Singular < Base
|
8
|
+
attr_reader :resource
|
9
|
+
|
10
|
+
# @param resource [Object]
|
11
|
+
# @param options see {Node::RelationshipData::Base#initialize} for additional parameters
|
12
|
+
def initialize(resource:, **options)
|
13
|
+
super
|
14
|
+
|
15
|
+
@resource = resource
|
16
|
+
|
17
|
+
unless @resource.nil?
|
18
|
+
@linkage_node = build_linkage_node(@resource)
|
19
|
+
|
20
|
+
add_resource_to_included(@resource, @linkage_node)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Hash{Symbol => Hash}]
|
25
|
+
def as_jsonapi
|
26
|
+
if resource.nil?
|
27
|
+
{ data: nil }
|
28
|
+
else
|
29
|
+
{ data: @linkage_node.as_jsonapi }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module SimpleJsonapi::Node
|
2
|
+
# Represents a resource's +relationships+ object, which contains the
|
3
|
+
# individual +relationship+ object.
|
4
|
+
#
|
5
|
+
# @!attribute [r] resource
|
6
|
+
# @return [Object]
|
7
|
+
# @!attribute [r] resource_type
|
8
|
+
# @return [String]
|
9
|
+
# @!attribute [r] relationship_definitions
|
10
|
+
# @return [Hash{Symbol => Definition::Relationship}]
|
11
|
+
class Relationships < Base
|
12
|
+
attr_reader :resource, :resource_type, :relationship_definitions
|
13
|
+
|
14
|
+
# @param resource [Object]
|
15
|
+
# @param resource_type [String]
|
16
|
+
# @param relationship_definitions [Hash{Symbol => Definition::Relationship}]
|
17
|
+
# @param options see {Node::Base#initialize} for additional parameters
|
18
|
+
def initialize(resource:, resource_type:, relationship_definitions:, **options)
|
19
|
+
super(options)
|
20
|
+
|
21
|
+
@resource = resource
|
22
|
+
@resource_type = resource_type
|
23
|
+
@relationship_definitions = relationship_definitions
|
24
|
+
|
25
|
+
@relationship_nodes = relationship_definitions_to_render.map do |_name, defn|
|
26
|
+
build_child_node(
|
27
|
+
SimpleJsonapi::Node::Relationship,
|
28
|
+
resource: resource,
|
29
|
+
relationship_definition: defn,
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Hash{Symbol => Hash}]
|
35
|
+
def as_jsonapi
|
36
|
+
if @relationship_nodes.any?
|
37
|
+
json = {}
|
38
|
+
@relationship_nodes.each do |rel_node|
|
39
|
+
json[rel_node.relationship_name] = rel_node.as_jsonapi
|
40
|
+
end
|
41
|
+
{ relationships: json }
|
42
|
+
else
|
43
|
+
{}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def relationship_definitions_to_render
|
50
|
+
@relationship_definitions_to_render ||= begin
|
51
|
+
include_all_fields = fields_spec.all_fields?(resource_type)
|
52
|
+
explicit_fields = fields_spec[resource_type]
|
53
|
+
|
54
|
+
relationship_definitions
|
55
|
+
.select { |name, _| include_all_fields || explicit_fields.include?(name) }
|
56
|
+
.select { |_, defn| render?(defn, resource) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SimpleJsonapi::Node::Resource
|
2
|
+
# Represents a single resource object or resource linkage object.
|
3
|
+
class Base < SimpleJsonapi::Node::Base
|
4
|
+
attr_reader :resource
|
5
|
+
|
6
|
+
def initialize(resource:, **options)
|
7
|
+
super(options)
|
8
|
+
|
9
|
+
@resource = resource
|
10
|
+
@serializer = serializer_inferrer.infer(resource).new
|
11
|
+
end
|
12
|
+
|
13
|
+
def resource_id
|
14
|
+
@resource_id ||= evaluate(serializer.id_definition, resource).to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def resource_type
|
18
|
+
@resource_type ||= evaluate(serializer.type_definition, resource).to_s
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|