Skip to content
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

Add option to redact data from exceptions #168

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,25 @@ options = {
OpenAPIParser.parse(yaml_file, options)
```

### Response error redaction
Errors generated during response validation often contain sensitive customer information which you may not
want to appear in logs and exception reporting. You can configure the parser to redact data values from exceptions by
passing the `redact_response_errors: true` option.

```ruby
normal_schema = YAML.safe_load_file('spec/data/normal.yml', permitted_classes: [Date, Time])
root = OpenAPIParser.parse(normal_schema, redact_response_errors: true)
op = root.request_operation(:get, '/characters')
op.validate_response_body(
OpenAPIParser::RequestOperation::ValidatableResponseBody.new(
200,
{'string_1' => 12},
{ 'Content-Type' => 'application/json' })
)

# Will raise with OpenAPIParser::ValidateError: #/paths/~1characters/get/responses/200/content/application~1json/schema/properties/string_1 expected string, but received Integer: <redacted>
```

## ToDo
- correct schema checker
- more detailed validator
Expand Down
11 changes: 9 additions & 2 deletions lib/openapi_parser/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ def validate_header
@config.fetch(:validate_header, true)
end

def redact_response_errors
@config.fetch(:redact_response_errors, false)
end

# @return [OpenAPIParser::SchemaValidator::Options]
def request_validator_options
@request_validator_options ||= OpenAPIParser::SchemaValidator::Options.new(coerce_value: coerce_value,
Expand All @@ -49,7 +53,10 @@ def request_validator_options

# @return [OpenAPIParser::SchemaValidator::ResponseValidateOptions]
def response_validate_options
@response_validate_options ||= OpenAPIParser::SchemaValidator::ResponseValidateOptions.
new(strict: strict_response_validation, validate_header: validate_header)
@response_validate_options ||= OpenAPIParser::SchemaValidator::ResponseValidateOptions.new(
strict: strict_response_validation,
validate_header: validate_header,
redact_errors: redact_response_errors
)
end
end
187 changes: 90 additions & 97 deletions lib/openapi_parser/errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,40 @@ def initialize(reference)
end
end

class ValueError < OpenAPIError
def initialize(value, reference, options:)
super(reference)
@options = options
@value = value
end

def redacted_value
@options.redact_errors ? '<redacted>' : @value.inspect
end
end

class MissingReferenceError < OpenAPIError
def message
"'#{@reference}' was referenced but could not be found"
end
end

class ValidateError < OpenAPIError
def initialize(data, type, reference)
super(reference)
@data = data
class ValidateError < ValueError
def initialize(value, type, reference, options:)
super(value, reference, options: options)
@type = type
end

def message
"#{@reference} expected #{@type}, but received #{@data.class}: #{@data.inspect}"
"#{@reference} expected #{@type}, but received #{@value.class}: #{redacted_value}"
end

class << self
# create ValidateError for SchemaValidator return data
# @param [Object] value
# @param [OpenAPIParser::Schemas::Base] schema
def build_error_result(value, schema)
[nil, OpenAPIParser::ValidateError.new(value, schema.type, schema.object_reference)]
def build_error_result(value, schema, options:)
[nil, OpenAPIParser::ValidateError.new(value, schema.type, schema.object_reference, options: options)]
end
end
end
Expand Down Expand Up @@ -71,149 +82,136 @@ def message
end
end

class NotExistDiscriminatorPropertyName < OpenAPIError
def initialize(key, value, reference)
super(reference)
class NotExistDiscriminatorPropertyName < ValueError
def initialize(key, value, reference, options:)
super(value, reference, options: options)
@key = key
@value = value
end

def message
"discriminator propertyName #{@key} does not exist in value #{@value.inspect} in #{@reference}"
"discriminator propertyName #{@key} does not exist in value #{redacted_value} in #{@reference}"
end
end

class NotOneOf < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class NotOneOf < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@value.inspect} isn't one of in #{@reference}"
"#{redacted_value} isn't one of in #{@reference}"
end
end

class NotAnyOf < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class NotAnyOf < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@value.inspect} isn't any of in #{@reference}"
"#{redacted_value} isn't any of in #{@reference}"
end
end

class NotEnumInclude < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class NotEnumInclude < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@value.inspect} isn't part of the enum in #{@reference}"
"#{redacted_value} isn't part of the enum in #{@reference}"
end
end

class LessThanMinimum < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class LessThanMinimum < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} #{@value.inspect} is less than minimum value"
"#{@reference} #{redacted_value} is less than minimum value"
end
end

class LessThanExclusiveMinimum < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class LessThanExclusiveMinimum < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} #{@value.inspect} cannot be less than or equal to exclusive minimum value"
"#{@reference} #{redacted_value} cannot be less than or equal to exclusive minimum value"
end
end

class MoreThanMaximum < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class MoreThanMaximum < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} #{@value.inspect} is more than maximum value"
"#{@reference} #{redacted_value} is more than maximum value"
end
end

class MoreThanExclusiveMaximum < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class MoreThanExclusiveMaximum < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} #{@value.inspect} cannot be more than or equal to exclusive maximum value"
"#{@reference} #{redacted_value} cannot be more than or equal to exclusive maximum value"
end
end

class InvalidPattern < OpenAPIError
def initialize(value, pattern, reference, example)
super(reference)
@value = value
class InvalidPattern < ValueError
def initialize(value, pattern, reference, example, options:)
super(value, reference, options: options)
@pattern = pattern
@example = example
end

def message
"#{@reference} pattern #{@pattern} does not match value: #{@value.inspect}#{@example ? ", example: #{@example}" : ""}"
"#{@reference} pattern #{@pattern} does not match value: #{redacted_value}#{@example ? ", example: #{@example}" : ""}"
end
end

class InvalidEmailFormat < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class InvalidEmailFormat < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} email address format does not match value: #{@value.inspect}"
"#{@reference} email address format does not match value: #{redacted_value}"
end
end

class InvalidUUIDFormat < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class InvalidUUIDFormat < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} Value: #{@value.inspect} is not conformant with UUID format"
"#{@reference} Value: #{redacted_value} is not conformant with UUID format"
end
end

class InvalidDateFormat < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class InvalidDateFormat < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} Value: #{@value.inspect} is not conformant with date format"
"#{@reference} Value: #{redacted_value} is not conformant with date format"
end
end

class InvalidDateTimeFormat < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class InvalidDateTimeFormat < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} Value: #{@value.inspect} is not conformant with date-time format"
"#{@reference} Value: #{redacted_value} is not conformant with date-time format"
end
end

Expand All @@ -229,58 +227,53 @@ def message
end
end

class MoreThanMaxLength < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class MoreThanMaxLength < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} #{@value.inspect} is longer than max length"
"#{@reference} #{redacted_value} is longer than max length"
end
end

class LessThanMinLength < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class LessThanMinLength < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} #{@value.inspect} is shorter than min length"
"#{@reference} #{redacted_value} is shorter than min length"
end
end

class MoreThanMaxItems < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class MoreThanMaxItems < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} #{@value.inspect} contains more than max items"
"#{@reference} #{redacted_value} contains more than max items"
end
end

class LessThanMinItems < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class LessThanMinItems < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} #{@value.inspect} contains fewer than min items"
"#{@reference} #{redacted_value} contains fewer than min items"
end
end

class NotUniqueItems < OpenAPIError
def initialize(value, reference)
super(reference)
@value = value
class NotUniqueItems < ValueError
def initialize(value, reference, options:)
super(value, reference, options: options)
end

def message
"#{@reference} #{@value.inspect} contains duplicate items"
"#{@reference} #{redacted_value} contains duplicate items"
end
end
end
Loading