simple_jsonapi 1.0.0
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/.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
|