class Praxis::Types::MultipartArray

Attributes

attributes[R]
identifier[R]
multiple[R]
options[R]
content_type[R]
preamble[RW]

Public Class Methods

construct(constructor_block, _options={}) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 44
def self.construct(constructor_block, _options={})
  Class.new(self, &constructor_block)
end
constructable?() click to toggle source
# File lib/praxis/types/multipart_array.rb, line 40
def self.constructable?
  true
end
describe(shallow=true, example: nil) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 157
def self.describe(shallow=true, example: nil)
  type_name = Attributor.type_name(self)
  hash = {
    name: type_name.gsub(Attributor::MODULE_PREFIX_REGEX, ''),
    family: self.family,
    id: self.id
  }
  hash[:example] = example if example

  hash[:part_name] = {type: name_type.describe(true)}

  unless shallow
    hash[:attributes] = {} if self.attributes.keys.any? { |name| name.kind_of? String}
    hash[:pattern_attributes] = {} if self.attributes.keys.any? { |name| name.kind_of? Regexp}

    if hash.key?(:attributes) || hash.key?(:pattern_attributes)
      self.describe_attributes(shallow, example: example).each do |name, sub_hash|
        case name
        when String
          hash[:attributes][name] = sub_hash
        when Regexp
          hash[:pattern_attributes][name.source] = sub_hash
        end
      end
    else
      hash[:part_payload] = {type: payload_type.describe(true)}
    end
  end
  hash
end
describe_attributes(shallow=true, example: nil) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 188
def self.describe_attributes(shallow=true, example: nil)
  self.attributes.each_with_object({}) do |(part_name, part_attribute), parts|
    sub_example = example.part(part_name) if example
    if sub_example && self.multiple.include?(part_name)
      sub_example = sub_example.first
    end

    sub_hash = part_attribute.describe(shallow, example: sub_example)


    if (options = sub_hash.delete(:options))
      sub_hash[:options] = {}
      if self.multiple.include?(part_name)
        sub_hash[:options][:multiple] = true
      end

      if (payload_attribute = options.delete :payload_attribute)
        if (required = payload_attribute.options[:required])
          sub_hash[:options][:required] = true
        end
      end
    end

    sub_hash[:type] = MultipartPart.describe(shallow, example: sub_example, options: part_attribute.options)


    parts[part_name] = sub_hash
  end
end
dump(value, **opts) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 310
def self.dump(value, **opts)
  value.dump(**opts)
end
example(context=Attributor::DEFAULT_ROOT_CONTEXT, **options) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 135
def self.example(context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
  example = self.new

  self.attributes.each do |name, attribute|
    next if name.kind_of? Regexp
    sub_context = self.generate_subcontext(context, name)

    part = attribute.example(sub_context)
    part.name = name
    example.push part

    if self.multiple.include? name
      part = attribute.example(sub_context + ['2'])
      part.name = name
      example.push part
    end
  end

  example
end
file(name, payload_type=nil, filename: nil, **opts, &block) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 112
def self.file(name, payload_type=nil, filename: nil, **opts, &block)
  self.part(name, payload_type, filename: true, **opts, &block)
end
inherited(klass) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 14
def self.inherited(klass)
  klass.instance_eval do
    @attributes = FuzzyHash.new
    @saved_blocks = []
    @multiple = []
    @options = {}

    @payload_type = Attributor::String
    @name_type = Attributor::String
    @member_type = Praxis::MultipartPart

    @payload_attribute = nil
    @part_attribute = nil

    @identifier = MediaTypeIdentifier.load('multipart/form-data').freeze

  end
end
load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, content_type:nil) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 116
def self.load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, content_type:nil)
  return value if value.kind_of?(self) || value.nil?

  if value.kind_of?(::String) && content_type.nil?
    raise ArgumentError, "content_type is required to load values of type String for #{Attributor.type_name(self)}"
  end

  parser = Praxis::MultipartParser.new({'Content-Type' => content_type}, value)
  preamble, parts = parser.parse

  instance = self.new
  instance.push(*parts)

  instance.preamble = preamble
  instance.content_type = content_type

  instance
end
name_type(type=nil) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 48
def self.name_type(type=nil)
  return @name_type if type.nil?

  @name_type = Attributor.resolve_type type
end
new(content_type: self.class.identifier) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 221
def initialize(content_type: self.class.identifier)
  self.content_type = content_type
end
part(name, payload_type=nil, multiple: false, filename: false, **opts, &block) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 76
def self.part(name, payload_type=nil, multiple: false, filename: false, **opts, &block)
  @attributes.default_proc = nil

  if name.kind_of?(Regexp)
    raise 'part with regexp name may not take :multiple option' if multiple
    raise 'part with regexp name may not be required' if opts[:required] == true
  end

  self.multiple << name if multiple

  compiler = Attributor::DSLCompiler.new(self, opts)

  if filename
    filename_attribute = compiler.define('filename', String, required: true)
  end

  if block_given?
    definition = PartDefinition.new(&block)
    payload_attribute = definition.payload_attribute
    header_attribute = definition.headers_attribute
    filename_attribute = definition.filename_attribute || filename_attribute

    self.attributes[name] = compiler.define(name, Praxis::MultipartPart,
                                            payload_attribute: payload_attribute,
                                            headers_attribute: header_attribute,
                                            filename_attribute: filename_attribute
                                            )
  else
    payload_attribute = compiler.define(name, payload_type || self.payload_type, **opts)
    self.attributes[name] = compiler.define(name, Praxis::MultipartPart,
                                            payload_attribute: payload_attribute,
                                            filename_attribute: filename_attribute
                                            )
  end
end
part_attribute() click to toggle source
# File lib/praxis/types/multipart_array.rb, line 72
def self.part_attribute
  @part_attribute ||= Attributor::Attribute.new(Praxis::MultipartPart, payload_attribute: self.payload_attribute)
end
payload_attribute() click to toggle source
# File lib/praxis/types/multipart_array.rb, line 68
def self.payload_attribute
  @payload_attribute ||= Attributor::Attribute.new(@payload_type)
end
payload_type(type=nil, **opts, &block) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 54
def self.payload_type(type=nil, **opts, &block)
  if type.nil?
    if block_given?
      type = Attributor::Struct
    else
      return @payload_type
    end
  end
  @payload_type = Attributor.resolve_type(type)
  @payload_attribute = Attributor::Attribute.new(@payload_type, **opts, &block)
  @part_attribute = nil
  @payload_type
end

Public Instance Methods

content_type=(content_type) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 225
def content_type=(content_type)
  @content_type = MediaTypeIdentifier.load(content_type)
  if @content_type.parameters.get('boundary').nil?
    @content_type.parameters.set 'boundary', 'Boundary_puppies'
  end
  @content_type
end
dump(**opts) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 314
def dump(**opts)
  boundary = content_type.parameters.get 'boundary'

  parts = self.collect do |part|
    part.dump(**opts)
  end

  all_entities = parts.join("\r\n--#{boundary}\r\n")
  "--#{boundary}\r\n#{all_entities}\r\n--#{boundary}--\r\n"
end
part(name) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 279
def part(name)
  if self.class.multiple.include?(name)
    self.select { |i| i.name == name }
  else
    self.find { |i| i.name == name }
  end
end
payload_type() click to toggle source
# File lib/praxis/types/multipart_array.rb, line 233
def payload_type
  self.class.payload_type
end
push(*parts, context: Attributor::DEFAULT_ROOT_CONTEXT) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 237
def push(*parts, context: Attributor::DEFAULT_ROOT_CONTEXT)
  part, *rest = parts
  if rest.any?
    return self.push(part, context: context).push(*rest, context:context)
  end

  original_context = context

  part.name = self.class.name_type.load(part.name, self.class.generate_subcontext(context, part.name))
  key = part.name

  context = self.class.generate_subcontext(context, key)

  # If no attributes are defined, we always use the default
  # payload_attribute, otherwise we constrain the parts
  # to the defined names.
  attribute = if self.class.attributes.empty?
    self.class.part_attribute
  elsif (default_thingy = self.class.attributes[key])
    default_thingy
  else
    nil
  end

  if attribute
    part.attribute = attribute
    part.load_payload(context + ['payload'])
    part.load_headers(context + ['headers'])
    return self << part
  elsif self.class.options[:case_insensitive_load]
    name = self.class.attributes.keys.find do |k|
      k.kind_of?(String) && key.downcase == k.downcase
    end
    if name
      part.name = name
      return self.push(part, context: original_context)
    end
  end

  raise Attributor::AttributorException, "Unknown part name received: #{key.inspect} while loading #{Attributor.humanize_context(context)}"
end
validate(context=Attributor::DEFAULT_ROOT_CONTEXT) click to toggle source
# File lib/praxis/types/multipart_array.rb, line 287
def validate(context=Attributor::DEFAULT_ROOT_CONTEXT)
  errors = self.each_with_index.each_with_object([]) do |(part, idx), errors|
    sub_context = if part.name
      self.class.generate_subcontext(context, part.name)
    else
      context + ["at(#{idx})"]
    end

    errors.push *part.validate(sub_context)
  end

  self.class.attributes.each do |name, attribute|
    payload_attribute = attribute.options[:payload_attribute]
    next unless payload_attribute.options[:required]
    next if self.part(name)

    sub_context = self.class.generate_subcontext(context, name)
    errors.push *payload_attribute.validate_missing_value(sub_context)
  end

  errors
end