jsonapi 0.1.1.beta2 → 0.1.1.beta6

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.
@@ -1,126 +0,0 @@
1
- module JSONAPI
2
- # c.f. http://jsonapi.org/format/#document-top-level
3
- class Document
4
- attr_reader :data, :meta, :errors, :json_api, :links, :included
5
-
6
- def initialize(document_hash, options = {})
7
- @hash = document_hash
8
- @options = options
9
- @data_defined = document_hash.key?('data')
10
- @data = parse_data(document_hash['data']) if @data_defined
11
- @meta_defined = document_hash.key?('meta')
12
- @meta = parse_meta(document_hash['meta']) if @meta_defined
13
- @errors_defined = document_hash.key?('errors')
14
- @errors = parse_errors(document_hash['errors']) if @errors_defined
15
- @jsonapi_defined = document_hash.key?('jsonapi')
16
- @jsonapi = JsonApi.new(document_hash['jsonapi'], @options) if
17
- @jsonapi_defined
18
- @links_hash = document_hash['links'] || {}
19
- @links = Links.new(@links_hash, @options)
20
- @included_defined = document_hash.key?('included')
21
- @included = parse_included(document_hash['included']) if
22
- @included_defined
23
-
24
- validate!
25
- end
26
-
27
- def to_hash
28
- @hash
29
- end
30
-
31
- def collection?
32
- @data.is_a?(Array)
33
- end
34
-
35
- private
36
-
37
- def validate!
38
- case
39
- when !@data_defined && !@meta_defined && !@errors_defined
40
- fail InvalidDocument,
41
- "a document MUST contain at least one of 'data', 'meta', or" \
42
- " or 'errors' at top-level"
43
- when @data_defined && @errors_defined
44
- fail InvalidDocument,
45
- "'data' and 'errors' MUST NOT coexist in the same document"
46
- when !@data_defined && @included_defined
47
- fail InvalidDocument, "'included' MUST NOT be present unless 'data' is"
48
- when @options[:verify_duplicates] && duplicates?
49
- fail InvalidDocument,
50
- "resources MUST NOT appear both in 'data' and 'included'"
51
- when @options[:verify_linkage] && !full_linkage?
52
- fail InvalidDocument,
53
- "resources in 'included' MUST respect full-linkage"
54
- end
55
- end
56
-
57
- def duplicates?
58
- resources = Set.new
59
-
60
- (Array(data) + Array(included)).each do |resource|
61
- return true unless resources.add?([resource.type, resource.id])
62
- end
63
-
64
- false
65
- end
66
-
67
- def full_linkage?
68
- return true unless @included
69
-
70
- reachable = Set.new
71
- # NOTE(lucas): Does Array() already dup?
72
- queue = Array(data).dup
73
- included_resources = Hash[included.map { |r| [[r.type, r.id], r] }]
74
- queue.each { |resource| reachable << [resource.type, resource.id] }
75
-
76
- traverse = lambda do |rel|
77
- ri = [rel.type, rel.id]
78
- return unless included_resources[ri]
79
- return unless reachable.add?(ri)
80
- queue << included_resources[ri]
81
- end
82
-
83
- until queue.empty?
84
- resource = queue.pop
85
- resource.relationships.each do |_, rel|
86
- Array(rel.data).map(&traverse)
87
- end
88
- end
89
-
90
- included_resources.keys.all? { |ri| reachable.include?(ri) }
91
- end
92
-
93
- def parse_data(data_hash)
94
- collection = data_hash.is_a?(Array)
95
- if collection
96
- data_hash.map { |h| Resource.new(h, @options.merge(id_optional: true)) }
97
- elsif data_hash.nil?
98
- nil
99
- else
100
- Resource.new(data_hash, @options.merge(id_optional: true))
101
- end
102
- end
103
-
104
- def parse_meta(meta_hash)
105
- fail InvalidDocument, "the value of 'meta' MUST be an object" unless
106
- meta_hash.is_a?(Hash)
107
- meta_hash
108
- end
109
-
110
- def parse_included(included_hash)
111
- fail InvalidDocument,
112
- "the value of 'included' MUST be an array of resource objects" unless
113
- included_hash.is_a?(Array)
114
-
115
- included_hash.map { |h| Resource.new(h, @options) }
116
- end
117
-
118
- def parse_errors(errors_hash)
119
- fail InvalidDocument,
120
- "the value of 'errors' MUST be an array of error objects" unless
121
- errors_hash.is_a?(Array)
122
-
123
- errors_hash.map { |h| Error.new(h, @options) }
124
- end
125
- end
126
- end
@@ -1,27 +0,0 @@
1
- module JSONAPI
2
- # c.f. http://jsonapi.org/format/#error-objects
3
- class Error
4
- attr_reader :id, :links, :status, :code, :title, :detail, :source, :meta
5
-
6
- def initialize(error_hash, options = {})
7
- fail InvalidDocument,
8
- "the value of 'errors' MUST be an array of error objects" unless
9
- error_hash.is_a?(Hash)
10
-
11
- @hash = error_hash
12
- @id = error_hash['id'] if error_hash.key?('id')
13
- links_hash = error_hash['links'] || {}
14
- @links = Links.new(links_hash, options)
15
- @status = error_hash['status'] if error_hash.key?('status')
16
- @code = error_hash['code'] if error_hash.key?('code')
17
- @title = error_hash['title'] if error_hash.key?('title')
18
- @detail = error_hash['detail'] if error_hash.key?('detail')
19
- @source = error_hash['source'] if error_hash.key?('source')
20
- @meta = error_hash['meta'] if error_hash.key?('meta')
21
- end
22
-
23
- def to_hash
24
- @hash
25
- end
26
- end
27
- end
@@ -1,4 +0,0 @@
1
- module JSONAPI
2
- class InvalidDocument < StandardError
3
- end
4
- end
@@ -1,67 +0,0 @@
1
- require 'jsonapi/include_directive/parser'
2
-
3
- module JSONAPI
4
- # Represent a recursive set of include directives
5
- # (c.f. http://jsonapi.org/format/#fetching-includes)
6
- #
7
- # Addition to the spec: two wildcards, namely '*' and '**'.
8
- # The former stands for any one level of relationship, and the latter stands
9
- # for any number of levels of relationships.
10
- # @example 'posts.*' # => Include related posts, and all the included posts'
11
- # related resources.
12
- # @example 'posts.**' # => Include related posts, and all the included
13
- # posts' related resources, and their related resources, recursively.
14
- class IncludeDirective
15
- # @param include_args (see Parser.include_hash_from_include_args)
16
- def initialize(include_args, options = {})
17
- include_hash = Parser.parse_include_args(include_args)
18
- @hash = include_hash.each_with_object({}) do |(key, value), hash|
19
- hash[key] = self.class.new(value, options)
20
- end
21
- @options = options
22
- end
23
-
24
- # @param key [Symbol, String]
25
- def key?(key)
26
- @hash.key?(key.to_sym) ||
27
- (@options[:allow_wildcard] && (@hash.key?(:*) || @hash.key?(:**)))
28
- end
29
-
30
- # @param key [Symbol, String]
31
- # @return [IncludeDirective, nil]
32
- def [](key)
33
- case
34
- when @hash.key?(key.to_sym)
35
- @hash[key.to_sym]
36
- when @options[:allow_wildcard] && @hash.key?(:**)
37
- self.class.new({ :** => {} }, @options)
38
- when @options[:allow_wildcard] && @hash.key?(:*)
39
- @hash[:*]
40
- end
41
- end
42
-
43
- # @return [Hash{Symbol => Hash}]
44
- def to_hash
45
- @hash.each_with_object({}) do |(key, value), hash|
46
- hash[key] = value.to_hash
47
- end
48
- end
49
-
50
- # @return [String]
51
- def to_string
52
- string_array = @hash.map do |(key, value)|
53
- string_value = value.to_string
54
- if string_value == ''
55
- key.to_s
56
- else
57
- string_value
58
- .split(',')
59
- .map { |x| key.to_s + '.' + x }
60
- .join(',')
61
- end
62
- end
63
-
64
- string_array.join(',')
65
- end
66
- end
67
- end
@@ -1,66 +0,0 @@
1
- module JSONAPI
2
- class IncludeDirective
3
- # Utilities to create an IncludeDirective hash from various types of
4
- # inputs.
5
- module Parser
6
- module_function
7
-
8
- # @api private
9
- def parse_include_args(include_args)
10
- case include_args
11
- when Symbol
12
- { include_args => {} }
13
- when Hash
14
- parse_hash(include_args)
15
- when Array
16
- parse_array(include_args)
17
- when String
18
- parse_string(include_args)
19
- else
20
- {}
21
- end
22
- end
23
-
24
- # @api private
25
- def parse_string(include_string)
26
- include_string.split(',')
27
- .map(&:strip)
28
- .each_with_object({}) do |path, hash|
29
- deep_merge!(hash, parse_path_string(path))
30
- end
31
- end
32
-
33
- # @api private
34
- def parse_path_string(include_path)
35
- include_path.split('.')
36
- .reverse
37
- .reduce({}) { |a, e| { e.to_sym => a } }
38
- end
39
-
40
- # @api private
41
- def parse_hash(include_hash)
42
- include_hash.each_with_object({}) do |(key, value), hash|
43
- hash[key.to_sym] = parse_include_args(value)
44
- end
45
- end
46
-
47
- # @api private
48
- def parse_array(include_array)
49
- include_array.each_with_object({}) do |x, hash|
50
- deep_merge!(hash, parse_include_args(x))
51
- end
52
- end
53
-
54
- # @api private
55
- def deep_merge!(src, ext)
56
- ext.each do |k, v|
57
- if src[k].is_a?(Hash) && v.is_a?(Hash)
58
- deep_merge!(src[k], v)
59
- else
60
- src[k] = v
61
- end
62
- end
63
- end
64
- end
65
- end
66
- end
@@ -1,19 +0,0 @@
1
- module JSONAPI
2
- # c.f. http://jsonapi.org/format/#document-jsonapi-object
3
- class JsonApi
4
- attr_reader :version, :meta
5
-
6
- def initialize(jsonapi_hash, options = {})
7
- fail InvalidDocument, "the value of 'jsonapi' MUST be an object" unless
8
- jsonapi_hash.is_a?(Hash)
9
-
10
- @hash = jsonapi_hash
11
- @version = jsonapi_hash['version'] if jsonapi_hash.key?('meta')
12
- @meta = jsonapi_hash['meta'] if jsonapi_hash.key?('meta')
13
- end
14
-
15
- def to_hash
16
- @hash
17
- end
18
- end
19
- end
@@ -1,41 +0,0 @@
1
- module JSONAPI
2
- # c.f. http://jsonapi.org/format/#document-links
3
- class Link
4
- attr_reader :value, :href, :meta
5
-
6
- def initialize(link_hash, options = {})
7
- @hash = link_hash
8
-
9
- validate!(link_hash)
10
- @value = link_hash
11
- return unless link_hash.is_a?(Hash)
12
-
13
- @href = link_hash['href']
14
- @meta = link_hash['meta']
15
- end
16
-
17
- def to_hash
18
- @hash
19
- end
20
-
21
- private
22
-
23
- def validate!(link_hash)
24
- case
25
- when !link_hash.is_a?(String) && !link_hash.is_a?(Hash)
26
- fail InvalidDocument,
27
- "a 'link' object MUST be either a string or an object"
28
- when link_hash.is_a?(Hash) && (!link_hash.key?('href') ||
29
- !link_hash['href'].is_a?(String))
30
- fail InvalidDocument,
31
- "a 'link' object MUST be either a string or an object containing" \
32
- " an 'href' string"
33
- when link_hash.is_a?(Hash) && (!link_hash.key?('meta') ||
34
- !link_hash['meta'].is_a?(Hash))
35
- fail InvalidDocument,
36
- "a 'link' object MUST be either a string or an object containing" \
37
- " an 'meta' object"
38
- end
39
- end
40
- end
41
- end
@@ -1,34 +0,0 @@
1
- module JSONAPI
2
- # c.f. http://jsonapi.org/format/#document-links
3
- class Links
4
- def initialize(links_hash, options = {})
5
- fail InvalidDocument, "the value of 'links' MUST be an object" unless
6
- links_hash.is_a?(Hash)
7
-
8
- @hash = links_hash
9
- @links = {}
10
- links_hash.each do |link_name, link_val|
11
- @links[link_name.to_s] = Link.new(link_val, options)
12
- define_singleton_method(link_name) do
13
- @links[link_name.to_s]
14
- end
15
- end
16
- end
17
-
18
- def to_hash
19
- @hash
20
- end
21
-
22
- def defined?(link_name)
23
- @links.key?(link_name.to_s)
24
- end
25
-
26
- def [](link_name)
27
- @links[link_name.to_s]
28
- end
29
-
30
- def keys
31
- @links.keys
32
- end
33
- end
34
- end
@@ -1,18 +0,0 @@
1
- require 'json'
2
-
3
- module JSONAPI
4
- module_function
5
-
6
- # Parse a JSON API document.
7
- #
8
- # @param document [Hash, String] the JSON API document.
9
- # @param options [Hash] options
10
- # @option options [Boolean] :id_optional (false) whether the resource
11
- # objects in the primary data must have an id
12
- # @return [JSON::API::Document]
13
- def parse(document, options = {})
14
- hash = document.is_a?(Hash) ? document : JSON.parse(document)
15
-
16
- Document.new(hash, options)
17
- end
18
- end
@@ -1,57 +0,0 @@
1
- module JSONAPI
2
- # c.f. http://jsonapi.org/format/#document-resource-object-relationships
3
- class Relationship
4
- attr_reader :data, :links, :meta
5
-
6
- def initialize(relationship_hash, options = {})
7
- @hash = relationship_hash
8
- @options = options
9
- @links_defined = relationship_hash.key?('links')
10
- @data_defined = relationship_hash.key?('data')
11
- @meta_defined = relationship_hash.key?('meta')
12
- links_hash = relationship_hash['links'] || {}
13
- @links = Links.new(links_hash, @options)
14
- @data = parse_linkage(relationship_hash['data']) if
15
- @data_defined
16
- @meta = relationship_hash['meta'] if @meta_defined
17
-
18
- validate!
19
- end
20
-
21
- def to_hash
22
- @hash
23
- end
24
-
25
- def collection?
26
- @data.is_a?(Array)
27
- end
28
-
29
- private
30
-
31
- def validate!
32
- case
33
- when !@links_defined && !@data_defined && !@meta_defined
34
- fail InvalidDocument,
35
- "a relationship object MUST contain at least one of 'links'," \
36
- " 'data', or 'meta'"
37
- when @links_defined &&
38
- !@links.defined?(:self) &&
39
- !@links.defined?(:related)
40
- fail InvalidDocument,
41
- "the 'links' object of a relationship object MUST contain at" \
42
- " least one of 'self' or 'related'"
43
- end
44
- end
45
-
46
- def parse_linkage(linkage_hash)
47
- collection = linkage_hash.is_a?(Array)
48
- if collection
49
- linkage_hash.map { |h| ResourceIdentifier.new(h, @options) }
50
- elsif linkage_hash.nil?
51
- nil
52
- else
53
- ResourceIdentifier.new(linkage_hash, @options)
54
- end
55
- end
56
- end
57
- end