class Model
An immutable model for parsing, filtering, and validating parameters.
Definitions
def initialize(parser, fields, strict: true)
Initialize a parameter model.
Signature
-
parameter
parserParser The content parser.
-
parameter
fieldsHash The parameter fields.
-
parameter
strictBoolean Whether unknown fields should produce validation errors.
Implementation
def initialize(parser, fields, strict: true)
@parser = parser
@fields = fields
@strict = strict
end
attr :fields
The fields in this model, indexed by name.
def parse(media_type, input, &upload_handler)
Parse, filter, and validate content parameters.
Signature
-
parameter
media_typeString | Protocol::Media::Type | Nil The content media type.
-
parameter
inputObject The readable content input.
-
yields
{|name, upload| ...} Each streaming upload. Its return value is inserted into the parsed value.
-
returns
Result The parsed value and validation errors.
Implementation
def parse(media_type, input, &upload_handler)
# Replace ephemeral multipart uploads with outcomes which can survive until validation:
value = @parser.parse(media_type, input) do |name, item|
if item.is_a?(Protocol::Multipart::FormData::Upload)
path = Protocol::URL::Encoding.split(name)
# Only process uploads accepted by an explicit field:
if upload_handler && field = upload_field(path)
field.process(name, item, &upload_handler)
else
Value::OMITTED
end
else
item
end
end
# Apply the model after parsing so ordinary values and upload outcomes follow the same hierarchy:
errors = []
value = apply(value, errors)
return Result.new(value, errors)
end
def parse!(media_type, input, &block)
Parse content parameters, raising when validation fails.
Signature
-
parameter
media_typeString | Protocol::Media::Type | Nil The content media type.
-
parameter
inputObject The readable content input.
-
yields
{|name, upload| ...} Each streaming upload. Its return value is inserted into the parsed value.
-
returns
Hash The valid value.
-
raises
ValidationError If validation fails.
Implementation
def parse!(media_type, input, &block)
result = parse(media_type, input, &block)
if result.valid?
return result.value
end
raise ValidationError, result
end
def apply(value, errors, path = [])
Apply this model to an existing argument hierarchy.
Signature
-
parameter
valueObject The parameter hierarchy.
-
parameter
errorsArray(Error) The validation error destination.
-
parameter
pathArray(String | Integer) The current argument path.
-
returns
Hash The filtered and converted value.
Implementation
def apply(value, errors, path = [])
# Parameter models always apply to a key/value hierarchy:
unless value.is_a?(Hash)
errors << Error.new(path, :invalid_type, expected: Hash, value: value)
return {}
end
# Copy the input so declared fields can be removed without modifying caller-owned data:
input = value.dup
output = {}
# Apply declared values and collect missing required parameters:
@fields.each do |name, field|
item_path = path + [name]
if input.key?(name)
item = input.delete(name)
if item.equal?(Value::OMITTED)
if field.required?
errors << Error.new(item_path, :required)
end
else
field.apply(item, output, errors, item_path)
end
elsif field.required?
errors << Error.new(item_path, :required)
end
end
# Reject remaining undeclared values when strict validation is enabled:
if @strict
input.each_key do |name|
errors << Error.new(path + [name], :unknown)
end
end
return output
end
def accepts_upload?(path)
Whether an upload path is explicitly accepted by this model.
Signature
-
parameter
pathArray(String) The decoded upload path.
-
returns
Boolean Whether the upload is accepted.
Implementation
def accepts_upload?(path)
return !!upload_field(path)
end
def upload_field(path)
Find the upload field which accepts the given decoded path.
Signature
-
parameter
pathArray(String) The decoded upload path.
-
returns
UploadField | Nil The accepting upload field.
Implementation
def upload_field(path)
# Walk fields using the decoded components of the form name:
name, *remaining = path
unless field = @fields[name]
return nil
end
return field.upload_field(remaining)
end
def freeze
Freeze this model and its fields.
Signature
-
returns
self The frozen model.
Implementation
def freeze
return self if self.frozen?
@parser.freeze
@fields.each_value(&:freeze)
@fields.freeze
super
end