class SoberSwag::Reporting::Output::Struct

A DSL for building “output object structs.”

Attributes

parent_struct[RW]
_struct_serialized[R]

Public Class Methods

call(value, view: :base) click to toggle source

Serialize an object to a hash.

@param value [Object] value to serialize @param view [Symbol] which view to use to serialize this output. @return [Hash] the serialized ruby hash, suitable for passing to JSON.generate

# File lib/sober_swag/reporting/output/struct.rb, line 124
def call(value, view: :base)
  view(view).output.call(value)
end
define_view(name, &block) click to toggle source

Define a view for this object.

Views behave like their own output structs, which inherit the parent (or 'base' view). This means that fields after the definition of a view *will be present in the view*. This enables views to maintain a subtyping relationship.

Your base view should thus serialize *as little as possible*.

View classes get defined as child constants. So, if I write `define_view(:foo)` on a struct called `Person`, I will get `Person::Foo` as a class I can use if I want!

@param name [Symbol] name of this view. @yieldself [self] a block in which you can add more fields to the view. @return [Class]

# File lib/sober_swag/reporting/output/struct.rb, line 161
def define_view(name, &block) # rubocop:disable Metrics/MethodLength
  raise ArgumentError, "duplicate view #{name}" if name == :base || views.include?(name)

  classy_name = name.to_s.classify

  Class.new(self).tap do |c|
    c.instance_eval(&block)
    c.define_singleton_method(:define_view) do |*|
      raise ArgumentError, 'no nesting views'
    end
    c.define_singleton_method(:identifier) do
      [parent_struct.identifier, classy_name.gsub('::', '.')].join('.')
    end
    const_set(classy_name, c)
    view_map[name] = c
  end
end
description(val = nil) click to toggle source

Set a description for the type of this output. It will show up as a description in the component key for this output. Right now that unfortunately will not render with ReDoc, but it should eventually.

@param val [String, nil] pass if you want to set, otherwise you will get the current value @return [String] the description assigned to this object, if any.

# File lib/sober_swag/reporting/output/struct.rb, line 47
def description(val = nil)
  return @description unless val

  @description = val
end
field(name, output, description: nil, &extract) click to toggle source

Define a new field to be serialized.

@param name [Symbol] name of this field. @param output [Interface] reporting output to use to serialize. @param description [String,nil] description for this field. @param block [Proc, nil]

If a block is given, it will be defined as a method on the output object struct.
If the block takes an argument, the object being serialized will be passed to it.
Otherwise, it will be accessible as `#object_to_serialize` from within the body.

You can access other methods from this method.
# File lib/sober_swag/reporting/output/struct.rb, line 22
def field(name, output, description: nil, &extract)
  define_field(name, extract)

  object_fields[name] = Object::Property.new(
    output.view(:base).via_map(&name.to_proc),
    description: description
  )
end
identified_with_base() click to toggle source

Used to generate 'allOf' subtyping relationships. Probably do not call this yourself.

@return [Interface]

# File lib/sober_swag/reporting/output/struct.rb, line 72
def identified_with_base
  object_output.referenced([identifier, 'Base'].join('.'))
end
identified_without_base() click to toggle source

Used to generate 'allOf' subtyping relationships. Probably do not call this yourself.

# File lib/sober_swag/reporting/output/struct.rb, line 79
def identified_without_base
  if parent_struct
    MergeObjects
      .new(parent_struct.inherited_output, object_output)
  else
    object_output
  end.referenced(identifier)
end
identifier(value = nil) click to toggle source

Set a new identifier for this output object.

@param value [String, nil] provide a new identifier to use.

Stateful operation.

@return [String] identifier key to use in the components hash.

In rare cases (a class with no name and no set identifier) it can return nil.
We consider this case "unsupported", IE, please do not do that.
# File lib/sober_swag/reporting/output/struct.rb, line 219
def identifier(value = nil)
  if value
    @identifier = value
  else
    @identifier || name&.gsub('::', '.')
  end
end
inherited(other) click to toggle source

When this class is inherited, it sets up a future subtyping relationship. This gets expressed with 'allOf' in the generated swagger.

# File lib/sober_swag/reporting/output/struct.rb, line 207
def inherited(other)
  other.parent_struct = self unless self == ::SoberSwag::Reporting::Output::Struct
end
inherited_output() click to toggle source

Used to generate 'allOf' subtyping relationships. Probably do not call this yourself! Use {#single_output} instead.

This allows us to implement inheritance. So, if you inherit from another output object struct, you get its methods and attributes. Views behave as if they have inherited the base object.

This means that any views added to any parent output objects will be visible in children. @return [Interface]

# File lib/sober_swag/reporting/output/struct.rb, line 99
def inherited_output
  inherited =
    if parent_struct
      MergeObjects
        .new(parent_struct.inherited_output, object_output)
    else
      object_output
    end

  identifier ? inherited.referenced([identifier, 'Base'].join('.')) : inherited
end
new(struct_serialized) click to toggle source
# File lib/sober_swag/reporting/output/struct.rb, line 247
def initialize(struct_serialized)
  @_struct_serialized = struct_serialized
end
object_fields() click to toggle source

@return [Hash<Symbol, Object::Property>] the properties defined directly on this object.

Does not include inherited fields!
# File lib/sober_swag/reporting/output/struct.rb, line 141
def object_fields
  @object_fields ||= {}
end
object_output() click to toggle source
# File lib/sober_swag/reporting/output/struct.rb, line 31
def object_output
  base = Object.new(object_fields).via_map { |o| new(o) }
  if description
    base.described(description)
  else
    base
  end
end
serialize_report(value, view: :base) click to toggle source

Serialize an object to a hash, with type-checking.

@param value [Object] value to serialize @param view [Symbol] which view to use @return [Hash] the serialized ruby hash, suitable for passsing to JSON.generate

# File lib/sober_swag/reporting/output/struct.rb, line 134
def serialize_report(value, view: :base)
  view(view).output.serialize_report(value)
end
single_output() click to toggle source

An output for this specific schema type. If this schema has any views, it will be defined as a map of possible views to the actual views used. Otherwise, it will directly be the base definition.

# File lib/sober_swag/reporting/output/struct.rb, line 57
def single_output
  single =
    if view_map.any?
      Viewed.new(identified_view_map)
    else
      inherited_output
    end
  identifier ? single.referenced(identifier) : single
end
swagger_schema() click to toggle source

Schema for this output. Will include views, if applicable.

# File lib/sober_swag/reporting/output/struct.rb, line 114
def swagger_schema
  single_output.swagger_schema
end
view(name) click to toggle source

@param name [Symbol] which view to use. @return [Interface] a serializer suitable for this interface.

# File lib/sober_swag/reporting/output/struct.rb, line 196
def view(name)
  return inherited_output if name == :base

  view_map.fetch(name).view(:base)
end
view_map() click to toggle source

@return Hash<Symbol,Class> map of potential views.

Does not include the 'base' view.
# File lib/sober_swag/reporting/output/struct.rb, line 182
def view_map
  @view_map ||= {}
end
views() click to toggle source

@return [Set<Symbol>] all applicable views.

Will always include `:base`.
# File lib/sober_swag/reporting/output/struct.rb, line 189
def views
  [:base, *view_map.keys].to_set
end

Private Class Methods

define_field(method, extractor) click to toggle source
# File lib/sober_swag/reporting/output/struct.rb, line 233
def define_field(method, extractor)
  e =
    if extractor.nil?
      proc { _struct_serialized.public_send(method) }
    elsif extractor.arity == 1
      proc { extractor.call(_struct_serialized) }
    else
      extractor
    end

  define_method(method, &e)
end
identified_view_map() click to toggle source
# File lib/sober_swag/reporting/output/struct.rb, line 229
def identified_view_map
  view_map.transform_values(&:identified_without_base).merge(base: inherited_output)
end

Public Instance Methods

object_to_serialize() click to toggle source

The object to serialize. Use this if you're defining your own methods.

# File lib/sober_swag/reporting/output/struct.rb, line 256
def object_to_serialize
  @_struct_serialized
end