jsonapi 0.1.1.beta2 → 0.1.1.beta6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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