sober_swag 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/lint.yml +1 -1
- data/.github/workflows/ruby.yml +1 -1
- data/.gitignore +2 -0
- data/.rubocop.yml +5 -1
- data/.yardopts +7 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +8 -0
- data/README.md +1 -1
- data/docs/serializers.md +3 -0
- data/example/Gemfile +1 -1
- data/example/config/environments/production.rb +1 -1
- data/lib/sober_swag.rb +6 -1
- data/lib/sober_swag/compiler.rb +29 -3
- data/lib/sober_swag/compiler/path.rb +42 -3
- data/lib/sober_swag/compiler/paths.rb +20 -0
- data/lib/sober_swag/compiler/primitive.rb +17 -0
- data/lib/sober_swag/compiler/type.rb +105 -22
- data/lib/sober_swag/controller.rb +39 -12
- data/lib/sober_swag/controller/route.rb +103 -20
- data/lib/sober_swag/input_object.rb +90 -7
- data/lib/sober_swag/nodes/array.rb +19 -0
- data/lib/sober_swag/nodes/attribute.rb +45 -4
- data/lib/sober_swag/nodes/base.rb +27 -7
- data/lib/sober_swag/nodes/binary.rb +30 -13
- data/lib/sober_swag/nodes/enum.rb +16 -1
- data/lib/sober_swag/nodes/list.rb +20 -0
- data/lib/sober_swag/nodes/nullable_primitive.rb +3 -0
- data/lib/sober_swag/nodes/object.rb +4 -1
- data/lib/sober_swag/nodes/one_of.rb +11 -3
- data/lib/sober_swag/nodes/primitive.rb +34 -2
- data/lib/sober_swag/nodes/sum.rb +8 -0
- data/lib/sober_swag/output_object.rb +35 -4
- data/lib/sober_swag/output_object/definition.rb +31 -1
- data/lib/sober_swag/output_object/field.rb +31 -11
- data/lib/sober_swag/output_object/field_syntax.rb +19 -3
- data/lib/sober_swag/output_object/view.rb +46 -1
- data/lib/sober_swag/parser.rb +7 -1
- data/lib/sober_swag/serializer/array.rb +27 -3
- data/lib/sober_swag/serializer/base.rb +75 -25
- data/lib/sober_swag/serializer/conditional.rb +33 -1
- data/lib/sober_swag/serializer/field_list.rb +18 -2
- data/lib/sober_swag/serializer/mapped.rb +10 -1
- data/lib/sober_swag/serializer/optional.rb +18 -1
- data/lib/sober_swag/serializer/primitive.rb +3 -0
- data/lib/sober_swag/server.rb +5 -1
- data/lib/sober_swag/type/named.rb +14 -0
- data/lib/sober_swag/types/comma_array.rb +4 -0
- data/lib/sober_swag/version.rb +1 -1
- metadata +3 -2
@@ -2,8 +2,11 @@ module SoberSwag
|
|
2
2
|
module Nodes
|
3
3
|
##
|
4
4
|
# Objects might have attribute keys, so they're
|
5
|
-
# basically a list of attributes
|
5
|
+
# basically a list of attributes.
|
6
6
|
class Object < SoberSwag::Nodes::Array
|
7
|
+
##
|
8
|
+
# @return [Hash{Symbol => Array<SoberSwag::Nodes::Attribute>}]
|
9
|
+
# the attributes, wrapped in an `attributes:` key.
|
7
10
|
def deconstruct_keys(_)
|
8
11
|
{ attributes: @elements }
|
9
12
|
end
|
@@ -1,11 +1,19 @@
|
|
1
1
|
module SoberSwag
|
2
2
|
module Nodes
|
3
3
|
##
|
4
|
-
#
|
5
|
-
#
|
4
|
+
# OpenAPI v3 represents types that are a "choice" between multiple alternatives as an array.
|
5
|
+
# However, it is easier to model these as a sum type initially: if a type can be either an `A`, a `B`, or a `C`, we can model this as:
|
6
|
+
#
|
7
|
+
# `Sum.new(A, Sum.new(B, C))`.
|
8
|
+
#
|
9
|
+
# This means we only ever need to deal with two types at once.
|
10
|
+
# So, we initially serialize to a sum type, then later transform to this array type for further serialization.
|
6
11
|
class OneOf < ::SoberSwag::Nodes::Array
|
12
|
+
##
|
13
|
+
# @return [Hash{Symbol => SoberSwag::Nodes::Base}]
|
14
|
+
# the alternatives, wrapped in an `alternatives:` key.
|
7
15
|
def deconstruct_keys(_)
|
8
|
-
{ alternatives: @
|
16
|
+
{ alternatives: @elements }
|
9
17
|
end
|
10
18
|
end
|
11
19
|
end
|
@@ -1,27 +1,59 @@
|
|
1
1
|
module SoberSwag
|
2
2
|
module Nodes
|
3
3
|
##
|
4
|
-
# Root node of the tree
|
4
|
+
# Root node of the tree.
|
5
|
+
# This contains "primitive values."
|
6
|
+
# Initially, this is probably the types of attributes or array elements or whatever.
|
7
|
+
# As we use {#cata} to transform this, it will start containing swagger-compatible type objects.
|
8
|
+
#
|
9
|
+
# This node can contain metadata as well.
|
5
10
|
class Primitive < Base
|
11
|
+
##
|
12
|
+
# @param value [Object] the primitive value to store
|
13
|
+
# @param metadata [Hash] the metadata
|
6
14
|
def initialize(value, metadata = {})
|
7
15
|
@value = value
|
8
16
|
@metadata = metadata
|
9
17
|
end
|
10
18
|
|
11
|
-
|
19
|
+
##
|
20
|
+
# @return [Object] the contained value
|
21
|
+
attr_reader :value
|
12
22
|
|
23
|
+
##
|
24
|
+
# @return [Hash] metadata associated with the contained value.
|
25
|
+
attr_reader :metadata
|
26
|
+
|
27
|
+
##
|
28
|
+
# @see SoberSwag::Nodes::Base#map
|
29
|
+
#
|
30
|
+
# This will actually map over {#value}.
|
13
31
|
def map(&block)
|
14
32
|
self.class.new(block.call(value), metadata.dup)
|
15
33
|
end
|
16
34
|
|
35
|
+
##
|
36
|
+
# Deconstructs to the value and the metadata.
|
37
|
+
#
|
38
|
+
# @return [Array(Object, Hash)] containing value and metadata.
|
17
39
|
def deconstruct
|
18
40
|
[value, metadata]
|
19
41
|
end
|
20
42
|
|
43
|
+
##
|
44
|
+
# Wraps the attributes in a hash.
|
45
|
+
#
|
46
|
+
# @return [Hash{Symbol => Object, Hash}]
|
47
|
+
# {#value} in `value:`, {#metadata} in `metadata:`
|
21
48
|
def deconstruct_keys(_)
|
22
49
|
{ value: value, metadata: metadata }
|
23
50
|
end
|
24
51
|
|
52
|
+
##
|
53
|
+
# @see SoberSwag::Nodes::Base#cata
|
54
|
+
# As this is a root node, we actually call the block directly.
|
55
|
+
# @yieldparam node [SoberSwag::Nodes::Primitive] this node.
|
56
|
+
# @return whatever the block returns.
|
25
57
|
def cata(&block)
|
26
58
|
block.call(self.class.new(value, metadata))
|
27
59
|
end
|
data/lib/sober_swag/nodes/sum.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
module SoberSwag
|
2
2
|
module Nodes
|
3
|
+
##
|
4
|
+
# A "Sum" type represents either one type or the other.
|
5
|
+
#
|
6
|
+
# It is called "Sum" because, if a type can be either type `A` or type `B`,
|
7
|
+
# the number of possible values for the type of `number_of_values(A) + number_of_values(B)`.
|
8
|
+
#
|
9
|
+
# Internally, this is primarily used when an object can be either one type or another.
|
10
|
+
# It will latter be flattened into {SoberSwag::Nodes::OneOf}
|
3
11
|
class Sum < Binary
|
4
12
|
end
|
5
13
|
end
|
@@ -5,7 +5,7 @@ module SoberSwag
|
|
5
5
|
# Create a serializer that is heavily inspired by the "Blueprinter" library.
|
6
6
|
# This allows you to make "views" and such inside.
|
7
7
|
#
|
8
|
-
# Under the hood, this is actually all based on {SoberSwag::
|
8
|
+
# Under the hood, this is actually all based on {SoberSwag::Serializer::Base}.
|
9
9
|
class OutputObject < SoberSwag::Serializer::Base
|
10
10
|
autoload(:Field, 'sober_swag/output_object/field')
|
11
11
|
autoload(:Definition, 'sober_swag/output_object/definition')
|
@@ -20,7 +20,7 @@ module SoberSwag
|
|
20
20
|
#
|
21
21
|
# PersonSerializer = SoberSwag::OutputObject.define do
|
22
22
|
# field :id, primitive(:Integer)
|
23
|
-
# field :name,
|
23
|
+
# field :name, primitive(:String).optional
|
24
24
|
#
|
25
25
|
# view :complex do
|
26
26
|
# field :age, primitive(:Integer)
|
@@ -32,9 +32,11 @@ module SoberSwag
|
|
32
32
|
# However, this is only a hack to get rid of the weird naming issue when
|
33
33
|
# generating swagger from dry structs: their section of the schema area
|
34
34
|
# is defined by their *Ruby Class Name*. In the future, if we get rid of this,
|
35
|
-
# we might be able to keep this on the value-level, in which case {
|
35
|
+
# we might be able to keep this on the value-level, in which case {.define}
|
36
36
|
# can simply return an *instance* of SoberSwag::Serializer that does
|
37
37
|
# the correct thing, with the name you give it. This works for now, though.
|
38
|
+
#
|
39
|
+
# @return [Class] the serializer generated.
|
38
40
|
def self.define(&block)
|
39
41
|
d = Definition.new.tap do |o|
|
40
42
|
o.instance_eval(&block)
|
@@ -42,28 +44,51 @@ module SoberSwag
|
|
42
44
|
new(d.fields, d.views, d.identifier)
|
43
45
|
end
|
44
46
|
|
47
|
+
##
|
48
|
+
# @param fields [Array<SoberSwag::OutputObject::Field>] the fields for this OutputObject
|
49
|
+
# @param views [Array<SoberSwag::OutputObject::View>] the views for this OutputObject
|
50
|
+
# @param identifier [String] the external identifier for this OutputObject
|
45
51
|
def initialize(fields, views, identifier)
|
46
52
|
@fields = fields
|
47
53
|
@views = views
|
48
54
|
@identifier = identifier
|
49
55
|
end
|
50
56
|
|
51
|
-
|
57
|
+
##
|
58
|
+
# @return [Array<SoberSwag::OutputObject::Field>]
|
59
|
+
attr_reader :fields
|
60
|
+
##
|
61
|
+
# @return [Array<SoberSwag::OutputObject::View>]
|
62
|
+
attr_reader :views
|
63
|
+
##
|
64
|
+
# @return [String] the external ID to use for this object
|
65
|
+
attr_reader :identifier
|
52
66
|
|
67
|
+
##
|
68
|
+
# Perform serialization.
|
53
69
|
def serialize(obj, opts = {})
|
54
70
|
serializer.serialize(obj, opts)
|
55
71
|
end
|
56
72
|
|
73
|
+
##
|
74
|
+
# Get a Dry::Struct of the type this OutputObject will serialize to.
|
57
75
|
def type
|
58
76
|
serializer.type
|
59
77
|
end
|
60
78
|
|
79
|
+
##
|
80
|
+
# Get a serializer for a single view contained in this output object.
|
81
|
+
# Note: given `:base`, it will return a serializer for the base OutputObject
|
82
|
+
# @param name [Symbol] the name of the view
|
83
|
+
# @return [SoberSwag::Serializer::Base] the serializer
|
61
84
|
def view(name)
|
62
85
|
return base_serializer if name == :base
|
63
86
|
|
64
87
|
@views.find { |v| v.name == name }
|
65
88
|
end
|
66
89
|
|
90
|
+
##
|
91
|
+
# A serializer for the "base type" of this OutputObject, with no views.
|
67
92
|
def base
|
68
93
|
base_serializer
|
69
94
|
end
|
@@ -72,6 +97,8 @@ module SoberSwag
|
|
72
97
|
# Compile down this to an appropriate serializer.
|
73
98
|
# It uses {SoberSwag::Serializer::Conditional} to do view-parsing,
|
74
99
|
# and {SoberSwag::Serializer::FieldList} to do the actual serialization.
|
100
|
+
#
|
101
|
+
# @todo: optimize view selection to use binary instead of linear search
|
75
102
|
def serializer # rubocop:disable Metrics/MethodLength
|
76
103
|
@serializer ||=
|
77
104
|
begin
|
@@ -92,10 +119,14 @@ module SoberSwag
|
|
92
119
|
end
|
93
120
|
end
|
94
121
|
|
122
|
+
##
|
123
|
+
# @return [String]
|
95
124
|
def to_s
|
96
125
|
"<SoberSwag::OutputObject(#{identifier})>"
|
97
126
|
end
|
98
127
|
|
128
|
+
##
|
129
|
+
# @return [SoberSwag::Serializer::FieldList] serializer for this output object.
|
99
130
|
def base_serializer
|
100
131
|
@base_serializer ||= SoberSwag::Serializer::FieldList.new(fields).tap do |s|
|
101
132
|
s.identifier(identifier)
|
@@ -9,14 +9,30 @@ module SoberSwag
|
|
9
9
|
@views = []
|
10
10
|
end
|
11
11
|
|
12
|
-
|
12
|
+
##
|
13
|
+
# @return [Array<SoberSwag::OutputObject::Field>]
|
14
|
+
attr_reader :fields
|
15
|
+
|
16
|
+
##
|
17
|
+
# @return [Array<SoberSwag::OutputObject::View>]
|
18
|
+
attr_reader :views
|
13
19
|
|
14
20
|
include FieldSyntax
|
15
21
|
|
22
|
+
##
|
23
|
+
# Adds a new field to the fields array
|
24
|
+
# @param field [SoberSwag::OutputObject::Field]
|
16
25
|
def add_field!(field)
|
17
26
|
@fields << field
|
18
27
|
end
|
19
28
|
|
29
|
+
##
|
30
|
+
# Define a new view, with the view DSL
|
31
|
+
# @param name [Symbol] the name of the view
|
32
|
+
# @param inherits [Symbol] the name of another view this
|
33
|
+
# view will "inherit" from
|
34
|
+
# @yieldself [SoberSwag::OutputObject::View]
|
35
|
+
# @return [nil] nothing interesting.
|
20
36
|
def view(name, inherits: nil, &block)
|
21
37
|
initial_fields =
|
22
38
|
if inherits.nil? || inherits == :base
|
@@ -31,6 +47,14 @@ module SoberSwag
|
|
31
47
|
@views << view
|
32
48
|
end
|
33
49
|
|
50
|
+
##
|
51
|
+
# @overload identifier()
|
52
|
+
# Get the identifier of this output object.
|
53
|
+
# @return [String] the identifier
|
54
|
+
# @overload identifier(arg)
|
55
|
+
# Set the identifier of this output object.
|
56
|
+
# @param arg [String] the external identifier to use for this OutputObject
|
57
|
+
# @return [String] `arg`
|
34
58
|
def identifier(arg = nil)
|
35
59
|
@identifier = arg if arg
|
36
60
|
@identifier
|
@@ -38,6 +62,12 @@ module SoberSwag
|
|
38
62
|
|
39
63
|
private
|
40
64
|
|
65
|
+
##
|
66
|
+
# Get the already-defined view with a specific name.
|
67
|
+
#
|
68
|
+
# @param name [Symbol] name of view to look up
|
69
|
+
# @return [SoberSwag::OutputObject::View] the view found
|
70
|
+
# @raise [ArgumentError] if no view with that name found
|
41
71
|
def find_view(name)
|
42
72
|
@views.find { |view| view.name == name } || (raise ArgumentError, "no view #{name.inspect} defined!")
|
43
73
|
end
|
@@ -4,6 +4,18 @@ module SoberSwag
|
|
4
4
|
# A single field in an output object.
|
5
5
|
# Later used to make an actual serializer from this.
|
6
6
|
class Field
|
7
|
+
##
|
8
|
+
# @param name [Symbol] the name of this field
|
9
|
+
# @param serializer [SoberSwag::Serializer::Base, Proc, Lambda] how to serialize
|
10
|
+
# the value in this field.
|
11
|
+
# If given a `Proc` or `Lambda`, the `Proc` or `Lambda` should return
|
12
|
+
# an instance of SoberSwag::Serializer::Base when called.
|
13
|
+
# @param from [Symbol] an optional parameter specifying
|
14
|
+
# that this field should be plucked "from" another
|
15
|
+
# attribute of a ruby object
|
16
|
+
# @param block [Proc] a proc to get this field from a serialized
|
17
|
+
# object. If not given, will try to grab an attribute
|
18
|
+
# with the same name, *or* with the name of `from:` if that was sent.
|
7
19
|
def initialize(name, serializer, from: nil, &block)
|
8
20
|
@name = name
|
9
21
|
@root_serializer = serializer
|
@@ -11,12 +23,18 @@ module SoberSwag
|
|
11
23
|
@block = block
|
12
24
|
end
|
13
25
|
|
26
|
+
##
|
27
|
+
# @return [Symbol] name of this field.
|
14
28
|
attr_reader :name
|
15
29
|
|
30
|
+
##
|
31
|
+
# @return [SoberSwag::Serializer::Base]
|
16
32
|
def serializer
|
17
33
|
@serializer ||= resolved_serializer.serializer.via_map(&transform_proc)
|
18
34
|
end
|
19
35
|
|
36
|
+
##
|
37
|
+
# @return [SoberSwag::Serializer::Base]
|
20
38
|
def resolved_serializer
|
21
39
|
if @root_serializer.is_a?(Proc)
|
22
40
|
@root_serializer.call
|
@@ -27,17 +45,19 @@ module SoberSwag
|
|
27
45
|
|
28
46
|
private
|
29
47
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
48
|
+
##
|
49
|
+
# @return [Proc]
|
50
|
+
def transform_proc
|
51
|
+
return @transform_proc if defined?(@transform_proc)
|
52
|
+
|
53
|
+
return @transform_proc = @block if @block
|
54
|
+
|
55
|
+
key = @from || @name
|
56
|
+
@transform_proc = proc do |object, _|
|
57
|
+
if object.respond_to?(key)
|
58
|
+
object.public_send(key)
|
59
|
+
else
|
60
|
+
object[key]
|
41
61
|
end
|
42
62
|
end
|
43
63
|
end
|
@@ -3,6 +3,13 @@ module SoberSwag
|
|
3
3
|
##
|
4
4
|
# Syntax for definitions that can add fields.
|
5
5
|
module FieldSyntax
|
6
|
+
##
|
7
|
+
# Defines a new field.
|
8
|
+
# @see SoberSwag::OutputObject::Field#initialize
|
9
|
+
# @param name [Symbol] name of this field
|
10
|
+
# @param serializer [SoberSwag::Serializer::Base] serializer to use for this field.
|
11
|
+
# @param from [Symbol] method name to extract this field from, for convenience.
|
12
|
+
# @param block [Proc] optional way to extract this field.
|
6
13
|
def field(name, serializer, from: nil, &block)
|
7
14
|
add_field!(Field.new(name, serializer, from: from, &block))
|
8
15
|
end
|
@@ -10,22 +17,31 @@ module SoberSwag
|
|
10
17
|
##
|
11
18
|
# Similar to #field, but adds multiple at once.
|
12
19
|
# Named #multi because #fields was already taken.
|
20
|
+
#
|
21
|
+
# @param names [Array<Symbol>] the field names to add.
|
22
|
+
# @param serializer [SoberSwag::Serializer::Base] the serializer to use for all fields.
|
13
23
|
def multi(names, serializer)
|
14
24
|
names.each { |name| field(name, serializer) }
|
15
25
|
end
|
16
26
|
|
17
27
|
##
|
18
28
|
# Given a symbol to this, we will use a primitive name
|
29
|
+
# @param name [Symbol] symbol to look up.
|
30
|
+
# @return [SoberSwag::Serializer::Base] serializer to use.
|
19
31
|
def primitive(name)
|
20
32
|
SoberSwag::Serializer.primitive(SoberSwag::Types.const_get(name))
|
21
33
|
end
|
22
34
|
|
23
35
|
##
|
24
36
|
# Merge in anything that has a list of fields, and use it.
|
25
|
-
# Note that merging in a full
|
26
|
-
|
37
|
+
# Note that merging in a full output object *will not* also merge in views, just fields defined on the base.
|
38
|
+
#
|
39
|
+
# @param other [#fields] a field container, like a {SoberSwag::OutputObject} or something
|
40
|
+
# @param except [Array<Symbol>] optionally exclude a field from the output object being merged
|
41
|
+
# @return [void]
|
42
|
+
def merge(other, except: [])
|
27
43
|
other.fields.each do |field|
|
28
|
-
add_field!(field)
|
44
|
+
add_field!(field) unless except.include?(field.name)
|
29
45
|
end
|
30
46
|
end
|
31
47
|
end
|
@@ -3,44 +3,87 @@ module SoberSwag
|
|
3
3
|
##
|
4
4
|
# DSL for defining a view.
|
5
5
|
# Used in `view` blocks within {SoberSwag::OutputObject.define}.
|
6
|
+
#
|
7
|
+
# Views are "variants" of {SoberSwag::OutputObject}s that contain
|
8
|
+
# different fields.
|
6
9
|
class View < SoberSwag::Serializer::Base
|
10
|
+
##
|
11
|
+
# Define a new view with the given base fields.
|
12
|
+
# @param name [Symbol] name for this view
|
13
|
+
# @param base_fields [Array<SoberSwag::OutputObject::Field>] fields already defined
|
14
|
+
# @yieldself [SoberSwag::OutputObject::View]
|
15
|
+
#
|
16
|
+
# @return [SoberSwag::OutputObject::View]
|
7
17
|
def self.define(name, base_fields, &block)
|
8
18
|
new(name, base_fields).tap do |view|
|
9
19
|
view.instance_eval(&block)
|
10
20
|
end
|
11
21
|
end
|
12
22
|
|
23
|
+
##
|
24
|
+
# An error thrown when you try to nest views inside views.
|
13
25
|
class NestingError < Error; end
|
14
26
|
|
15
27
|
include FieldSyntax
|
16
28
|
|
29
|
+
##
|
30
|
+
# @param name [Sybmol] name for this view.
|
31
|
+
# @param base_fields [Array<SoberSwag::OutputObject::Field>] already-defined fields.
|
17
32
|
def initialize(name, base_fields = [])
|
18
33
|
@name = name
|
19
34
|
@fields = base_fields.dup
|
20
35
|
end
|
21
36
|
|
22
|
-
|
37
|
+
##
|
38
|
+
# @return [Symbol] the name of this view
|
39
|
+
attr_reader :name
|
40
|
+
|
41
|
+
##
|
42
|
+
# @return [Array<SoberSwag::OutputObject::Fields>] the fields defined in this view.
|
43
|
+
attr_reader :fields
|
23
44
|
|
45
|
+
##
|
46
|
+
# Serialize an object according to this view.
|
47
|
+
# @param object what to serialize
|
48
|
+
# @param opts [Hash] arbitrary options
|
49
|
+
# @return [Hash] the serialized result
|
24
50
|
def serialize(obj, opts = {})
|
25
51
|
serializer.serialize(obj, opts)
|
26
52
|
end
|
27
53
|
|
54
|
+
##
|
55
|
+
# Get the type of this view.
|
56
|
+
# @return [Class] the type, a subclass of {Dry::Struct}
|
28
57
|
def type
|
29
58
|
serializer.type
|
30
59
|
end
|
31
60
|
|
61
|
+
##
|
62
|
+
# Excludes a field with the given name from this view.
|
63
|
+
# @param name [Symbol] field to exclude.
|
64
|
+
# @return [nil] nothing interesting
|
32
65
|
def except!(name)
|
33
66
|
@fields.reject! { |f| f.name == name }
|
34
67
|
end
|
35
68
|
|
69
|
+
##
|
70
|
+
# Always raises {NestingError}
|
71
|
+
# @raise {NestingError} always
|
36
72
|
def view(*)
|
37
73
|
raise NestingError, 'no views in views'
|
38
74
|
end
|
39
75
|
|
76
|
+
##
|
77
|
+
# Adds a field do this view.
|
78
|
+
# @param field [SoberSwag::OutputObject::Field]
|
79
|
+
# @return [nil] nothing interesting
|
40
80
|
def add_field!(field)
|
41
81
|
@fields << field
|
42
82
|
end
|
43
83
|
|
84
|
+
##
|
85
|
+
# Pretty show for humans
|
86
|
+
# @return [String]
|
44
87
|
def to_s
|
45
88
|
"<SoberSwag::OutputObject::View(#{identifier})>"
|
46
89
|
end
|
@@ -48,6 +91,8 @@ module SoberSwag
|
|
48
91
|
##
|
49
92
|
# Get the serializer defined by this view.
|
50
93
|
# WARNING: Don't add more fields after you call this.
|
94
|
+
#
|
95
|
+
# @return [SoberSwag::Serializer::FieldList]
|
51
96
|
def serializer
|
52
97
|
@serializer ||=
|
53
98
|
SoberSwag::Serializer::FieldList.new(fields).tap { |s| s.identifier(identifier) }
|