sober_swag 0.21.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/CHANGELOG.md +5 -0
  4. data/bin/console +30 -10
  5. data/docs/reporting.md +190 -0
  6. data/example/Gemfile +2 -2
  7. data/example/Gemfile.lock +92 -101
  8. data/example/app/controllers/application_controller.rb +4 -0
  9. data/example/app/controllers/people_controller.rb +44 -28
  10. data/example/app/output_objects/identified_output.rb +7 -0
  11. data/example/app/output_objects/person_output_object.rb +37 -11
  12. data/example/app/output_objects/post_output_object.rb +0 -4
  13. data/example/app/output_objects/reporting_post_output.rb +18 -0
  14. data/example/bin/rspec +29 -0
  15. data/example/spec/requests/people/create_spec.rb +3 -2
  16. data/example/spec/requests/people/index_spec.rb +1 -1
  17. data/lib/sober_swag/compiler/path.rb +3 -1
  18. data/lib/sober_swag/compiler.rb +58 -12
  19. data/lib/sober_swag/controller/route.rb +44 -8
  20. data/lib/sober_swag/controller.rb +18 -5
  21. data/lib/sober_swag/reporting/compiler.rb +39 -0
  22. data/lib/sober_swag/reporting/input/base.rb +11 -0
  23. data/lib/sober_swag/reporting/input/bool.rb +19 -0
  24. data/lib/sober_swag/reporting/input/converting/bool.rb +24 -0
  25. data/lib/sober_swag/reporting/input/converting/date.rb +30 -0
  26. data/lib/sober_swag/reporting/input/converting/date_time.rb +28 -0
  27. data/lib/sober_swag/reporting/input/converting/decimal.rb +24 -0
  28. data/lib/sober_swag/reporting/input/converting/integer.rb +19 -0
  29. data/lib/sober_swag/reporting/input/converting.rb +16 -0
  30. data/lib/sober_swag/reporting/input/defer.rb +29 -0
  31. data/lib/sober_swag/reporting/input/described.rb +38 -0
  32. data/lib/sober_swag/reporting/input/dictionary.rb +37 -0
  33. data/lib/sober_swag/reporting/input/either.rb +51 -0
  34. data/lib/sober_swag/reporting/input/enum.rb +44 -0
  35. data/lib/sober_swag/reporting/input/format.rb +39 -0
  36. data/lib/sober_swag/reporting/input/interface.rb +87 -0
  37. data/lib/sober_swag/reporting/input/list.rb +44 -0
  38. data/lib/sober_swag/reporting/input/mapped.rb +36 -0
  39. data/lib/sober_swag/reporting/input/merge_objects.rb +72 -0
  40. data/lib/sober_swag/reporting/input/null.rb +34 -0
  41. data/lib/sober_swag/reporting/input/number.rb +19 -0
  42. data/lib/sober_swag/reporting/input/object/property.rb +53 -0
  43. data/lib/sober_swag/reporting/input/object.rb +100 -0
  44. data/lib/sober_swag/reporting/input/pattern.rb +46 -0
  45. data/lib/sober_swag/reporting/input/referenced.rb +38 -0
  46. data/lib/sober_swag/reporting/input/struct.rb +271 -0
  47. data/lib/sober_swag/reporting/input/text.rb +42 -0
  48. data/lib/sober_swag/reporting/input.rb +54 -0
  49. data/lib/sober_swag/reporting/invalid_schema_error.rb +21 -0
  50. data/lib/sober_swag/reporting/output/base.rb +25 -0
  51. data/lib/sober_swag/reporting/output/bool.rb +25 -0
  52. data/lib/sober_swag/reporting/output/defer.rb +69 -0
  53. data/lib/sober_swag/reporting/output/described.rb +42 -0
  54. data/lib/sober_swag/reporting/output/dictionary.rb +46 -0
  55. data/lib/sober_swag/reporting/output/interface.rb +83 -0
  56. data/lib/sober_swag/reporting/output/list.rb +54 -0
  57. data/lib/sober_swag/reporting/output/merge_objects.rb +97 -0
  58. data/lib/sober_swag/reporting/output/null.rb +25 -0
  59. data/lib/sober_swag/reporting/output/number.rb +25 -0
  60. data/lib/sober_swag/reporting/output/object/property.rb +45 -0
  61. data/lib/sober_swag/reporting/output/object.rb +54 -0
  62. data/lib/sober_swag/reporting/output/partitioned.rb +77 -0
  63. data/lib/sober_swag/reporting/output/pattern.rb +50 -0
  64. data/lib/sober_swag/reporting/output/referenced.rb +42 -0
  65. data/lib/sober_swag/reporting/output/struct.rb +262 -0
  66. data/lib/sober_swag/reporting/output/text.rb +25 -0
  67. data/lib/sober_swag/reporting/output/via_map.rb +67 -0
  68. data/lib/sober_swag/reporting/output/viewed.rb +72 -0
  69. data/lib/sober_swag/reporting/output.rb +54 -0
  70. data/lib/sober_swag/reporting/report/base.rb +57 -0
  71. data/lib/sober_swag/reporting/report/either.rb +36 -0
  72. data/lib/sober_swag/reporting/report/error.rb +15 -0
  73. data/lib/sober_swag/reporting/report/list.rb +28 -0
  74. data/lib/sober_swag/reporting/report/merged_object.rb +25 -0
  75. data/lib/sober_swag/reporting/report/object.rb +29 -0
  76. data/lib/sober_swag/reporting/report/output.rb +14 -0
  77. data/lib/sober_swag/reporting/report/value.rb +28 -0
  78. data/lib/sober_swag/reporting/report.rb +16 -0
  79. data/lib/sober_swag/reporting.rb +11 -0
  80. data/lib/sober_swag/version.rb +1 -1
  81. data/lib/sober_swag.rb +1 -0
  82. metadata +65 -2
@@ -0,0 +1,53 @@
1
+ module SoberSwag
2
+ module Reporting
3
+ module Input
4
+ class Object
5
+ ##
6
+ # Describe a single property key in an object.
7
+ class Property
8
+ def initialize(value, required:, description: '')
9
+ @value = value
10
+ @required = required
11
+ @description = description
12
+ end
13
+
14
+ ##
15
+ # @return [SoberSwag::Reporting::Input::Interface] value type
16
+ attr_reader :value
17
+
18
+ def required?
19
+ @required
20
+ end
21
+
22
+ ##
23
+ # @return [String, nil] description
24
+ attr_reader :description
25
+
26
+ def property_schema
27
+ direct, refined = value.swagger_schema
28
+
29
+ if description
30
+ [add_description(direct), refined]
31
+ else
32
+ [direct, refined]
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def add_description(dir)
39
+ t =
40
+ if dir.key?(:$ref)
41
+ # workaround: we have to do this if we want to allow
42
+ # descriptions in reference types
43
+ { allOf: [dir] }
44
+ else
45
+ dir
46
+ end
47
+ t.merge(description: description)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,100 @@
1
+ module SoberSwag
2
+ module Reporting
3
+ module Input
4
+ ##
5
+ # Input object values
6
+ class Object < Base
7
+ autoload :Property, 'sober_swag/reporting/input/object/property'
8
+ ##
9
+ # @param fields [Hash<Symbol, Property>]
10
+ def initialize(fields)
11
+ @fields = fields
12
+ end
13
+
14
+ ##
15
+ # @return [Hash<String,#call>]
16
+ attr_reader :fields
17
+
18
+ def call(value)
19
+ return Report::Value.new(['was a not a JSON object']) unless value.is_a?(Hash)
20
+
21
+ bad, good = fields.map { |k, prop|
22
+ extract_value(k, prop, value)
23
+ }.compact.partition { |(_, v)| v.is_a?(Report::Base) }
24
+
25
+ return Report::Object.new(bad.to_h) if bad.any?
26
+
27
+ good.to_h
28
+ end
29
+
30
+ def swagger_schema
31
+ fields, found = field_schemas
32
+
33
+ obj = {
34
+ type: 'object',
35
+ properties: fields
36
+ }.merge(required_portion)
37
+
38
+ [obj, found]
39
+ end
40
+
41
+ def swagger_query_schema
42
+ swagger_parameter_schema.map do |param|
43
+ param.merge({ in: :query, style: :deepObject, explode: true })
44
+ end
45
+ end
46
+
47
+ def swagger_path_schema
48
+ swagger_parameter_schema.map do |param|
49
+ param.merge({ in: :path })
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def swagger_parameter_schema
56
+ fields.map do |name, field|
57
+ key_schema, = field.property_schema
58
+ base = {
59
+ name: name,
60
+ schema: key_schema,
61
+ required: field.required?
62
+ }
63
+ field.description ? base.merge(description: field.description) : base
64
+ end
65
+ end
66
+
67
+ def field_schemas
68
+ fields.reduce([{}, Set.new]) do |(field_schemas, found), (k, v)|
69
+ key_schema, key_found = v.property_schema
70
+ [
71
+ field_schemas.merge(k => key_schema),
72
+ found.merge(key_found)
73
+ ]
74
+ end
75
+ end
76
+
77
+ ##
78
+ # Either the list of required keys, or something stating "provide at least one key."
79
+ # This is needed because you can't have an empty list of keys.
80
+ def required_portion
81
+ required_fields = fields.map { |k, v| k if v.required? }.compact
82
+
83
+ if required_fields.empty?
84
+ {}
85
+ else
86
+ { required: required_fields }
87
+ end
88
+ end
89
+
90
+ def extract_value(key, property, input)
91
+ if input.key?(key)
92
+ [key, property.value.call(input[key])]
93
+ elsif property.required?
94
+ [key, Report::Value.new(['is required'])]
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,46 @@
1
+ module SoberSwag
2
+ module Reporting
3
+ module Input
4
+ ##
5
+ # Input values that validate against a pattern
6
+ class Pattern < Base
7
+ def initialize(input, pattern)
8
+ @input = input
9
+ @pattern = pattern
10
+ end
11
+
12
+ ##
13
+ # @return [#call] input type
14
+ attr_reader :input
15
+
16
+ ##
17
+ # @return [#matches] regexp matcher
18
+ attr_reader :pattern
19
+
20
+ def call(value)
21
+ val = input.call(value)
22
+
23
+ return val if val.is_a?(Report::Base)
24
+
25
+ if pattern.match?(value)
26
+ value
27
+ else
28
+ Report::Value.new(["did not match pattern #{pattern}"])
29
+ end
30
+ end
31
+
32
+ def swagger_schema
33
+ single, found = input.swagger_schema
34
+
35
+ [add_schema_key(single, { pattern: formatted_pattern }), found]
36
+ end
37
+
38
+ ##
39
+ # Try to format a pattern so it'll work nicely with JS.
40
+ def formatted_pattern
41
+ pattern.to_s.gsub('?-mix:', '')
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,38 @@
1
+ module SoberSwag
2
+ module Reporting
3
+ module Input
4
+ ##
5
+ # An input that should be "referenced" in the final schema.
6
+ class Referenced < Base
7
+ def initialize(value, reference)
8
+ @value = value
9
+ @reference = reference
10
+ end
11
+
12
+ ##
13
+ # @return [Interface] the actual input
14
+ attr_reader :value
15
+ ##
16
+ # @return [String] key in the components hash
17
+ attr_reader :reference
18
+
19
+ def call(input)
20
+ @value.call(input)
21
+ end
22
+
23
+ def swagger_schema
24
+ [
25
+ { "$ref": ref_path },
26
+ { reference => proc { value.swagger_schema } }
27
+ ]
28
+ end
29
+
30
+ private
31
+
32
+ def ref_path
33
+ "#/components/schemas/#{reference}"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,271 @@
1
+ module SoberSwag
2
+ module Reporting
3
+ module Input
4
+ ##
5
+ # Base class of input structs.
6
+ #
7
+ # These allow you to define both an input type and a ruby type at once.
8
+ # They provide a fluid interface for doing so.
9
+ #
10
+ # Classes which inherit from {Struct} "quack like" an {Interface}, so you can use them as input type definitions.
11
+ #
12
+ # You should add attributes using the {.attribute} or {.attribute?} methods.
13
+ # These also let you nest definitions, so this is okay:
14
+ #
15
+ # ```ruby
16
+ # class Person < SoberSwag::Reporting::Input::Struct
17
+ # attribute :first_name, SoberSwag::Reporting::Input.text
18
+ # attribute :stats do
19
+ # attribute :average_score, SoberSwag::Reporting::Input.number
20
+ # end
21
+ # end
22
+ # ```
23
+ class Struct # rubocop:disable Metrics/ClassLength
24
+ class << self
25
+ ##
26
+ # @overload attribute(name, input, description: nil)
27
+ # Define a new attribute, which will be required.
28
+ # @param name [Symbol] the name of this attribute
29
+ # @param input [Interface] input reporting type
30
+ # @param description [String,nil] description for this attribute
31
+ # @overload attribute(name, description: nil, &block)
32
+ # Define a new nested attribute, which will be required, using a block to describe
33
+ # a sub-struct. This block will immediately be evaluated to create a child struct.
34
+ # @param name [Symbol] the name of the attribute.
35
+ # The sub-struct defined will be stored in a constant on this class,
36
+ # under this name, classified.
37
+ #
38
+ # So if the name is :first_name, then the constant will be FirstName
39
+ # @param description [String, nil] describe this attribute
40
+ # @yieldself [SoberSwag::Reporting::Input::Struct] yields
41
+ def attribute(name, input = nil, description: nil, &block)
42
+ input_type = make_input_type(name, input, block)
43
+ add_attribute!(name, input_type, required: true, description: description)
44
+ end
45
+
46
+ ##
47
+ # @overload attribute?(name, input, description: nil)
48
+ # Define a new attribute, which will be not required.
49
+ # @param name [Symbol] the name of this attribute
50
+ # @param input [Interface] input reporting type
51
+ # @param description [String,nil] description for this attribute
52
+ # @overload attribute?(name, description: nil, &block)
53
+ # Define a new nested attribute, which will not be required, using a block to describe
54
+ # a sub-struct. This block will immediately be evaluated to create a child struct.
55
+ # @param name [Symbol] the name of the attribute.
56
+ # The sub-struct defined will be stored in a constant on this class,
57
+ # under this name, classified.
58
+ #
59
+ # So if the name is :first_name, then the constant will be FirstName
60
+ # @param description [String, nil] describe this attribute
61
+ # @yieldself [SoberSwag::Reporting::Input::Struct] yields
62
+ def attribute?(name, input = nil, description: nil, &block)
63
+ input_type = make_input_type(name, input, block)
64
+
65
+ add_attribute!(name, input_type, required: false, description: description)
66
+ end
67
+
68
+ ##
69
+ # Add an attribute, specifying if it is required or not via an argument.
70
+ # You should use {#attribute} or {#attribute?} instead of this almost always.
71
+ #
72
+ # @param name [Symbol] name of this attribute
73
+ # @param input [Interface] type fot this attribue
74
+ # @param required [true,false] if this attribute is required
75
+ # @param description [String,nil] optional description for this attribute
76
+ #
77
+ def add_attribute!(name, input, required:, description: nil)
78
+ raise ArgumentError, 'name must be a symbol' unless name.is_a?(Symbol)
79
+
80
+ define_attribute(name) # defines an instance method to access this attribute
81
+
82
+ object_properties[name] = Object::Property.new(
83
+ input,
84
+ required: required,
85
+ description: description
86
+ )
87
+ end
88
+
89
+ ##
90
+ # Get a list of properties defined by *this instance*.
91
+ #
92
+ # Please do not mutate this, it will break everything.
93
+ #
94
+ # @return [Hash<Symbol, Object::Property>]
95
+ def object_properties
96
+ @object_properties ||= {}
97
+ end
98
+
99
+ ##
100
+ # @return [SoberSwag::Reporting::Input::Struct,nil] the struct we inherit from.
101
+ # Used to implement `allOf` style inheritance.
102
+ attr_accessor :parent_struct
103
+
104
+ ##
105
+ # @param other [Class] the inheriting class
106
+ #
107
+ # Used to implement `allOf` style inheritance by setting {#parent_struct} on the object that is inheriting from us.
108
+ def inherited(other)
109
+ other.parent_struct = self unless self == SoberSwag::Reporting::Input::Struct
110
+ end
111
+
112
+ include Interface
113
+
114
+ ##
115
+ # @return [SoberSwag::Reporting::Input::Base] the type to use for input.
116
+ def input_type
117
+ object_type.mapped { |x| new(x) }.referenced(identifier)
118
+ end
119
+
120
+ ##
121
+ # @overload identifier()
122
+ # @return [String,nil] the identifier for this object, used for its reference path.
123
+ # @overload identifier(val)
124
+ # Sets an identifier for this struct.
125
+ # @param val [String] the identifier to set
126
+ # @return [String] the set identifier.
127
+ def identifier(val = nil)
128
+ if val
129
+ @identifier = val
130
+ else
131
+ @identifier || name&.gsub('::', '.')
132
+ end
133
+ end
134
+
135
+ ##
136
+ # @return [SoberSwag::Reporting::Input::Struct, SoberSwag::Reporting::Report::Base] the struct class,
137
+ # or a report of what went wrong.
138
+ def call(attrs)
139
+ input_type.call(attrs)
140
+ end
141
+
142
+ ##
143
+ # @see #call
144
+ def parse(json)
145
+ call(json)
146
+ end
147
+
148
+ ##
149
+ # @see call!
150
+ def parse!(json)
151
+ call!(json)
152
+ end
153
+
154
+ ##
155
+ # @return [Array[Hash, Hash]] swagger schema type.
156
+ def swagger_schema
157
+ input_type.swagger_schema
158
+ end
159
+
160
+ def swagger_query_schema
161
+ object_type.swagger_query_schema
162
+ end
163
+
164
+ def swagger_path_schema
165
+ object_type.swagger_path_schema
166
+ end
167
+
168
+ private
169
+
170
+ def make_input_type(name, input, block)
171
+ raise ArgumentError, 'cannot pass a block to make a sub-struct and a field type' if input && block
172
+
173
+ return input if input
174
+
175
+ raise ArgumentError, 'must pass an input type OR a block to make a sub-struct' unless block
176
+
177
+ const_name = name.to_s.camelize
178
+
179
+ raise ArgumentError, 'cannot define struct sub-type, constant already exists!' if const_defined?(const_name)
180
+
181
+ Class.new(SoberSwag::Reporting::Input::Struct, &block).tap { |c| const_set(const_name, c) }
182
+ end
183
+
184
+ ##
185
+ # Quick method which defines an accessor method for this struct.
186
+ def define_attribute(name)
187
+ define_method(name) do
188
+ struct_properties[name]
189
+ end
190
+ define_method("#{name}_present?") do
191
+ struct_properties.key?(name)
192
+ end
193
+ end
194
+
195
+ def object_type
196
+ if parent_struct.nil?
197
+ Object.new(object_properties)
198
+ else
199
+ MergeObjects.new(parent_struct, Object.new(object_properties))
200
+ end
201
+ end
202
+ end
203
+
204
+ def initialize(props)
205
+ @struct_properties = props
206
+ end
207
+
208
+ attr_reader :struct_properties
209
+
210
+ def [](name)
211
+ @struct_properties[name]
212
+ end
213
+
214
+ ##
215
+ # Hash code for this struct.
216
+ def hash
217
+ [self.class.hash, *ordered_values.hash].hash
218
+ end
219
+
220
+ ##
221
+ # Return an array of the values of this, in order.
222
+ def ordered_values
223
+ self.class.object_properties.keys.map { |k| @struct_properties[k] }
224
+ end
225
+
226
+ ##
227
+ # Allow structs to be compared like values.
228
+ def eql?(other)
229
+ return false unless other.is_a?(self.class)
230
+
231
+ ordered_values.eql?(other.ordered_values)
232
+ end
233
+
234
+ ##
235
+ # Allow structs to be ordered like values.
236
+ def <=>(other)
237
+ return nil unless other.is_a?(self.class)
238
+
239
+ ordered_values <=> other.ordered_values
240
+ end
241
+
242
+ include Comparable
243
+
244
+ ##
245
+ # Extracts the transformed struct properties.
246
+ #
247
+ # Keys not present in the input will also not be present in this hash.
248
+ def to_h
249
+ @struct_properties.transform_values do |value|
250
+ if value.is_a?(SoberSwag::Reporting::Input::Struct)
251
+ value.to_h
252
+ else
253
+ value
254
+ end
255
+ end
256
+ end
257
+
258
+ def to_s
259
+ inspect
260
+ end
261
+
262
+ def inspect
263
+ keys = self.class.object_properties.keys.each.with_object([]) do |k, obj|
264
+ obj << "#{k}=#{public_send(k).inspect}" if public_send(:"#{k}_present?")
265
+ end
266
+ "#<#{self.class.name || self.class.inspect[2..-2]} #{keys.join(' ')}>"
267
+ end
268
+ end
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,42 @@
1
+ module SoberSwag
2
+ module Reporting
3
+ module Input
4
+ ##
5
+ # Input for a single text value.
6
+ class Text < Base
7
+ def call(value)
8
+ return value if value.is_a?(String)
9
+
10
+ Report::Value.new(['was not a string'])
11
+ end
12
+
13
+ ##
14
+ # Get a new input value which requires a regexp.
15
+ #
16
+ # @paran regexp [Regexp] regular expression to match on
17
+ # @return [Pattern] pattern-based input.
18
+ def with_pattern(regexp)
19
+ Pattern.new(self, regexp)
20
+ end
21
+
22
+ include Comparable
23
+
24
+ def eql?(other)
25
+ other.class == self.class
26
+ end
27
+
28
+ def <=>(other)
29
+ eql?(other) ? 0 : 1
30
+ end
31
+
32
+ def hash
33
+ [self.class.hash, 1].hash
34
+ end
35
+
36
+ def swagger_schema
37
+ [{ type: 'string' }, {}]
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,54 @@
1
+ module SoberSwag
2
+ module Reporting
3
+ ##
4
+ # Module for SoberSwag reporting inputs.
5
+ module Input
6
+ autoload :Base, 'sober_swag/reporting/input/base'
7
+ autoload :Bool, 'sober_swag/reporting/input/bool'
8
+ autoload :Converting, 'sober_swag/reporting/input/converting'
9
+ autoload :Described, 'sober_swag/reporting/input/described'
10
+ autoload :Dictionary, 'sober_swag/reporting/input/dictionary'
11
+ autoload :Defer, 'sober_swag/reporting/input/defer'
12
+ autoload :Enum, 'sober_swag/reporting/input/enum'
13
+ autoload :Either, 'sober_swag/reporting/input/either'
14
+ autoload :Format, 'sober_swag/reporting/input/format'
15
+ autoload :Number, 'sober_swag/reporting/input/number'
16
+ autoload :Interface, 'sober_swag/reporting/input/interface'
17
+ autoload :List, 'sober_swag/reporting/input/list'
18
+ autoload :Mapped, 'sober_swag/reporting/input/mapped'
19
+ autoload :MergeObjects, 'sober_swag/reporting/input/merge_objects'
20
+ autoload :Null, 'sober_swag/reporting/input/null'
21
+ autoload :Object, 'sober_swag/reporting/input/object'
22
+ autoload :Pattern, 'sober_swag/reporting/input/pattern'
23
+ autoload :Referenced, 'sober_swag/reporting/input/referenced'
24
+ autoload :Struct, 'sober_swag/reporting/input/struct'
25
+ autoload :Text, 'sober_swag/reporting/input/text'
26
+
27
+ class << self
28
+ ##
29
+ # @return [SoberSwag::Reporting::Input::Bool]
30
+ def bool
31
+ Bool.new
32
+ end
33
+
34
+ ##
35
+ # @return [SoberSwag::Reporting::Input::Text]
36
+ def text
37
+ Text.new
38
+ end
39
+
40
+ ##
41
+ # @return [SoberSwag::Reporting::Input::Number]
42
+ def number
43
+ Number.new
44
+ end
45
+
46
+ ##
47
+ # @return [SoberSwag::Reporting::Input::Null]
48
+ def null
49
+ Null.new
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,21 @@
1
+ module SoberSwag
2
+ module Reporting
3
+ ##
4
+ # Thrown we cannot generate a swagger schema for some reason.
5
+ #
6
+ # This typically only occurs if you use types that are too complicated.
7
+ # For example, an object type cannot be used as part of the path params.
8
+ class InvalidSchemaError < StandardError
9
+ def initialize(input)
10
+ @input = input
11
+
12
+ super("Could not generate schema for #{input}")
13
+ end
14
+
15
+ attr_reader :input
16
+
17
+ class InvalidForPathError < InvalidSchemaError; end
18
+ class InvalidForQueryError < InvalidSchemaError; end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ module SoberSwag
2
+ module Reporting
3
+ module Output
4
+ ##
5
+ # Base type for simple outputs.
6
+ class Base
7
+ include Interface
8
+
9
+ ##
10
+ # Acceptable views to use with this output.
11
+ #
12
+ # @return [Set<Symbol>] the views
13
+ def views
14
+ %i[base].to_set
15
+ end
16
+
17
+ def view(view_key)
18
+ return self if view_key == :base
19
+
20
+ raise ArgumentError, "#{view_key} is not a view" unless views.include?(view_key)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ module SoberSwag
2
+ module Reporting
3
+ module Output
4
+ ##
5
+ # Output booleans.
6
+ class Bool < Base
7
+ def call(input)
8
+ input
9
+ end
10
+
11
+ def serialize_report(input)
12
+ result = call(input)
13
+
14
+ return Report::Value.new(['was not a boolean']) unless [true, false].include?(result)
15
+
16
+ result
17
+ end
18
+
19
+ def swagger_schema
20
+ [{ type: 'boolean' }, {}]
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end