Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* [#2580](https://github.com/ruby-grape/grape/pull/2580): Refactor endpoint helpers and error middleware integration - [@ericproulx](https://github.com/ericproulx).
* [#2581](https://github.com/ruby-grape/grape/pull/2581): Delegate `to_s` in Grape::API::Instance - [@ericproulx](https://github.com/ericproulx).
* [#2582](https://github.com/ruby-grape/grape/pull/2582): Fix leaky slash when normalizing - [@ericproulx](https://github.com/ericproulx).
* [#2583](https://github.com/ruby-grape/grape/pull/2583): Optimize api parameter documentation and memory usage - [@ericproulx](https://github.com/ericproulx).
* Your contribution here.

#### Fixes
Expand Down
60 changes: 0 additions & 60 deletions lib/grape/validations/attributes_doc.rb

This file was deleted.

52 changes: 52 additions & 0 deletions lib/grape/validations/params_documentation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

module Grape
module Validations
# Documents parameters of an endpoint. If documentation isn't needed (for instance, it is an
# internal API), the class only cleans up attributes to avoid junk in RAM.

module ParamsDocumentation
def document_params(attrs, validations, type = nil, values = nil, except_values = nil)
if @api.namespace_inheritable(:do_not_document)
validations.except!(:desc, :description, :documentation)
else
documented_attrs = attrs.each_with_object({}) do |name, memo|
memo[full_name(name)] = extract_details(validations, type, values, except_values)
end
@api.namespace_stackable(:params, documented_attrs)
end
end

private

def extract_details(validations, type, values, except_values)
{}.tap do |details|
details[:required] = validations.key?(:presence)
details[:type] = TypeCache[type] if type
details[:values] = values if values
details[:except_values] = except_values if except_values
details[:default] = validations[:default] if validations.key?(:default)
if validations.key?(:length)
details[:min_length] = validations[:length][:min] if validations[:length].key?(:min)
details[:max_length] = validations[:length][:max] if validations[:length].key?(:max)
end

desc = validations.delete(:desc) || validations.delete(:description)
details[:desc] = desc if desc

documentation = validations.delete(:documentation)
details[:documentation] = documentation if documentation
end
end

class TypeCache < Grape::Util::Cache
def initialize
super
@cache = Hash.new do |h, type|
h[type] = type.to_s
end
end
end
end
end
end
32 changes: 12 additions & 20 deletions lib/grape/validations/params_scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class ParamsScope
attr_reader :type, :params_meeting_dependency

include Grape::DSL::Parameters
include Grape::Validations::ParamsDocumentation

# There are a number of documentation options on entities that don't have
# corresponding validators. Since there is nowhere that enumerates them all,
Expand Down Expand Up @@ -323,23 +324,14 @@ def configure_declared_params
end

def validates(attrs, validations)
doc = AttributesDoc.new @api, self
doc.extract_details validations

coerce_type = infer_coercion(validations)

doc.type = coerce_type

required = validations.key?(:presence)
default = validations[:default]
values = validations[:values].is_a?(Hash) ? validations.dig(:values, :value) : validations[:values]

doc.values = values

except_values = validations[:except_values].is_a?(Hash) ? validations.dig(:except_values, :value) : validations[:except_values]

# NB. values and excepts should be nil, Proc, Array, or Range.
# Specifically, values should NOT be a Hash

# use values or excepts to guess coerce type when stated type is Array
coerce_type = guess_coerce_type(coerce_type, values, except_values)

Expand All @@ -349,23 +341,23 @@ def validates(attrs, validations)
# type should be compatible with values array, if both exist
validate_value_coercion(coerce_type, values, except_values)

doc.document attrs
document_params attrs, validations, coerce_type, values, except_values

opts = derive_validator_options(validations)

# Validate for presence before any other validators
validates_presence(validations, attrs, doc, opts)
validates_presence(validations, attrs, opts)

# Before we run the rest of the validators, let's handle
# whatever coercion so that we are working with correctly
# type casted values
coerce_type validations, attrs, doc, opts
coerce_type validations, attrs, required, opts

validations.each do |type, options|
# Don't try to look up validators for documentation params that don't have one.
next if RESERVED_DOCUMENTATION_KEYWORDS.include?(type)

validate(type, options, attrs, doc, opts)
validate(type, options, attrs, required, opts)
end
end

Expand Down Expand Up @@ -429,7 +421,7 @@ def check_coerce_with(validations)
# composited from more than one +requires+/+optional+
# parameter, and needs to be run before most other
# validations.
def coerce_type(validations, attrs, doc, opts)
def coerce_type(validations, attrs, required, opts)
check_coerce_with(validations)

return unless validations.key?(:coerce)
Expand All @@ -439,7 +431,7 @@ def coerce_type(validations, attrs, doc, opts)
method: validations[:coerce_with],
message: validations[:coerce_message]
}
validate('coerce', coerce_options, attrs, doc, opts)
validate('coerce', coerce_options, attrs, required, opts)
validations.delete(:coerce_with)
validations.delete(:coerce)
validations.delete(:coerce_message)
Expand All @@ -465,11 +457,11 @@ def check_incompatible_option_values(default, values, except_values)
raise Grape::Exceptions::IncompatibleOptionValues.new(:default, default, :except, except_values)
end

def validate(type, options, attrs, doc, opts)
def validate(type, options, attrs, required, opts)
validator_options = {
attributes: attrs,
options: options,
required: doc.required,
required: required,
params_scope: self,
opts: opts,
validator_class: Validations.require_validator(type)
Expand Down Expand Up @@ -516,10 +508,10 @@ def derive_validator_options(validations)
}
end

def validates_presence(validations, attrs, doc, opts)
def validates_presence(validations, attrs, opts)
return unless validations.key?(:presence) && validations[:presence]

validate('presence', validations.delete(:presence), attrs, doc, opts)
validate('presence', validations.delete(:presence), attrs, true, opts)
validations.delete(:message) if validations.key?(:message)
end
end
Expand Down
156 changes: 0 additions & 156 deletions spec/grape/validations/attributes_doc_spec.rb

This file was deleted.

Loading