insights-api-common 3.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/LICENSE.txt +202 -0
- data/README.md +102 -0
- data/Rakefile +18 -0
- data/app/models/authentication.rb +19 -0
- data/app/models/concerns/encryption_concern.rb +52 -0
- data/app/models/encryption.rb +13 -0
- data/lib/generators/shared_utilities/migration_generator.rb +79 -0
- data/lib/generators/shared_utilities/orm_helper.rb +25 -0
- data/lib/generators/shared_utilities/templates/migration.rb +27 -0
- data/lib/generators/shared_utilities/templates/migration_existing.rb +28 -0
- data/lib/insights.rb +1 -0
- data/lib/insights/api/common.rb +12 -0
- data/lib/insights/api/common/application_controller_mixins/api_doc.rb +39 -0
- data/lib/insights/api/common/application_controller_mixins/common.rb +27 -0
- data/lib/insights/api/common/application_controller_mixins/exception_handling.rb +41 -0
- data/lib/insights/api/common/application_controller_mixins/openapi_enabled.rb +13 -0
- data/lib/insights/api/common/application_controller_mixins/parameters.rb +134 -0
- data/lib/insights/api/common/application_controller_mixins/request_body_validation.rb +48 -0
- data/lib/insights/api/common/application_controller_mixins/request_parameter_validation.rb +29 -0
- data/lib/insights/api/common/application_controller_mixins/request_path.rb +70 -0
- data/lib/insights/api/common/engine.rb +20 -0
- data/lib/insights/api/common/entitlement.rb +37 -0
- data/lib/insights/api/common/error_document.rb +29 -0
- data/lib/insights/api/common/filter.rb +175 -0
- data/lib/insights/api/common/graphql.rb +127 -0
- data/lib/insights/api/common/graphql/associated_records.rb +44 -0
- data/lib/insights/api/common/graphql/association_loader.rb +35 -0
- data/lib/insights/api/common/graphql/generator.rb +148 -0
- data/lib/insights/api/common/graphql/templates/model_type.erb +35 -0
- data/lib/insights/api/common/graphql/templates/query_type.erb +49 -0
- data/lib/insights/api/common/graphql/templates/schema.erb +6 -0
- data/lib/insights/api/common/graphql/types/big_int.rb +23 -0
- data/lib/insights/api/common/graphql/types/date_time.rb +16 -0
- data/lib/insights/api/common/graphql/types/query_filter.rb +16 -0
- data/lib/insights/api/common/graphql/types/query_sort_by.rb +16 -0
- data/lib/insights/api/common/inflections.rb +28 -0
- data/lib/insights/api/common/logging.rb +17 -0
- data/lib/insights/api/common/metrics.rb +39 -0
- data/lib/insights/api/common/middleware/web_server_metrics.rb +62 -0
- data/lib/insights/api/common/open_api.rb +2 -0
- data/lib/insights/api/common/open_api/docs.rb +54 -0
- data/lib/insights/api/common/open_api/docs/component_collection.rb +67 -0
- data/lib/insights/api/common/open_api/docs/doc_v3.rb +102 -0
- data/lib/insights/api/common/open_api/docs/object_definition.rb +39 -0
- data/lib/insights/api/common/open_api/generator.rb +520 -0
- data/lib/insights/api/common/open_api/serializer.rb +31 -0
- data/lib/insights/api/common/option_redirect_enhancements.rb +23 -0
- data/lib/insights/api/common/paginated_response.rb +108 -0
- data/lib/insights/api/common/rbac/access.rb +66 -0
- data/lib/insights/api/common/rbac/acl.rb +74 -0
- data/lib/insights/api/common/rbac/policies.rb +33 -0
- data/lib/insights/api/common/rbac/query_shared_resource.rb +45 -0
- data/lib/insights/api/common/rbac/roles.rb +77 -0
- data/lib/insights/api/common/rbac/seed.rb +140 -0
- data/lib/insights/api/common/rbac/service.rb +67 -0
- data/lib/insights/api/common/rbac/share_resource.rb +60 -0
- data/lib/insights/api/common/rbac/unshare_resource.rb +32 -0
- data/lib/insights/api/common/rbac/utilities.rb +30 -0
- data/lib/insights/api/common/request.rb +111 -0
- data/lib/insights/api/common/routing.rb +26 -0
- data/lib/insights/api/common/user.rb +48 -0
- data/lib/insights/api/common/version.rb +7 -0
- data/lib/tasks/insights/api/common_tasks.rake +4 -0
- data/spec/support/default_as_json.rb +17 -0
- data/spec/support/rbac_shared_contexts.rb +44 -0
- data/spec/support/requests_spec_helper.rb +7 -0
- data/spec/support/service_spec_helper.rb +26 -0
- data/spec/support/user_header_spec_helper.rb +68 -0
- metadata +403 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module SharedUtilities
|
2
|
+
module OrmHelper
|
3
|
+
private
|
4
|
+
|
5
|
+
def model_exists?
|
6
|
+
File.exist?(File.join(destination_root, model_path))
|
7
|
+
end
|
8
|
+
|
9
|
+
def migration_exists?(table_name)
|
10
|
+
Dir.glob("#{File.join(destination_root, migration_path)}/[0-9]*_*.rb").grep(/\d+_add_devise_to_#{table_name}.rb$/).first
|
11
|
+
end
|
12
|
+
|
13
|
+
def migration_path
|
14
|
+
if Rails.version >= '5.0.3'
|
15
|
+
db_migrate_path
|
16
|
+
else
|
17
|
+
@migration_path ||= File.join("db", "migrate")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def model_path
|
22
|
+
@model_path ||= File.join("app", "models", "#{file_path}.rb")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Create<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def change
|
3
|
+
create_table :<%= table_name %><%= primary_key_type %> do |t|
|
4
|
+
<%= migration_data -%>
|
5
|
+
|
6
|
+
<% attributes.each do |attribute| -%>
|
7
|
+
t.<%= attribute.type %> :<%= attribute.name %>
|
8
|
+
<% end -%>
|
9
|
+
t.references "resource", :polymorphic => true, :index => true
|
10
|
+
t.string :name
|
11
|
+
t.string :authtype
|
12
|
+
t.string :status
|
13
|
+
t.string :status_details
|
14
|
+
t.bigint :tenant_id
|
15
|
+
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table :encryptions<%= primary_key_type %> do |t|
|
20
|
+
t.references "authentication", :index => true
|
21
|
+
t.string :secret
|
22
|
+
t.bigint :tenant_id
|
23
|
+
|
24
|
+
t.timestamps
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class UpdateOn<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def self.up
|
3
|
+
change_table :<%= table_name %> do |t|
|
4
|
+
<%= migration_data -%>
|
5
|
+
|
6
|
+
<% attributes.each do |attribute| -%>
|
7
|
+
t.<%= attribute.type %> :<%= attribute.name %>
|
8
|
+
<% end -%>
|
9
|
+
|
10
|
+
# Uncomment below if timestamps were not included in your original model.
|
11
|
+
# t.timestamps null: false
|
12
|
+
end
|
13
|
+
|
14
|
+
# Assumption is made there is no 'encryptions' table, so creating it here
|
15
|
+
create_table :encryptions<%= primary_key_type %> do |t|
|
16
|
+
t.references "<%= table_name %>", :index => true
|
17
|
+
t.string :secret
|
18
|
+
t.bigint :tenant_id
|
19
|
+
|
20
|
+
t.timestamps
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.down
|
25
|
+
# By default, we don't want to make any assumption about how to roll back a migration when your
|
26
|
+
# model already existed. Please edit below which fields you would like to remove in this migration.
|
27
|
+
raise ActiveRecord::IrreversibleMigration
|
28
|
+
end
|
data/lib/insights.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'insights/api/common'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "insights/api/common/engine"
|
2
|
+
require "insights/api/common/entitlement"
|
3
|
+
require "insights/api/common/error_document"
|
4
|
+
require "insights/api/common/filter"
|
5
|
+
require "insights/api/common/inflections"
|
6
|
+
require "insights/api/common/logging"
|
7
|
+
require "insights/api/common/metrics"
|
8
|
+
require "insights/api/common/open_api"
|
9
|
+
require "insights/api/common/option_redirect_enhancements"
|
10
|
+
require "insights/api/common/request"
|
11
|
+
require "insights/api/common/routing"
|
12
|
+
require "insights/api/common/user"
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module ApplicationControllerMixins
|
5
|
+
module ApiDoc
|
6
|
+
def self.included(other)
|
7
|
+
other.extend(self::ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def api_doc
|
13
|
+
self.class.send(:api_doc)
|
14
|
+
end
|
15
|
+
|
16
|
+
def api_doc_definition
|
17
|
+
self.class.send(:api_doc_definition)
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
private
|
22
|
+
|
23
|
+
def api_doc
|
24
|
+
@api_doc ||= ::Insights::API::Common::OpenApi::Docs.instance[api_version[1..-1].sub(/x/, ".")]
|
25
|
+
end
|
26
|
+
|
27
|
+
def api_doc_definition
|
28
|
+
@api_doc_definition ||= api_doc.definitions[model.name]
|
29
|
+
end
|
30
|
+
|
31
|
+
def api_version
|
32
|
+
@api_version ||= name.split("::")[1].downcase
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module ApplicationControllerMixins
|
5
|
+
module Common
|
6
|
+
def self.included(other)
|
7
|
+
other.extend(self::ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def model
|
13
|
+
self.class.send(:model)
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
private
|
18
|
+
|
19
|
+
def model
|
20
|
+
@model ||= controller_name.classify.constantize
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module ApplicationControllerMixins
|
5
|
+
module ExceptionHandling
|
6
|
+
DEFAULT_ERROR_CODE = 400
|
7
|
+
|
8
|
+
def self.included(other)
|
9
|
+
other.rescue_from(StandardError, RuntimeError) do |exception|
|
10
|
+
errors = Insights::API::Common::ErrorDocument.new.tap do |error_document|
|
11
|
+
exception_list_from(exception).each do |exc|
|
12
|
+
code = exc.respond_to?(:code) ? exc.code : error_code_from_class(exc)
|
13
|
+
error_document.add(code, "#{exc.class}: #{exc.message}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
render :json => errors.to_h, :status => error_code_from_class(exception)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def exception_list_from(exception)
|
22
|
+
[].tap do |arr|
|
23
|
+
until exception.nil?
|
24
|
+
arr << exception
|
25
|
+
exception = exception.cause
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def error_code_from_class(exception)
|
31
|
+
if ActionDispatch::ExceptionWrapper.rescue_responses.key?(exception.class.to_s)
|
32
|
+
ActionDispatch::ExceptionWrapper.rescue_responses[exception.class.to_s]
|
33
|
+
else
|
34
|
+
DEFAULT_ERROR_CODE
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module ApplicationControllerMixins
|
5
|
+
module Parameters
|
6
|
+
def self.included(other)
|
7
|
+
other.include(OpenapiEnabled)
|
8
|
+
end
|
9
|
+
|
10
|
+
def params_for_create
|
11
|
+
check_if_openapi_enabled
|
12
|
+
# We already validate this with OpenAPI validator, that validates every request, so we shouldn't do it again here.
|
13
|
+
body_params.permit!
|
14
|
+
end
|
15
|
+
|
16
|
+
def safe_params_for_list
|
17
|
+
check_if_openapi_enabled
|
18
|
+
# :limit & :offset can be passed in for pagination purposes, but shouldn't show up as params for filtering purposes
|
19
|
+
@safe_params_for_list ||= params.merge(params_for_polymorphic_subcollection).permit(*permitted_params, :filter => {}, :sort_by => [])
|
20
|
+
end
|
21
|
+
|
22
|
+
def permitted_params
|
23
|
+
check_if_openapi_enabled
|
24
|
+
api_doc_definition.all_attributes + [:limit, :offset, :sort_by] + [subcollection_foreign_key]
|
25
|
+
end
|
26
|
+
|
27
|
+
def subcollection_foreign_key
|
28
|
+
"#{request_path_parts["primary_collection_name"].singularize}_id"
|
29
|
+
end
|
30
|
+
|
31
|
+
def params_for_polymorphic_subcollection
|
32
|
+
return {} unless subcollection?
|
33
|
+
return {} unless reflection = primary_collection_model&.reflect_on_association(request_path_parts["subcollection_name"])
|
34
|
+
return {} unless as = reflection.options[:as]
|
35
|
+
|
36
|
+
{"#{as}_type" => primary_collection_model.name, "#{as}_id" => request_path_parts["primary_collection_id"]}
|
37
|
+
end
|
38
|
+
|
39
|
+
def primary_collection_model
|
40
|
+
@primary_collection_model ||= request_path_parts["primary_collection_name"].singularize.classify.safe_constantize
|
41
|
+
end
|
42
|
+
|
43
|
+
def params_for_list
|
44
|
+
check_if_openapi_enabled
|
45
|
+
safe_params = safe_params_for_list.slice(*all_attributes_for_index)
|
46
|
+
if safe_params[subcollection_foreign_key_using_through_relation]
|
47
|
+
# If this is a through relation, we need to replace the :foreign_key by the foreign key with right table
|
48
|
+
# information. So e.g. :container_images with :tags subcollection will have {:container_image_id => ID} and we need
|
49
|
+
# to replace it with {:container_images_tags => {:container_image_id => ID}}, where :container_images_tags is the
|
50
|
+
# name of the mapping table.
|
51
|
+
safe_params[through_relation_klass.table_name.to_sym] = {
|
52
|
+
subcollection_foreign_key_using_through_relation => safe_params.delete(subcollection_foreign_key_using_through_relation)
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
safe_params
|
57
|
+
end
|
58
|
+
|
59
|
+
def through_relation_klass
|
60
|
+
check_if_openapi_enabled
|
61
|
+
return unless subcollection?
|
62
|
+
return unless reflection = primary_collection_model&.reflect_on_association(request_path_parts["subcollection_name"])
|
63
|
+
return unless through = reflection.options[:through]
|
64
|
+
|
65
|
+
primary_collection_model&.reflect_on_association(through).klass
|
66
|
+
end
|
67
|
+
|
68
|
+
def through_relation_name
|
69
|
+
check_if_openapi_enabled
|
70
|
+
# Through relation name taken from the subcollection model side, so we can use this for table join.
|
71
|
+
return unless through_relation_klass
|
72
|
+
return unless through_relation_association = model.reflect_on_all_associations.detect { |x| !x.polymorphic? && x.klass == through_relation_klass }
|
73
|
+
|
74
|
+
through_relation_association.name
|
75
|
+
end
|
76
|
+
|
77
|
+
def subcollection_foreign_key_using_through_relation
|
78
|
+
return unless through_relation_klass
|
79
|
+
|
80
|
+
subcollection_foreign_key
|
81
|
+
end
|
82
|
+
|
83
|
+
def all_attributes_for_index
|
84
|
+
check_if_openapi_enabled
|
85
|
+
api_doc_definition.all_attributes + [subcollection_foreign_key_using_through_relation]
|
86
|
+
end
|
87
|
+
|
88
|
+
def filtered
|
89
|
+
check_if_openapi_enabled
|
90
|
+
Insights::API::Common::Filter.new(model, safe_params_for_list[:filter], api_doc_definition).apply
|
91
|
+
end
|
92
|
+
|
93
|
+
def pagination_limit
|
94
|
+
safe_params_for_list[:limit]
|
95
|
+
end
|
96
|
+
|
97
|
+
def pagination_offset
|
98
|
+
safe_params_for_list[:offset]
|
99
|
+
end
|
100
|
+
|
101
|
+
def query_sort_by
|
102
|
+
safe_params_for_list[:sort_by]
|
103
|
+
end
|
104
|
+
|
105
|
+
def params_for_update
|
106
|
+
check_if_openapi_enabled
|
107
|
+
# We already validate this with OpenAPI validator, here only to satisfy the strong parameters check
|
108
|
+
attr_list = *api_doc_definition.all_attributes - api_doc_definition.read_only_attributes
|
109
|
+
strong_params_hash = sanctified_permit_param(api_doc_definition, attr_list)
|
110
|
+
body_params.permit(strong_params_hash)
|
111
|
+
end
|
112
|
+
|
113
|
+
def sanctified_permit_param(api_doc_definition, attributes)
|
114
|
+
api_doc_definition['properties'].each_with_object([]) do |(k, v), memo|
|
115
|
+
next unless attributes.each { |attr| attr.include?(k) }
|
116
|
+
|
117
|
+
memo << if v['type'] == 'array'
|
118
|
+
{ k => [] }
|
119
|
+
elsif v['type'] == 'object'
|
120
|
+
{ k => {} }
|
121
|
+
else
|
122
|
+
k
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def check_if_openapi_enabled
|
128
|
+
raise ArgumentError, "Openapi not enabled" unless self.class.openapi_enabled
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module ApplicationControllerMixins
|
5
|
+
module RequestBodyValidation
|
6
|
+
class BodyParseError < ::RuntimeError
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.included(other)
|
10
|
+
ActionController::Parameters.action_on_unpermitted_parameters = :raise
|
11
|
+
|
12
|
+
other.include(OpenapiEnabled)
|
13
|
+
|
14
|
+
other.before_action(:validate_request)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def body_params
|
20
|
+
@body_params ||= begin
|
21
|
+
raw_body = request.body.read
|
22
|
+
parsed_body = raw_body.blank? ? {} : JSON.parse(raw_body)
|
23
|
+
ActionController::Parameters.new(parsed_body).permit!
|
24
|
+
rescue JSON::ParserError
|
25
|
+
raise Insights::API::Common::ApplicationControllerMixins::RequestBodyValidation::BodyParseError, "Failed to parse request body, expected JSON"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Validates against openapi.json
|
30
|
+
# - only for HTTP POST/PATCH
|
31
|
+
def validate_request
|
32
|
+
return unless request.post? || request.patch?
|
33
|
+
return unless self.class.openapi_enabled
|
34
|
+
|
35
|
+
api_version = self.class.send(:api_version)[1..-1].sub(/x/, ".")
|
36
|
+
|
37
|
+
self.class.send(:api_doc).validate!(
|
38
|
+
request.method,
|
39
|
+
request.path,
|
40
|
+
api_version,
|
41
|
+
body_params.as_json
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module ApplicationControllerMixins
|
5
|
+
module RequestParameterValidation
|
6
|
+
def self.included(other)
|
7
|
+
other.include(OpenapiEnabled)
|
8
|
+
|
9
|
+
other.before_action(:validate_request_parameters)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def validate_request_parameters
|
15
|
+
api_version = self.class.send(:api_version)[1..-1].sub(/x/, ".")
|
16
|
+
|
17
|
+
api_doc.try(
|
18
|
+
:validate_parameters!,
|
19
|
+
request.method,
|
20
|
+
request.path,
|
21
|
+
api_version,
|
22
|
+
params.slice(:sort_by)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module ApplicationControllerMixins
|
5
|
+
module RequestPath
|
6
|
+
class RequestPathError < ::RuntimeError
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.included(other)
|
10
|
+
other.extend(self::ClassMethods)
|
11
|
+
|
12
|
+
other.before_action(:validate_primary_collection_id)
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_path
|
16
|
+
request.env["REQUEST_URI"]
|
17
|
+
end
|
18
|
+
|
19
|
+
def request_path_parts
|
20
|
+
@request_path_parts ||= begin
|
21
|
+
path, _query = request_path.split("?")
|
22
|
+
path.match(/\/(?<full_version_string>v\d+.\d+)\/(?<primary_collection_name>\w+)(\/(?<primary_collection_id>[^\/]+)(\/(?<subcollection_name>\w+))?)?/)&.named_captures || {}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def subcollection?
|
27
|
+
!!(request_path_parts["subcollection_name"] && request_path_parts["primary_collection_id"] && request_path_parts["primary_collection_name"])
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def id_regexp
|
33
|
+
self.class.send(:id_regexp, request_path_parts["primary_collection_name"])
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_primary_collection_id
|
37
|
+
id = request_path_parts["primary_collection_id"]
|
38
|
+
return if id.blank?
|
39
|
+
|
40
|
+
raise RequestPathError, "ID is invalid" unless id.match(id_regexp)
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
private
|
45
|
+
|
46
|
+
def id_regexp(primary_collection_name)
|
47
|
+
@id_regexp ||= begin
|
48
|
+
id_parameter = id_parameter_from_api_doc(primary_collection_name)
|
49
|
+
id_parameter ? id_parameter.fetch_path("schema", "pattern") : /^\d+$/
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def id_parameter_from_api_doc(primary_collection_name)
|
54
|
+
# Find the id parameter in the documented route
|
55
|
+
id_parameter = api_doc.paths.fetch_path("/#{primary_collection_name}/{id}", "get", "parameters", 0)
|
56
|
+
# The route isn't documented, return nil
|
57
|
+
return unless id_parameter
|
58
|
+
|
59
|
+
# Return the id parameter or resolve the reference to it and return that
|
60
|
+
reference = id_parameter["$ref"]
|
61
|
+
return id_parameter unless reference
|
62
|
+
|
63
|
+
api_doc.parameters[reference.split("parameters/").last]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|