easy-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/.github/workflows/publish-gem.yml +60 -0
- data/.github/workflows/rake.yml +35 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +106 -0
- data/LICENSE.txt +21 -0
- data/README.md +209 -0
- data/Rakefile +20 -0
- data/UsingTheRequestObject.md +74 -0
- data/UsingUserConfigurations.md +95 -0
- data/bin/bundle +114 -0
- data/bin/console +15 -0
- data/bin/htmldiff +29 -0
- data/bin/kramdown +29 -0
- data/bin/ldiff +29 -0
- data/bin/license_finder +29 -0
- data/bin/license_finder_pip.py +29 -0
- data/bin/maruku +29 -0
- data/bin/marutex +29 -0
- data/bin/nokogiri +29 -0
- data/bin/racc +29 -0
- data/bin/rackup +29 -0
- data/bin/rake +29 -0
- data/bin/redcarpet +29 -0
- data/bin/reverse_markdown +29 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/bin/ruby-parse +29 -0
- data/bin/ruby-rewrite +29 -0
- data/bin/setup +8 -0
- data/bin/solargraph +29 -0
- data/bin/thor +29 -0
- data/bin/tilt +29 -0
- data/bin/yard +29 -0
- data/bin/yardoc +29 -0
- data/bin/yri +29 -0
- data/easy-jsonapi.gemspec +39 -0
- data/lib/easy/jsonapi.rb +12 -0
- data/lib/easy/jsonapi/collection.rb +144 -0
- data/lib/easy/jsonapi/config_manager.rb +144 -0
- data/lib/easy/jsonapi/config_manager/config.rb +49 -0
- data/lib/easy/jsonapi/document.rb +71 -0
- data/lib/easy/jsonapi/document/error.rb +48 -0
- data/lib/easy/jsonapi/document/error/error_member.rb +15 -0
- data/lib/easy/jsonapi/document/jsonapi.rb +26 -0
- data/lib/easy/jsonapi/document/jsonapi/jsonapi_member.rb +15 -0
- data/lib/easy/jsonapi/document/links.rb +36 -0
- data/lib/easy/jsonapi/document/links/link.rb +15 -0
- data/lib/easy/jsonapi/document/meta.rb +26 -0
- data/lib/easy/jsonapi/document/meta/meta_member.rb +14 -0
- data/lib/easy/jsonapi/document/resource.rb +56 -0
- data/lib/easy/jsonapi/document/resource/attributes.rb +37 -0
- data/lib/easy/jsonapi/document/resource/attributes/attribute.rb +29 -0
- data/lib/easy/jsonapi/document/resource/relationships.rb +40 -0
- data/lib/easy/jsonapi/document/resource/relationships/relationship.rb +50 -0
- data/lib/easy/jsonapi/document/resource_id.rb +28 -0
- data/lib/easy/jsonapi/exceptions.rb +27 -0
- data/lib/easy/jsonapi/exceptions/document_exceptions.rb +619 -0
- data/lib/easy/jsonapi/exceptions/headers_exceptions.rb +156 -0
- data/lib/easy/jsonapi/exceptions/naming_exceptions.rb +36 -0
- data/lib/easy/jsonapi/exceptions/query_params_exceptions.rb +67 -0
- data/lib/easy/jsonapi/exceptions/user_defined_exceptions.rb +253 -0
- data/lib/easy/jsonapi/field.rb +43 -0
- data/lib/easy/jsonapi/header_collection.rb +38 -0
- data/lib/easy/jsonapi/header_collection/header.rb +11 -0
- data/lib/easy/jsonapi/item.rb +88 -0
- data/lib/easy/jsonapi/middleware.rb +158 -0
- data/lib/easy/jsonapi/name_value_pair.rb +72 -0
- data/lib/easy/jsonapi/name_value_pair_collection.rb +78 -0
- data/lib/easy/jsonapi/parser.rb +38 -0
- data/lib/easy/jsonapi/parser/document_parser.rb +196 -0
- data/lib/easy/jsonapi/parser/headers_parser.rb +33 -0
- data/lib/easy/jsonapi/parser/rack_req_params_parser.rb +117 -0
- data/lib/easy/jsonapi/request.rb +40 -0
- data/lib/easy/jsonapi/request/query_param_collection.rb +56 -0
- data/lib/easy/jsonapi/request/query_param_collection/fields_param.rb +32 -0
- data/lib/easy/jsonapi/request/query_param_collection/fields_param/fieldset.rb +34 -0
- data/lib/easy/jsonapi/request/query_param_collection/filter_param.rb +28 -0
- data/lib/easy/jsonapi/request/query_param_collection/filter_param/filter.rb +34 -0
- data/lib/easy/jsonapi/request/query_param_collection/include_param.rb +119 -0
- data/lib/easy/jsonapi/request/query_param_collection/page_param.rb +55 -0
- data/lib/easy/jsonapi/request/query_param_collection/query_param.rb +47 -0
- data/lib/easy/jsonapi/request/query_param_collection/sort_param.rb +25 -0
- data/lib/easy/jsonapi/response.rb +22 -0
- data/lib/easy/jsonapi/utility.rb +158 -0
- data/lib/easy/jsonapi/version.rb +8 -0
- metadata +248 -0
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'easy/jsonapi/exceptions/user_defined_exceptions'
|
4
|
+
require 'easy/jsonapi/parser/headers_parser'
|
5
|
+
require 'easy/jsonapi/utility'
|
6
|
+
|
7
|
+
module JSONAPI
|
8
|
+
module Exceptions
|
9
|
+
# Validates that Headers comply with the JSONAPI specification
|
10
|
+
module HeadersExceptions
|
11
|
+
|
12
|
+
# Media types that are complient with the spec if no parameters are included
|
13
|
+
JSONAPI_MEDIA_TYPES = ['application/vnd.api+json', '*/*', 'application/*'].freeze
|
14
|
+
|
15
|
+
# A more specific standard error to raise when an exception is found
|
16
|
+
class InvalidHeader < StandardError
|
17
|
+
attr_accessor :status_code
|
18
|
+
|
19
|
+
# Init w a status code, so that it can be accessed when rescuing an exception
|
20
|
+
def initialize(status_code)
|
21
|
+
@status_code = status_code
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Check http verb vs included headers
|
27
|
+
# @param env [Hash] The rack environment variable
|
28
|
+
def self.check_request(env, config_manager = nil, opts = {})
|
29
|
+
check_compliance(env, config_manager, opts)
|
30
|
+
check_http_method_against_headers(env)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check jsonapi compliance
|
34
|
+
# @param (see #check_request)
|
35
|
+
def self.check_compliance(env, config_manager = nil, opts = {})
|
36
|
+
check_content_type(env)
|
37
|
+
check_accept(env)
|
38
|
+
|
39
|
+
hdrs = JSONAPI::Parser::HeadersParser.parse(env)
|
40
|
+
usr_opts = { http_method: opts[:http_method], path: opts[:path] }
|
41
|
+
err_msg = JSONAPI::Exceptions::UserDefinedExceptions.check_user_header_requirements(hdrs, config_manager, usr_opts)
|
42
|
+
return err_msg unless err_msg.nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
class << self
|
46
|
+
private
|
47
|
+
|
48
|
+
# Checks the content type of the request to see if it is jsonapi.
|
49
|
+
# @param (see #compliant?)
|
50
|
+
# @return nil Returns nil if no error found
|
51
|
+
# @raise InvalidHeader if not jsonapi compliant
|
52
|
+
def check_content_type(env)
|
53
|
+
return if content_type_not_included_or_is_compliant?(env['CONTENT_TYPE'])
|
54
|
+
|
55
|
+
raise_error('Clients MUST send all JSON:API data in request documents with the header ' \
|
56
|
+
'Content-Type: application/vnd.api+json without any media type parameters.',
|
57
|
+
415)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Checks to see the Accept header includes jsonapi without params
|
61
|
+
# @param (see #compliant?)
|
62
|
+
def check_accept(env)
|
63
|
+
return if env['HTTP_ACCEPT'].nil? || # no accept header means compliant
|
64
|
+
contains_at_least_one_jsonapi_media_type_without_params?(env['HTTP_ACCEPT'])
|
65
|
+
|
66
|
+
raise_error('Clients that include the JSON:API media type in their Accept header MUST ' \
|
67
|
+
'specify the media type there at least once without any media type parameters.',
|
68
|
+
406)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @param content_type [String | NilClass] The http content-type header
|
72
|
+
# @return [TrueClass | FalseClass]
|
73
|
+
def content_type_not_included_or_is_compliant?(content_type)
|
74
|
+
content_type.nil? || content_type == 'application/vnd.api+json'
|
75
|
+
end
|
76
|
+
|
77
|
+
# Check the http verb against the content_type and accept header and raise
|
78
|
+
# error if the combination doesn't make sense
|
79
|
+
# @param (see #compliant?)
|
80
|
+
# @raise InvalidHeader the invalid header incombination with the http verb
|
81
|
+
def check_http_method_against_headers(env)
|
82
|
+
case env['REQUEST_METHOD']
|
83
|
+
when 'GET'
|
84
|
+
check_get_against_hdrs(env)
|
85
|
+
when 'POST' || 'PATCH' || 'PUT'
|
86
|
+
check_post_against_hdrs(env)
|
87
|
+
when 'DELETE'
|
88
|
+
check_delete_against_hdrs(env)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Raise error if a GET request has a body or a content type header
|
93
|
+
# @param (see #compliant?)
|
94
|
+
def check_get_against_hdrs(env)
|
95
|
+
raise_error('GET requests cannot have a body.') unless env['rack.input'].nil?
|
96
|
+
raise_error("GET request cannot have a 'CONTENT_TYPE' http header.") unless env['CONTENT_TYPE'].nil?
|
97
|
+
end
|
98
|
+
|
99
|
+
# POST, PUT, and PATCH request must have a content type header,
|
100
|
+
# a body, and a content-type and accept header that accepts jsonapi
|
101
|
+
# @param (see #compliant?)
|
102
|
+
def check_post_against_hdrs(env)
|
103
|
+
raise_error("POST, PUT, and PATCH requests must have a 'CONTENT_TYPE' header.") unless env['CONTENT_TYPE']
|
104
|
+
raise_error('POST, PUT, and PATCH requests must have a body.') unless env['rack.input']
|
105
|
+
|
106
|
+
return if env['CONTENT_TYPE'] == 'application/vnd.api+json' && accepts_jsonapi?(env)
|
107
|
+
|
108
|
+
raise_error('POST, PUT, and PATCH requests must have an ACCEPT header that includes the ' \
|
109
|
+
"JSON:API media type, if they include a JSON:API 'CONTENT_TYPE' header")
|
110
|
+
end
|
111
|
+
|
112
|
+
# Check the accept header to see if any of the provided media types indicate that
|
113
|
+
# jsonapi is accepted
|
114
|
+
# @param (see #compliant?)
|
115
|
+
def accepts_jsonapi?(env)
|
116
|
+
return true if env['HTTP_ACCEPT'].nil?
|
117
|
+
|
118
|
+
env['HTTP_ACCEPT'].split(',').each do |mt|
|
119
|
+
return true if JSONAPI_MEDIA_TYPES.include?(mt)
|
120
|
+
end
|
121
|
+
false
|
122
|
+
end
|
123
|
+
|
124
|
+
# Raise error if DELETE hdr has a body or a content type header
|
125
|
+
def check_delete_against_hdrs(env)
|
126
|
+
raise_error('DELETE requests cannot have a body.') unless env['rack.input'].nil?
|
127
|
+
raise_error("DELETE request cannot have a 'CONTENT_TYPE' http header.") unless env['CONTENT_TYPE'].nil?
|
128
|
+
end
|
129
|
+
|
130
|
+
# @param accept_hdr [String] The value of the http accept header
|
131
|
+
def contains_at_least_one_jsonapi_media_type_without_params?(accept_hdr)
|
132
|
+
accept_hdr.split(',').each do |mt|
|
133
|
+
if mt == 'application/vnd.api+json'
|
134
|
+
return true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
false
|
138
|
+
end
|
139
|
+
|
140
|
+
# Is the media type jsonapi and does it have included parameters
|
141
|
+
# @param media_type [String] One of the accepted media types
|
142
|
+
# @return [TrueClass | FalseClass]
|
143
|
+
def jsonapi_and_has_params?(media_type)
|
144
|
+
media_type_split = media_type.split(';')
|
145
|
+
JSONAPI_MEDIA_TYPES.include?(media_type_split.first) && \
|
146
|
+
contains_media_type_params?(media_type_split)
|
147
|
+
end
|
148
|
+
|
149
|
+
# @param msg [String] The message to raise InvalidHeader with.
|
150
|
+
def raise_error(msg, status_code = 400)
|
151
|
+
raise InvalidHeader.new(status_code), msg
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSONAPI
|
4
|
+
module Exceptions
|
5
|
+
|
6
|
+
# Checking for JSONAPI naming rules compliance
|
7
|
+
module NamingExceptions
|
8
|
+
|
9
|
+
# JSONAPI member names can only contain a-z, A-Z, 0-9, '-', '_', and the last two cannot be used
|
10
|
+
# at the start or end of a member name.
|
11
|
+
# @param name [String] The string to check for member name rule compliance
|
12
|
+
# @return
|
13
|
+
def self.check_member_constraints(name)
|
14
|
+
name = name.to_s
|
15
|
+
return 'Member names MUST contain at least one character' if name == ''
|
16
|
+
unless (name =~ /[^a-zA-Z0-9_-]/).nil?
|
17
|
+
return 'Member names MUST contain only the allowed characters: ' \
|
18
|
+
"a-z, A-Z, 0-9, '-', '_'"
|
19
|
+
end
|
20
|
+
unless (name[0] =~ /[-_]/).nil? && (name[-1] =~ /[-_]/).nil?
|
21
|
+
return 'Member names MUST start and end with a globally allowed character'
|
22
|
+
end
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# JSONAPI implementation specific query parameters follow the same constraints as member names
|
27
|
+
# with the additional requirement that they must also contain at least one non a-z character.
|
28
|
+
# @param name [String] The string to check for
|
29
|
+
def self.check_additional_constraints(name)
|
30
|
+
name = name.to_s
|
31
|
+
return nil unless (name =~ /[^a-z]/).nil?
|
32
|
+
'Implementation specific query parameters MUST contain at least one non a-z character'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'easy/jsonapi/exceptions/naming_exceptions'
|
4
|
+
require 'easy/jsonapi/exceptions/user_defined_exceptions'
|
5
|
+
|
6
|
+
module JSONAPI
|
7
|
+
module Exceptions
|
8
|
+
|
9
|
+
# Validates that the Query Parameters comply with the JSONAPI specification
|
10
|
+
module QueryParamsExceptions
|
11
|
+
|
12
|
+
# A more specific Standard Error to raise
|
13
|
+
class InvalidQueryParameter < StandardError
|
14
|
+
attr_accessor :status_code
|
15
|
+
|
16
|
+
# Init w a status code, so that it can be accessed when rescuing an exception
|
17
|
+
def initialize(status_code)
|
18
|
+
@status_code = status_code
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# The jsonapi specific query parameters.
|
24
|
+
SPECIAL_QUERY_PARAMS = %i[include fields page sort filter].freeze
|
25
|
+
|
26
|
+
# Checks to see if the query paramaters conform to the JSONAPI spec and raises InvalidQueryParameter
|
27
|
+
# if any parts are found to be non compliant
|
28
|
+
# @param rack_req_params [Hash] The hash of the query parameters given by Rack::Request
|
29
|
+
def self.check_compliance(rack_req_params, config_manager = nil, opts = {})
|
30
|
+
impl_spec_names = rack_req_params.keys - %w[include fields page sort filter]
|
31
|
+
impl_spec_names.each do |name|
|
32
|
+
check_param_name(name)
|
33
|
+
end
|
34
|
+
|
35
|
+
err_msg = JSONAPI::Exceptions::UserDefinedExceptions.check_user_query_param_requirements(rack_req_params, config_manager, opts)
|
36
|
+
return err_msg unless err_msg.nil?
|
37
|
+
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks an implementation specific param name to see if it complies to the spec.
|
42
|
+
def self.check_param_name(name)
|
43
|
+
should_return =
|
44
|
+
NamingExceptions.check_member_constraints(name).nil? && \
|
45
|
+
NamingExceptions.check_additional_constraints(name).nil? && \
|
46
|
+
!name.include?('-')
|
47
|
+
return if should_return
|
48
|
+
|
49
|
+
raise_error(
|
50
|
+
'Implementation specific query parameters MUST adhere to the same constraints ' \
|
51
|
+
'as member names. Allowed characters are: a-z, A-Z, 0-9 for beginning, middle, or end characters, ' \
|
52
|
+
"and '_' is allowed for middle characters. (While the JSON:API spec also allows '-', it is not " \
|
53
|
+
'recommended, and thus is prohibited in this implementation). ' \
|
54
|
+
'Implementation specific query members MUST contain at least one non a-z character as well. ' \
|
55
|
+
"Param name given: \"#{name}\""
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param msg [String] The message to raise InvalidQueryParameter with.
|
60
|
+
def self.raise_error(msg, status_code = 400)
|
61
|
+
raise InvalidQueryParameter.new(status_code), msg
|
62
|
+
end
|
63
|
+
|
64
|
+
private_class_method :raise_error
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'easy/jsonapi/exceptions/document_exceptions'
|
4
|
+
require 'easy/jsonapi/exceptions/headers_exceptions'
|
5
|
+
require 'easy/jsonapi/exceptions/query_params_exceptions'
|
6
|
+
|
7
|
+
module JSONAPI
|
8
|
+
module Exceptions
|
9
|
+
# Allows a user of the gem to define extra requirements they want the middlewar to check for.
|
10
|
+
module UserDefinedExceptions
|
11
|
+
|
12
|
+
# Invaliid Document Error when checking user defined restrictions
|
13
|
+
class InvalidComponent < StandardError
|
14
|
+
attr_accessor :status_code, :msg
|
15
|
+
|
16
|
+
def initialize(err)
|
17
|
+
@msg = err[0]
|
18
|
+
@status_code = err[1] || 400
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# The type of UserDefinedExceptions Error
|
24
|
+
class InvalidDocument < InvalidComponent
|
25
|
+
end
|
26
|
+
|
27
|
+
# The type of UserDefinedExceptions Error
|
28
|
+
class InvalidHeader < InvalidComponent
|
29
|
+
end
|
30
|
+
|
31
|
+
# The type of UserDefinedExceptions Error
|
32
|
+
class InvalidQueryParam < InvalidComponent
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
|
37
|
+
# Performs compliance checks on the document to see if it complies
|
38
|
+
# to the user defined requirements
|
39
|
+
# @param document [Hash] The hash representation of the json body
|
40
|
+
# @param config_manager [JSONAPI::ConfigManager] The config_manager option to retreive user
|
41
|
+
# requirements from
|
42
|
+
# @return [NilClass | String] Nil or the String Error Message
|
43
|
+
def check_user_document_requirements(document, config_manager, opts)
|
44
|
+
return if config_manager.nil?
|
45
|
+
|
46
|
+
config = get_config(config_manager, opts[:http_method], opts[:path])
|
47
|
+
err = check_for_client_generated_id(document, config.allow_client_ids, opts[:http_method])
|
48
|
+
|
49
|
+
return if config.default? && config_manager.size.positive?
|
50
|
+
|
51
|
+
if err.nil?
|
52
|
+
err = check_for_required_document_members(document, config.required_document_members)
|
53
|
+
end
|
54
|
+
# To add more user requirement features, add more methods here
|
55
|
+
|
56
|
+
JSONAPI::Exceptions::UserDefinedExceptions::InvalidDocument.new(err) unless err.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
# Performs compliance checks on the headers to see if it complies
|
60
|
+
# to the user defined requirements
|
61
|
+
# @param headers [Hash | JSONAPI::HeaderCollection] The collection of provided headers. Keys should be upper case strings
|
62
|
+
# with underscores instead of dashes.
|
63
|
+
# @param config (see #check_user_document_requirements)
|
64
|
+
def check_user_header_requirements(headers, config_manager, opts)
|
65
|
+
return if config_manager.nil? || config_manager.default?
|
66
|
+
config = get_config(config_manager, opts[:http_method], opts[:path])
|
67
|
+
|
68
|
+
err =
|
69
|
+
check_for_required_headers(headers, config.required_headers)
|
70
|
+
# To add more user requirement features, add more methods here
|
71
|
+
|
72
|
+
JSONAPI::Exceptions::UserDefinedExceptions::InvalidHeader.new(err) unless err.nil?
|
73
|
+
end
|
74
|
+
|
75
|
+
# Performs compliance checks on the query params to see if it complies
|
76
|
+
# to the user defined requirements
|
77
|
+
# @param rack_req_params [Hash] The hash of the query parameters given by Rack::Request
|
78
|
+
# @param config (see #check_user_document_requirements)
|
79
|
+
def check_user_query_param_requirements(rack_req_params, config_manager, opts)
|
80
|
+
return if config_manager.nil? || config_manager.default?
|
81
|
+
config = get_config(config_manager, opts[:http_method], opts[:path])
|
82
|
+
|
83
|
+
err =
|
84
|
+
check_for_required_params(rack_req_params, config.required_query_params)
|
85
|
+
# To add more user requirement features, add more methods here
|
86
|
+
|
87
|
+
JSONAPI::Exceptions::UserDefinedExceptions::InvalidQueryParam.new(err) unless err.nil?
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# ***************************
|
93
|
+
# * Document Helper Methods *
|
94
|
+
# ***************************
|
95
|
+
|
96
|
+
# Checks to see whether the document conatians all the require members
|
97
|
+
# See spec file for more examples.
|
98
|
+
# Ex:
|
99
|
+
# {
|
100
|
+
# data:
|
101
|
+
# [
|
102
|
+
# {
|
103
|
+
# type: nil,
|
104
|
+
# attributes: { a1: nil, a2: nil }
|
105
|
+
# }
|
106
|
+
# ],
|
107
|
+
# meta:
|
108
|
+
# {
|
109
|
+
# m1: nil
|
110
|
+
# }
|
111
|
+
# }
|
112
|
+
# @param document (see #check_user_document_requirements)
|
113
|
+
# @param req_mems[Hash] The hash representation of the user-defined required json members.
|
114
|
+
def check_for_required_document_members(document, req_mems)
|
115
|
+
if reached_value_to_check?(document, req_mems)
|
116
|
+
return check_values(document, req_mems)
|
117
|
+
end
|
118
|
+
|
119
|
+
err = check_structure(document, req_mems)
|
120
|
+
return err unless err.nil?
|
121
|
+
|
122
|
+
case req_mems
|
123
|
+
when Hash
|
124
|
+
req_mems.each do |k, v|
|
125
|
+
err = check_for_required_document_members(document[k], v)
|
126
|
+
return err unless err.nil?
|
127
|
+
end
|
128
|
+
when Array
|
129
|
+
document.each do |m|
|
130
|
+
err = check_for_required_document_members(m, req_mems.first)
|
131
|
+
return err unless err.nil?
|
132
|
+
end
|
133
|
+
end
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
|
137
|
+
# Check if same class or if req_mems nil. If not it indicates the user has specified set
|
138
|
+
# of required values for a given key. Check whether the current valueis within the set.
|
139
|
+
# @param (see #check_required_document_members)
|
140
|
+
# @return [NilClass | String] An error message if one found.
|
141
|
+
def check_structure(document, req_mems)
|
142
|
+
if both_are_hashes(document, req_mems)
|
143
|
+
doc_keys = document.keys
|
144
|
+
req_keys = req_mems.keys
|
145
|
+
return if doc_keys & req_keys == req_keys
|
146
|
+
["Document is missing user-defined required keys: #{req_keys - doc_keys}"]
|
147
|
+
else
|
148
|
+
return if document.instance_of?(req_mems.class) || req_mems.nil?
|
149
|
+
["User-defined required members hash does not mimic structure of json document: #{document}"]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def both_are_hashes(first, second)
|
154
|
+
first.is_a?(Hash) && second.is_a?(Hash)
|
155
|
+
end
|
156
|
+
|
157
|
+
# @return [TrueClass | FalseClass]
|
158
|
+
def reached_value_to_check?(document, req_mems)
|
159
|
+
(req_mems.is_a?(Proc) || req_mems.nil?) && !document.is_a?(Hash) && !document.is_a?(Array)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Checks whether a value given is within the permitted values
|
163
|
+
# @param value_given [Any]
|
164
|
+
def check_values(value_given, permitted_values)
|
165
|
+
return if permitted_values.nil? || (permitted_values.is_a?(Proc) && permitted_values.call(value_given))
|
166
|
+
["The user-defined Proc found at #{permitted_values.source_location}, evaluated the given value, #{value_given}, to be non compliant."]
|
167
|
+
end
|
168
|
+
|
169
|
+
# Checks if a resource id was included in the primary resource sent in a POST request
|
170
|
+
# @param document (see# #check_required_document_members)
|
171
|
+
# @param allow_client_ids [TrueClass | FalseClass] Does the user allow client generated
|
172
|
+
# ids
|
173
|
+
# @param http_method [String] Does the document belong to a POST request
|
174
|
+
def check_for_client_generated_id(document, allow_client_ids, http_method)
|
175
|
+
return unless http_method == 'POST' && !allow_client_ids
|
176
|
+
return unless JSONAPI::Utility.all_hash_path?(document, %i[data id])
|
177
|
+
|
178
|
+
msg = 'Document MUST return 403 Forbidden in response to an unsupported request ' \
|
179
|
+
'to create a resource with a client-generated ID.'
|
180
|
+
[msg, 403]
|
181
|
+
end
|
182
|
+
|
183
|
+
# *************************
|
184
|
+
# * Header Helper Methods *
|
185
|
+
# *************************
|
186
|
+
|
187
|
+
# Checks to makes sure the headers conatin the user defined required headers
|
188
|
+
# @param headers [Hash] The provided headers
|
189
|
+
# @param req_headers [Hash] The required headers
|
190
|
+
def check_for_required_headers(headers, req_headers)
|
191
|
+
return if req_headers.nil?
|
192
|
+
|
193
|
+
req_headers.each do |hdr|
|
194
|
+
h_name = hdr.to_s.upcase.gsub(/-/, '_')
|
195
|
+
unless headers[h_name]
|
196
|
+
return ["Headers missing one of the user-defined required headers: #{h_name}"]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
nil
|
200
|
+
end
|
201
|
+
|
202
|
+
# ******************************
|
203
|
+
# * Query Param Helper Methods *
|
204
|
+
# ******************************
|
205
|
+
|
206
|
+
# Checks to make sure the query params contain the user defined required params
|
207
|
+
# Rack Request Params Ex:
|
208
|
+
# {
|
209
|
+
# 'fields' => { 'articles' => 'title,body,author', 'people' => 'name' },
|
210
|
+
# 'include' => 'author,comments-likers,comments.users',
|
211
|
+
# 'josh_ua' => 'demoss,simpson',
|
212
|
+
# 'page' => { 'offset' => '5', 'limit' => '20' },
|
213
|
+
# 'filter' => { 'comments' => '(author/age > 21)', 'users' => '(age < 15)' },
|
214
|
+
# 'sort' => 'age,title'
|
215
|
+
# }
|
216
|
+
# Required Params Hash Ex:
|
217
|
+
# {
|
218
|
+
# fields: { articles: nil },
|
219
|
+
# include: nil
|
220
|
+
# page: nil
|
221
|
+
# }
|
222
|
+
# @param rack_req_params [Hash] The Rack::Request.params hash
|
223
|
+
# @param required_params [Hash] The user defined required query params
|
224
|
+
def check_for_required_params(rack_req_params, required_params)
|
225
|
+
return if required_params.nil?
|
226
|
+
|
227
|
+
case required_params
|
228
|
+
when Hash
|
229
|
+
required_params.each do |k, v|
|
230
|
+
return ["Query Params missing one of the user-defined required query params: #{k}"] unless rack_req_params[k.to_s]
|
231
|
+
err = check_for_required_params(rack_req_params[k.to_s], v)
|
232
|
+
return err unless err.nil?
|
233
|
+
end
|
234
|
+
when nil
|
235
|
+
return
|
236
|
+
else
|
237
|
+
return ['The user-defined required query params hash must contain keys with values either hash or nil']
|
238
|
+
end
|
239
|
+
nil
|
240
|
+
end
|
241
|
+
|
242
|
+
def get_config(config_manager, http_method, path)
|
243
|
+
if http_method
|
244
|
+
res_type = JSONAPI::Utility.path_to_res_type(path)
|
245
|
+
config_manager[res_type] || config_manager.global
|
246
|
+
else
|
247
|
+
config_manager.global
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|