Class: SoberSwag::Reporting::Output::Struct

Inherits:
Object
  • Object
show all
Extended by:
Interface
Defined in:
lib/sober_swag/reporting/output/struct.rb

Overview

A DSL for building "output object structs."

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Interface

call!, described, enum, in_range, list, nilable, partitioned, referenced, reporting?, serialize, via_map

Constructor Details

#initialize(struct_serialized) ⇒ Struct

Returns a new instance of Struct.



280
281
282
# File 'lib/sober_swag/reporting/output/struct.rb', line 280

def initialize(struct_serialized)
  @_struct_serialized = struct_serialized
end

Class Attribute Details

.parent_structObject

Returns the value of attribute parent_struct.



212
213
214
# File 'lib/sober_swag/reporting/output/struct.rb', line 212

def parent_struct
  @parent_struct
end

Instance Attribute Details

#_struct_serializedObject (readonly)

Returns the value of attribute _struct_serialized.



284
285
286
# File 'lib/sober_swag/reporting/output/struct.rb', line 284

def _struct_serialized
  @_struct_serialized
end

Class Method Details

.bad_field_message(name, field_type) ⇒ Object (private)



239
240
241
242
243
244
245
# File 'lib/sober_swag/reporting/output/struct.rb', line 239

def bad_field_message(name, field_type)
  [
    "Output type used for field #{name.inspect} was",
    "#{field_type.inspect}, which is not an instance of",
    SoberSwag::Reporting::Output::Interface.name
  ].join(' ')
end

.call(value, view: :base) ⇒ Hash

Serialize an object to a hash.

Parameters:

  • value (Object)

    value to serialize

  • view (Symbol) (defaults to: :base)

    which view to use to serialize this output.

Returns:

  • (Hash)

    the serialized ruby hash, suitable for passing to JSON.generate



126
127
128
# File 'lib/sober_swag/reporting/output/struct.rb', line 126

def call(value, view: :base)
  view(view).output.call(value)
end

.define_field(method, extractor) ⇒ Object (private)



266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/sober_swag/reporting/output/struct.rb', line 266

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

.define_inherited_view(name, inherits:, &block) ⇒ Class

Defines a view for this object, which "inherits" another view.

Parameters:

  • name (Symbol)

    name of this view

  • inherits (Symbol)

    name of the view this view inherits

Returns:

  • (Class)

See Also:

  • for how views behave.


175
176
177
# File 'lib/sober_swag/reporting/output/struct.rb', line 175

def define_inherited_view(name, inherits:, &block)
  define_view_with_parent(name, view_class(inherits), block)
end

.define_view(name, &block) ⇒ Class

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!

Parameters:

  • name (Symbol)

    name of this view.

Returns:

  • (Class)


163
164
165
# File 'lib/sober_swag/reporting/output/struct.rb', line 163

def define_view(name, &block)
  define_view_with_parent(name, self, block)
end

.define_view_with_parent(name, parent, block) ⇒ Object (private)

Raises:

  • (ArgumentError)


247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/sober_swag/reporting/output/struct.rb', line 247

def define_view_with_parent(name, parent, block)
  raise ArgumentError, "duplicate view #{name}" if name == :base || views.include?(name)

  classy_name = name.to_s.classify
  us = self # grab this so its identifier doesn't get nested under whatever parent it inherits from, since its our view

  Class.new(parent).tap do |c|
    c.instance_eval(&block)
    c.define_singleton_method(:define_view) { |*| raise ArgumentError, 'no nesting views' }
    c.define_singleton_method(:identifier) { [us.identifier, classy_name.gsub('::', '.')].join('.') }
    const_set(classy_name, c)
    view_map[name] = c
  end
end

.description(val = nil) ⇒ String

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.

Parameters:

  • val (String, nil) (defaults to: nil)

    pass if you want to set, otherwise you will get the current value

Returns:

  • (String)

    the description assigned to this object, if any.



49
50
51
52
53
# File 'lib/sober_swag/reporting/output/struct.rb', line 49

def description(val = nil)
  return @description unless val

  @description = val
end

.field(name, output, description: nil, &extract) ⇒ Object

Define a new field to be serialized.

Parameters:

  • name (Symbol)

    name of this field.

  • output (Interface)

    reporting output to use to serialize.

  • description (String, nil) (defaults to: nil)

    description for this field.

  • 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.

Raises:

  • (ArgumentError)


22
23
24
25
26
27
28
29
30
31
# File 'lib/sober_swag/reporting/output/struct.rb', line 22

def field(name, output, description: nil, &extract)
  raise ArgumentError, bad_field_message(name, output) unless output.is_a?(Interface)

  define_field(name, extract)

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

.identified_view_mapObject (private)



262
263
264
# File 'lib/sober_swag/reporting/output/struct.rb', line 262

def identified_view_map
  view_map.transform_values(&:identified_without_base).merge(base: inherited_output)
end

.identified_with_baseInterface

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

Returns:



74
75
76
# File 'lib/sober_swag/reporting/output/struct.rb', line 74

def identified_with_base
  object_output.referenced([identifier, 'Base'].join('.'))
end

.identified_without_baseObject

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



81
82
83
84
85
86
87
88
# File 'lib/sober_swag/reporting/output/struct.rb', line 81

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) ⇒ String

Set a new identifier for this output object.

Parameters:

  • value (String, nil) (defaults to: nil)

    provide a new identifier to use. Stateful operation.

Returns:

  • (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.



229
230
231
232
233
234
235
# File 'lib/sober_swag/reporting/output/struct.rb', line 229

def identifier(value = nil)
  if value
    @identifier = value
  else
    @identifier || name&.gsub('::', '.')
  end
end

.inherited(other) ⇒ Object

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



217
218
219
# File 'lib/sober_swag/reporting/output/struct.rb', line 217

def inherited(other)
  other.parent_struct = self unless self == ::SoberSwag::Reporting::Output::Struct
end

.inherited_outputInterface

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.

Returns:



101
102
103
104
105
106
107
108
109
110
111
# File 'lib/sober_swag/reporting/output/struct.rb', line 101

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

.object_fieldsHash<Symbol, Object::Property>

Returns the properties defined directly on this object. Does not include inherited fields!.

Returns:

  • (Hash<Symbol, Object::Property>)

    the properties defined directly on this object. Does not include inherited fields!



143
144
145
# File 'lib/sober_swag/reporting/output/struct.rb', line 143

def object_fields
  @object_fields ||= {}
end

.object_outputObject



33
34
35
36
37
38
39
40
# File 'lib/sober_swag/reporting/output/struct.rb', line 33

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) ⇒ Hash

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

Parameters:

  • value (Object)

    value to serialize

  • view (Symbol) (defaults to: :base)

    which view to use

Returns:

  • (Hash)

    the serialized ruby hash, suitable for passsing to JSON.generate



136
137
138
# File 'lib/sober_swag/reporting/output/struct.rb', line 136

def serialize_report(value, view: :base)
  view(view).output.serialize_report(value)
end

.single_outputObject

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.



59
60
61
62
63
64
65
66
67
# File 'lib/sober_swag/reporting/output/struct.rb', line 59

def single_output
  single =
    if view_map.any?
      Viewed.new(identified_view_map)
    else
      inherited_output
    end
  identifier ? single.referenced(identifier) : single
end

.swagger_schemaObject

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



116
117
118
# File 'lib/sober_swag/reporting/output/struct.rb', line 116

def swagger_schema
  single_output.swagger_schema
end

.view(name) ⇒ Interface

Returns a serializer suitable for this interface.

Parameters:

  • name (Symbol)

    which view to use.

Returns:

  • (Interface)

    a serializer suitable for this interface.



196
197
198
199
200
# 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_class(name) ⇒ Class

Equivalent to .view, but returns the raw view class.

Returns:

  • (Class)


206
207
208
209
210
# File 'lib/sober_swag/reporting/output/struct.rb', line 206

def view_class(name)
  return self if name == :base

  view_map.fetch(name)
end

.view_mapObject



182
183
184
# File 'lib/sober_swag/reporting/output/struct.rb', line 182

def view_map
  @view_map ||= {}
end

.viewsSet<Symbol>

Returns all applicable views. Will always include :base.

Returns:

  • (Set<Symbol>)

    all applicable views. Will always include :base.



189
190
191
# File 'lib/sober_swag/reporting/output/struct.rb', line 189

def views
  [:base, *view_map.keys].to_set
end

Instance Method Details

#object_to_serializeObject

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



289
290
291
# File 'lib/sober_swag/reporting/output/struct.rb', line 289

def object_to_serialize
  @_struct_serialized
end