New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring RequestValidation and ResponseValidation #149

Merged
merged 9 commits into from Nov 13, 2018
Copy path View file
@@ -6,10 +6,8 @@
require_relative "committee/string_params_coercer"
require_relative "committee/parameter_coercer"
require_relative "committee/request_unpacker"
require_relative "committee/request_validator"
require_relative "committee/response_generator"
require_relative "committee/response_validator"
require_relative "committee/router"
require_relative "committee/validation_error"
require_relative "committee/drivers"
@@ -21,6 +19,12 @@
require_relative "committee/middleware/response_validation"
require_relative "committee/middleware/stub"
require_relative "committee/schema_validator/option"
require_relative "committee/schema_validator/hyper_schema"
require_relative "committee/schema_validator/hyper_schema/request_validator"
require_relative "committee/schema_validator/hyper_schema/response_validator"
require_relative "committee/schema_validator/hyper_schema/router"
require_relative "committee/bin/committee_stub"
require_relative "committee/test/methods"
Copy path View file
@@ -57,6 +57,11 @@ class Schema
def driver
raise "needs implementation"
end
def build_router(options)
validator_option = Committee::SchemaValidator::Option.new(options, self)
Committee::SchemaValidator::HyperSchema::Router.new(self, validator_option)
end
end
end
end
end
@@ -4,15 +4,11 @@ def initialize(app, options={})
@app = app
@error_class = options.fetch(:error_class, Committee::ValidationError)
@params_key = options[:params_key] || "committee.params"
@headers_key = options[:headers_key] || "committee.headers"
@raise = options[:raise]
@schema = get_schema(options[:schema] ||
raise(ArgumentError, "Committee: need option `schema`"))
@schema = get_schema(options)
@router = Committee::Router.new(@schema,
prefix: options[:prefix]
)
@router = @schema.build_router(options)
end
def call(env)
@@ -27,54 +23,25 @@ def call(env)
private
# For modern use of the library a schema should be an instance of
# Committee::Drivers::Schema so that we know that all the computationally
# difficult parsing is already done by the time we try to handle any
# request.
#
# However, for reasons of backwards compatibility we also allow schema
# input to be a string, a data hash, or a JsonSchema::Schema. In the former
# two cases we just parse as if we were sent hyper-schema. In the latter,
# we have the hyper-schema driver wrap it in a new Committee object.
def get_schema(schema)
# These are in a separately conditional ladder so that we only show the
# user one warning.
if schema.is_a?(String)
warn_string_deprecated
elsif schema.is_a?(Hash)
warn_hash_deprecated
end
def get_schema(options)
schema = options[:schema]
if schema.is_a?(String)
schema = JSON.parse(schema)
end
if schema
if schema.is_a?(Hash) || schema.is_a?(JsonSchema::Schema)
driver = Committee::Drivers::HyperSchema.new
# Expect the type we want by now. If we don't have it, the user passed
# something else non-standard in.
if !schema.is_a?(Committee::Drivers::Schema)
raise ArgumentError, "Committee: schema expected to be an instance of Committee::Drivers::Schema."
end
# The driver itself has its own special cases to be able to parse
# either a hash or JsonSchema::Schema object.
schema = driver.parse(schema)
return schema
end
# Expect the type we want by now. If we don't have it, the user passed
# something else non-standard in.
if !schema.is_a?(Committee::Drivers::Schema)
raise ArgumentError, "Committee: schema expected to be a hash or " \
"an instance of Committee::Drivers::Schema."
end
schema
end
def warn_hash_deprecated
Committee.warn_deprecated("Committee: passing a hash to schema " \
"option is deprecated; please send a driver object instead.")
raise(ArgumentError, "Committee: need option `schema`")
end
def warn_string_deprecated
Committee.warn_deprecated("Committee: passing a string to schema " \
"option is deprecated; please send a driver object instead.")
def build_schema_validator(request)
@router.build_schema_validator(request)
end
end
end
@@ -3,67 +3,19 @@ class RequestValidation < Base
def initialize(app, options={})
super
@allow_form_params = options.fetch(:allow_form_params, true)
@allow_query_params = options.fetch(:allow_query_params, true)
@check_content_type = options.fetch(:check_content_type, true)
@check_header = options.fetch(:check_header, true)
@optimistic_json = options.fetch(:optimistic_json, false)
@strict = options[:strict]
@coerce_date_times = options.fetch(:coerce_date_times, false)
@coerce_recursive = options.fetch(:coerce_recursive, true)
@coerce_form_params = options.fetch(:coerce_form_params,
@schema.driver.default_coerce_form_params)
@coerce_path_params = options.fetch(:coerce_path_params,
@schema.driver.default_path_params)
@coerce_query_params = options.fetch(:coerce_query_params,
@schema.driver.default_query_params)
@strict = options[:strict]
# deprecated
@allow_extra = options[:allow_extra]
end
def handle(request)
link, param_matches = @router.find_request_link(request)
if link
# Attempts to coerce parameters that appear in a link's URL to Ruby
# types that can be validated with a schema.
if @coerce_path_params
Committee::StringParamsCoercer.new(param_matches, link.schema, coerce_recursive: @coerce_recursive).call!
end
# Attempts to coerce parameters that appear in a query string to Ruby
# types that can be validated with a schema.
if @coerce_query_params && !request.GET.nil? && !link.schema.nil?
Committee::StringParamsCoercer.new(request.GET, link.schema, coerce_recursive: @coerce_recursive).call!
end
end
request.env[@params_key], request.env[@headers_key] = Committee::RequestUnpacker.new(
request,
allow_form_params: @allow_form_params,
allow_query_params: @allow_query_params,
coerce_form_params: @coerce_form_params,
optimistic_json: @optimistic_json,
schema: link ? link.schema : nil
).call
schema_validator = build_schema_validator(request)
schema_validator.request_validate(request)
request.env[@params_key].merge!(param_matches) if param_matches
raise Committee::NotFound, "That request method and path combination isn't defined." if !schema_validator.link_exist? && @strict
if link
validator = Committee::RequestValidator.new(link, check_content_type: @check_content_type, check_header: @check_header)
validator.call(request, request.env[@params_key], request.env[@headers_key])
parameter_coerce!(request, link, @params_key)
parameter_coerce!(request, link, "rack.request.query_hash") if !request.GET.nil? && !link.schema.nil?
@app.call(request.env)
elsif @strict
raise Committee::NotFound, "That request method and path combination isn't defined."
else
@app.call(request.env)
end
@app.call(request.env)
rescue Committee::BadRequest, Committee::InvalidRequest
raise if @raise
@error_class.new(400, :bad_request, $!.message).render
@@ -78,13 +30,5 @@ def handle(request)
raise Committee::InvalidRequest if @raise
@error_class.new(400, :bad_request, "Request body wasn't valid JSON.").render
end
private
def parameter_coerce!(request, link, coerce_key)
Committee::ParameterCoercer.
new(request.env[coerce_key], link.schema, coerce_date_times: @coerce_date_times, coerce_recursive: @coerce_recursive).
call!
end
end
end
@@ -10,15 +10,7 @@ def initialize(app, options = {})
def handle(request)
status, headers, response = @app.call(request.env)
link, _ = @router.find_request_link(request)
if validate?(status) && link
full_body = ""
response.each do |chunk|
full_body << chunk
end
data = JSON.parse(full_body)
Committee::ResponseValidator.new(link, validate_errors: validate_errors).call(status, headers, data)
end
build_schema_validator(request).response_validate(status, headers, response) if validate?(status)
[status, headers, response]
rescue Committee::InvalidResponse
@@ -30,7 +22,7 @@ def handle(request)
end
def validate?(status)
Committee::ResponseValidator.validate?(status, validate_errors: validate_errors)
status != 204 and validate_errors || (200...300).include?(status)
end
end
end
@@ -0,0 +1,87 @@
class Committee::SchemaValidator
class HyperSchema
attr_reader :link, :param_matches, :validator_option
def initialize(router, request, validator_option)
@link, @param_matches = router.find_request_link(request)
@validator_option = validator_option
end
def request_validate(request)
# Attempts to coerce parameters that appear in a link's URL to Ruby
# types that can be validated with a schema.
param_matches_hash = validator_option.coerce_path_params ? coerce_path_params : {}
# Attempts to coerce parameters that appear in a query string to Ruby
# types that can be validated with a schema.
coerce_query_params(request) if validator_option.coerce_query_params
request_unpack(request)
request.env[validator_option.params_key].merge!(param_matches_hash) if param_matches_hash
request_schema_validation(request)
parameter_coerce!(request, link, validator_option.params_key)
parameter_coerce!(request, link, "rack.request.query_hash") if link_exist? && !request.GET.nil? && !link.schema.nil?
end
def response_validate(status, headers, response)
return unless link_exist?
full_body = ""
response.each do |chunk|
full_body << chunk
end
data = JSON.parse(full_body)
Committee::SchemaValidator::HyperSchema::ResponseValidator.new(link, validate_errors: validator_option.validate_errors).call(status, headers, data)
end
def link_exist?
!link.nil?
end
private
def coerce_path_params
return unless link_exist?
Committee::StringParamsCoercer.new(param_matches, link.schema, coerce_recursive: validator_option.coerce_recursive).call!
param_matches
end
def coerce_query_params(request)
return unless link_exist?
return if request.GET.nil? || link.schema.nil?
Committee::StringParamsCoercer.new(request.GET, link.schema, coerce_recursive: validator_option.coerce_recursive).call!
end
def request_unpack(request)
request.env[validator_option.params_key], request.env[validator_option.headers_key] = Committee::RequestUnpacker.new(
request,
allow_form_params: validator_option.allow_form_params,
allow_query_params: validator_option.allow_query_params,
coerce_form_params: validator_option.coerce_form_params,
optimistic_json: validator_option.optimistic_json,
schema: link ? link.schema : nil
).call
end
def request_schema_validation(request)
return unless link_exist?
validator = Committee::SchemaValidator::HyperSchema::RequestValidator.new(link, check_content_type: validator_option.check_content_type, check_header: validator_option.check_header)
validator.call(request, request.env[validator_option.params_key], request.env[validator_option.headers_key])
end
def parameter_coerce!(request, link, coerce_key)
return unless link_exist?
Committee::ParameterCoercer.
new(request.env[coerce_key],
link.schema,
coerce_date_times: validator_option.coerce_date_times,
coerce_recursive: validator_option.coerce_recursive).
call!
end
end
end
@@ -1,5 +1,5 @@
module Committee
class RequestValidator
class SchemaValidator::HyperSchema::RequestValidator
def initialize(link, options = {})
@link = link
@check_content_type = options.fetch(:check_content_type, true)
@@ -1,5 +1,5 @@
module Committee
class ResponseValidator
class SchemaValidator::HyperSchema::ResponseValidator
attr_reader :validate_errors
def initialize(link, options = {})
@@ -1,9 +1,11 @@
module Committee
class Router
def initialize(schema, options = {})
@prefix = options[:prefix]
class SchemaValidator::HyperSchema::Router
def initialize(schema, validator_option)
@prefix = validator_option.prefix
@prefix_regexp = /\A#{Regexp.escape(@prefix)}/.freeze if @prefix
@schema = schema
@validator_option = validator_option
end
def includes?(path)
@@ -30,5 +32,9 @@ def find_link(method, path)
def find_request_link(request)
find_link(request.request_method, request.path_info)
end
def build_schema_validator(request)
Committee::SchemaValidator::HyperSchema.new(self, request, @validator_option)
end
end
end
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.