Class: Praxis::MultipartPart

Inherits:
Object
  • Object
show all
Includes:
Attributor::Type
Defined in:
lib/praxis/multipart/part.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(body, headers = {}, **args) ⇒ MultipartPart

Returns a new instance of MultipartPart.



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/praxis/multipart/part.rb', line 71

def initialize(body, headers = {}, **args)
  @name = args[:name]
  @body = body
  @headers = headers
  @default_handler = Praxis::Application.instance.handlers['json']

  self.content_type = 'text/plain' if content_type.nil?

  @filename = args[:filename]

  @payload_attribute = args[:payload_attribute]
  @headers_attribute = args[:headers_attribute]
  @filename_attribute = args[:filename_attribute]

  reset_content_disposition
end

Instance Attribute Details

#bodyObject Also known as: payload

Returns the value of attribute body.



8
9
10
# File 'lib/praxis/multipart/part.rb', line 8

def body
  @body
end

#default_handlerObject

Returns the value of attribute default_handler.



8
9
10
# File 'lib/praxis/multipart/part.rb', line 8

def default_handler
  @default_handler
end

#filenameObject

Returns the value of attribute filename.



7
8
9
# File 'lib/praxis/multipart/part.rb', line 7

def filename
  @filename
end

#filename_attributeObject

Returns the value of attribute filename_attribute.



8
9
10
# File 'lib/praxis/multipart/part.rb', line 8

def filename_attribute
  @filename_attribute
end

#headersObject

Returns the value of attribute headers.



8
9
10
# File 'lib/praxis/multipart/part.rb', line 8

def headers
  @headers
end

#headers_attributeObject

Returns the value of attribute headers_attribute.



8
9
10
# File 'lib/praxis/multipart/part.rb', line 8

def headers_attribute
  @headers_attribute
end

#nameObject

Returns the value of attribute name.



7
8
9
# File 'lib/praxis/multipart/part.rb', line 7

def name
  @name
end

#payload_attributeObject

Returns the value of attribute payload_attribute.



8
9
10
# File 'lib/praxis/multipart/part.rb', line 8

def payload_attribute
  @payload_attribute
end

Class Method Details

.check_option!(name, definition) ⇒ Object



10
11
12
13
14
15
16
17
18
19
# File 'lib/praxis/multipart/part.rb', line 10

def self.check_option!(name, definition)
  case name
  when :payload_attribute, :headers_attribute, :filename_attribute
    raise Attributor::AttributorException, "Value for option #{name.inspect} must be an Attribute. Got #{definition.class.name}" unless definition.nil? || definition.is_a?(Attributor::Attribute)
  else
    return :unknown
  end

  :ok
end

.describe(shallow = true, example: nil, options: {}) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/praxis/multipart/part.rb', line 52

def self.describe(shallow = true, example: nil, options: {})
  hash = super(shallow, example: example)

  if (payload_attribute = options[:payload_attribute])
    payload_example = example.payload if example
    hash[:payload] = payload_attribute.describe(shallow, example: payload_example)
  end
  if (headers_attribute = options[:headers_attribute])
    headers_example = example.headers if example
    hash[:headers] = headers_attribute.describe(shallow, example: headers_example)
  end
  if (filename_attribute = options[:filename_attribute])
    filename_example = example.filename if example
    hash[:filename] = filename_attribute.describe(shallow, example: filename_example)
  end

  hash
end

.dump_for_openapi(_example_part) ⇒ Object



258
259
260
261
# File 'lib/praxis/multipart/part.rb', line 258

def self.dump_for_openapi(_example_part)
  # TODO: This needs to be structured as OpenAPI requires it
  raise 'dumping a part for open api not implemented yet'
end

.example(context = nil, options: {}) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/praxis/multipart/part.rb', line 29

def self.example(context = nil, options: {})
  if (payload_attribute = options[:payload_attribute])
    payload = payload_attribute.example(context + ['payload'])
  end

  headers = if (headers_attribute = options[:headers_attribute])
              headers_attribute.example(context + ['headers'])
            else
              {}
            end

  name = options[:name]

  filename = if (filename_attribute = options[:filename_attribute])
               filename_attribute.example(context + ['filename'])
             end

  new(payload, headers, name: name, filename: filename,
                        payload_attribute: payload_attribute,
                        headers_attribute: headers_attribute,
                        filename_attribute: filename_attribute)
end

.json_schema_typeObject



25
26
27
# File 'lib/praxis/multipart/part.rb', line 25

def self.json_schema_type
  :object
end

.native_typeObject



21
22
23
# File 'lib/praxis/multipart/part.rb', line 21

def self.native_type
  self
end

Instance Method Details

#attribute=(attribute) ⇒ Object

Raises:

  • (ArgumentError)


91
92
93
94
95
96
97
98
99
# File 'lib/praxis/multipart/part.rb', line 91

def attribute=(attribute)
  raise ArgumentError, "invalid attribute type #{attribute.type}" unless is_a?(attribute.type)

  @payload_attribute = attribute.options[:payload_attribute] if attribute.options.key? :payload_attribute

  @headers_attribute = attribute.options[:headers_attribute] if attribute.options.key? :headers_attribute

  @filename_attribute = attribute.options[:filename_attribute] if attribute.options.key? :filename_attribute
end

#content_typeMediaTypeIdentifier

Determine the content type of this response.

Returns:



119
120
121
# File 'lib/praxis/multipart/part.rb', line 119

def content_type
  @content_type ||= MediaTypeIdentifier.load(headers['Content-Type']).freeze
end

#content_type=(identifier) ⇒ String

TODO:

DRY this out (also used in Response)

Set the content type for this response.

Parameters:

Returns:

  • (String)


128
129
130
131
# File 'lib/praxis/multipart/part.rb', line 128

def content_type=(identifier)
  @content_type = nil
  headers['Content-Type'] = MediaTypeIdentifier.load(identifier).to_s
end

#derive_content_type(handler_name) ⇒ Object

Determine an appropriate default content_type for this part given the preferred handler_name, if possible.

Considers any pre-defined set of values on the content_type attributge of the headers.



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/praxis/multipart/part.rb', line 202

def derive_content_type(handler_name)
  possible_values = if content_type.match 'text/plain'
                      _, content_type_attribute = headers_attribute&.attributes&.find { |k, _v| k.to_s =~ /^content[-_]{1}type$/i }
                      if content_type_attribute&.options&.key?(:values)
                        content_type_attribute.options[:values]
                      else
                        []
                      end
                    else
                      [content_type]
                    end

  # generic default encoding is the best we can do
  return MediaTypeIdentifier.load("application/#{handler_name}") if possible_values.empty?

  # if any defined value match the preferred handler_name, return it
  possible_values.each do |ct|
    mti = MediaTypeIdentifier.load(ct)
    return mti if mti.handler_name == handler_name
  end

  # otherwise, pick the first
  pick = MediaTypeIdentifier.load(possible_values.first)

  # and return that one if it already corresponds to a registered handler
  # otherwise, add the encoding
  if Praxis::Application.instance.handlers.include?(pick.handler_name)
    pick
  else
    pick + handler_name
  end
end

#dump(default_format: nil, **opts) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/praxis/multipart/part.rb', line 235

def dump(default_format: nil, **opts)
  original_content_type = content_type

  body = payload_attribute.dump(payload, **opts)

  body_string = case body
                when Hash, Array
                  self.content_type = derive_content_type(default_format) if default_format

                  handler.generate(body)
                else
                  body
                end

  header_string = headers.collect do |name, value|
    "#{name}: #{value}"
  end.join("\r\n")

  "#{header_string}\r\n\r\n#{body_string}"
ensure
  self.content_type = original_content_type
end

#encode!Object



137
138
139
140
141
142
143
144
145
# File 'lib/praxis/multipart/part.rb', line 137

def encode!
  case @body
  when Hash, Array
    # response payload is structured data; transform it into an entity using the handler
    # implied by the response's media type. If no handler is registered for this
    # name, assume JSON as a default handler.
    @body = JSON.pretty_generate(@body)
  end
end

#handlerObject



192
193
194
195
# File 'lib/praxis/multipart/part.rb', line 192

def handler
  handlers = Praxis::Application.instance.handlers
  (content_type && handlers[content_type.handler_name]) || @default_handler
end

#load_headers(_context = Attributor::DEFAULT_ROOT_CONTEXT) ⇒ Object



112
113
114
# File 'lib/praxis/multipart/part.rb', line 112

def load_headers(_context = Attributor::DEFAULT_ROOT_CONTEXT)
  self.headers = headers_attribute.load(headers) if headers_attribute
end

#load_payload(_context = Attributor::DEFAULT_ROOT_CONTEXT) ⇒ Object



101
102
103
104
105
106
107
108
109
110
# File 'lib/praxis/multipart/part.rb', line 101

def load_payload(_context = Attributor::DEFAULT_ROOT_CONTEXT)
  return unless payload_attribute

  value = if payload.is_a?(String)
            handler.parse(payload)
          else
            payload
          end
  self.payload = payload_attribute.load(value)
end

#reset_content_dispositionObject



171
172
173
174
175
176
177
178
# File 'lib/praxis/multipart/part.rb', line 171

def reset_content_disposition
  headers['Content-Disposition'] = begin
    disposition = "form-data; name=#{name}"
    disposition += "; filename=#{filename}" if filename

    disposition
  end
end

#statusObject



133
134
135
# File 'lib/praxis/multipart/part.rb', line 133

def status
  @headers['Status'].to_i
end

#validate(context = Attributor::DEFAULT_ROOT_CONTEXT) ⇒ Object



165
166
167
168
169
# File 'lib/praxis/multipart/part.rb', line 165

def validate(context = Attributor::DEFAULT_ROOT_CONTEXT)
  errors = validate_headers(context)
  errors.push(*validate_payload(context))
  errors.push(*validate_filename(context))
end

#validate_filename(context = Attributor::DEFAULT_ROOT_CONTEXT) ⇒ Object



159
160
161
162
163
# File 'lib/praxis/multipart/part.rb', line 159

def validate_filename(context = Attributor::DEFAULT_ROOT_CONTEXT)
  return [] unless filename_attribute

  filename_attribute.validate(filename, context + ['filename'])
end

#validate_headers(context = Attributor::DEFAULT_ROOT_CONTEXT) ⇒ Object



147
148
149
150
151
# File 'lib/praxis/multipart/part.rb', line 147

def validate_headers(context = Attributor::DEFAULT_ROOT_CONTEXT)
  return [] unless headers_attribute

  headers_attribute.validate(headers, context + ['headers'])
end

#validate_payload(context = Attributor::DEFAULT_ROOT_CONTEXT) ⇒ Object



153
154
155
156
157
# File 'lib/praxis/multipart/part.rb', line 153

def validate_payload(context = Attributor::DEFAULT_ROOT_CONTEXT)
  return [] unless payload_attribute

  payload_attribute.validate(payload, context + ['payload'])
end