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