Class: Protocol::Content::Parameters::Model

Inherits:
Object
  • Object
show all
Defined in:
lib/protocol/content/parameters/model.rb

Overview

An immutable model for parsing, filtering, and validating parameters.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parser, fields, strict: true) ⇒ Model

Initialize a parameter model.



18
19
20
21
22
# File 'lib/protocol/content/parameters/model.rb', line 18

def initialize(parser, fields, strict: true)
	@parser = parser
	@fields = fields
	@strict = strict
end

Instance Attribute Details

#fieldsObject (readonly)

The fields in this model, indexed by name.



25
26
27
# File 'lib/protocol/content/parameters/model.rb', line 25

def fields
  @fields
end

Instance Method Details

#accepts_upload?(path) ⇒ Boolean

Whether an upload path is explicitly accepted by this model.

Returns:

  • (Boolean)


119
120
121
# File 'lib/protocol/content/parameters/model.rb', line 119

def accepts_upload?(path)
	return !!upload_field(path)
end

#apply(value, errors, path = []) ⇒ Object

Apply this model to an existing argument hierarchy.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/protocol/content/parameters/model.rb', line 76

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

#freezeObject

Freeze this model and its fields.



139
140
141
142
143
144
145
146
# File 'lib/protocol/content/parameters/model.rb', line 139

def freeze
	return self if self.frozen?
	
	@parser.freeze
	@fields.each_value(&:freeze)
	@fields.freeze
	super
end

#parse(media_type, input, &upload_handler) ⇒ Object

Parse, filter, and validate content parameters.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/protocol/content/parameters/model.rb', line 32

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

#parse!(media_type, input, &block) ⇒ Object

Parse content parameters, raising when validation fails.

Raises:



61
62
63
64
65
66
67
68
69
# File 'lib/protocol/content/parameters/model.rb', line 61

def parse!(media_type, input, &block)
	result = parse(media_type, input, &block)
	
	if result.valid?
		return result.value
	end
	
	raise ValidationError, result
end

#upload_field(path) ⇒ Object

Find the upload field which accepts the given decoded path.



126
127
128
129
130
131
132
133
134
135
# File 'lib/protocol/content/parameters/model.rb', line 126

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