easy-jsonapi 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/publish-gem.yml +60 -0
  3. data/.github/workflows/rake.yml +35 -0
  4. data/.rspec +3 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +5 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +5 -0
  9. data/Gemfile.lock +106 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +209 -0
  12. data/Rakefile +20 -0
  13. data/UsingTheRequestObject.md +74 -0
  14. data/UsingUserConfigurations.md +95 -0
  15. data/bin/bundle +114 -0
  16. data/bin/console +15 -0
  17. data/bin/htmldiff +29 -0
  18. data/bin/kramdown +29 -0
  19. data/bin/ldiff +29 -0
  20. data/bin/license_finder +29 -0
  21. data/bin/license_finder_pip.py +29 -0
  22. data/bin/maruku +29 -0
  23. data/bin/marutex +29 -0
  24. data/bin/nokogiri +29 -0
  25. data/bin/racc +29 -0
  26. data/bin/rackup +29 -0
  27. data/bin/rake +29 -0
  28. data/bin/redcarpet +29 -0
  29. data/bin/reverse_markdown +29 -0
  30. data/bin/rspec +29 -0
  31. data/bin/rubocop +29 -0
  32. data/bin/ruby-parse +29 -0
  33. data/bin/ruby-rewrite +29 -0
  34. data/bin/setup +8 -0
  35. data/bin/solargraph +29 -0
  36. data/bin/thor +29 -0
  37. data/bin/tilt +29 -0
  38. data/bin/yard +29 -0
  39. data/bin/yardoc +29 -0
  40. data/bin/yri +29 -0
  41. data/easy-jsonapi.gemspec +39 -0
  42. data/lib/easy/jsonapi.rb +12 -0
  43. data/lib/easy/jsonapi/collection.rb +144 -0
  44. data/lib/easy/jsonapi/config_manager.rb +144 -0
  45. data/lib/easy/jsonapi/config_manager/config.rb +49 -0
  46. data/lib/easy/jsonapi/document.rb +71 -0
  47. data/lib/easy/jsonapi/document/error.rb +48 -0
  48. data/lib/easy/jsonapi/document/error/error_member.rb +15 -0
  49. data/lib/easy/jsonapi/document/jsonapi.rb +26 -0
  50. data/lib/easy/jsonapi/document/jsonapi/jsonapi_member.rb +15 -0
  51. data/lib/easy/jsonapi/document/links.rb +36 -0
  52. data/lib/easy/jsonapi/document/links/link.rb +15 -0
  53. data/lib/easy/jsonapi/document/meta.rb +26 -0
  54. data/lib/easy/jsonapi/document/meta/meta_member.rb +14 -0
  55. data/lib/easy/jsonapi/document/resource.rb +56 -0
  56. data/lib/easy/jsonapi/document/resource/attributes.rb +37 -0
  57. data/lib/easy/jsonapi/document/resource/attributes/attribute.rb +29 -0
  58. data/lib/easy/jsonapi/document/resource/relationships.rb +40 -0
  59. data/lib/easy/jsonapi/document/resource/relationships/relationship.rb +50 -0
  60. data/lib/easy/jsonapi/document/resource_id.rb +28 -0
  61. data/lib/easy/jsonapi/exceptions.rb +27 -0
  62. data/lib/easy/jsonapi/exceptions/document_exceptions.rb +619 -0
  63. data/lib/easy/jsonapi/exceptions/headers_exceptions.rb +156 -0
  64. data/lib/easy/jsonapi/exceptions/naming_exceptions.rb +36 -0
  65. data/lib/easy/jsonapi/exceptions/query_params_exceptions.rb +67 -0
  66. data/lib/easy/jsonapi/exceptions/user_defined_exceptions.rb +253 -0
  67. data/lib/easy/jsonapi/field.rb +43 -0
  68. data/lib/easy/jsonapi/header_collection.rb +38 -0
  69. data/lib/easy/jsonapi/header_collection/header.rb +11 -0
  70. data/lib/easy/jsonapi/item.rb +88 -0
  71. data/lib/easy/jsonapi/middleware.rb +158 -0
  72. data/lib/easy/jsonapi/name_value_pair.rb +72 -0
  73. data/lib/easy/jsonapi/name_value_pair_collection.rb +78 -0
  74. data/lib/easy/jsonapi/parser.rb +38 -0
  75. data/lib/easy/jsonapi/parser/document_parser.rb +196 -0
  76. data/lib/easy/jsonapi/parser/headers_parser.rb +33 -0
  77. data/lib/easy/jsonapi/parser/rack_req_params_parser.rb +117 -0
  78. data/lib/easy/jsonapi/request.rb +40 -0
  79. data/lib/easy/jsonapi/request/query_param_collection.rb +56 -0
  80. data/lib/easy/jsonapi/request/query_param_collection/fields_param.rb +32 -0
  81. data/lib/easy/jsonapi/request/query_param_collection/fields_param/fieldset.rb +34 -0
  82. data/lib/easy/jsonapi/request/query_param_collection/filter_param.rb +28 -0
  83. data/lib/easy/jsonapi/request/query_param_collection/filter_param/filter.rb +34 -0
  84. data/lib/easy/jsonapi/request/query_param_collection/include_param.rb +119 -0
  85. data/lib/easy/jsonapi/request/query_param_collection/page_param.rb +55 -0
  86. data/lib/easy/jsonapi/request/query_param_collection/query_param.rb +47 -0
  87. data/lib/easy/jsonapi/request/query_param_collection/sort_param.rb +25 -0
  88. data/lib/easy/jsonapi/response.rb +22 -0
  89. data/lib/easy/jsonapi/utility.rb +158 -0
  90. data/lib/easy/jsonapi/version.rb +8 -0
  91. 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