sober_swag 0.21.0 → 0.22.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/.rubocop.yml +1 -0
- data/CHANGELOG.md +5 -0
- data/bin/console +30 -10
- data/docs/reporting.md +190 -0
- data/example/Gemfile +2 -2
- data/example/Gemfile.lock +92 -101
- data/example/app/controllers/application_controller.rb +4 -0
- data/example/app/controllers/people_controller.rb +44 -28
- data/example/app/output_objects/identified_output.rb +7 -0
- data/example/app/output_objects/person_output_object.rb +37 -11
- data/example/app/output_objects/post_output_object.rb +0 -4
- data/example/app/output_objects/reporting_post_output.rb +18 -0
- data/example/bin/rspec +29 -0
- data/example/spec/requests/people/create_spec.rb +3 -2
- data/example/spec/requests/people/index_spec.rb +1 -1
- data/lib/sober_swag/compiler/path.rb +3 -1
- data/lib/sober_swag/compiler.rb +58 -12
- data/lib/sober_swag/controller/route.rb +44 -8
- data/lib/sober_swag/controller.rb +18 -5
- data/lib/sober_swag/reporting/compiler.rb +39 -0
- data/lib/sober_swag/reporting/input/base.rb +11 -0
- data/lib/sober_swag/reporting/input/bool.rb +19 -0
- data/lib/sober_swag/reporting/input/converting/bool.rb +24 -0
- data/lib/sober_swag/reporting/input/converting/date.rb +30 -0
- data/lib/sober_swag/reporting/input/converting/date_time.rb +28 -0
- data/lib/sober_swag/reporting/input/converting/decimal.rb +24 -0
- data/lib/sober_swag/reporting/input/converting/integer.rb +19 -0
- data/lib/sober_swag/reporting/input/converting.rb +16 -0
- data/lib/sober_swag/reporting/input/defer.rb +29 -0
- data/lib/sober_swag/reporting/input/described.rb +38 -0
- data/lib/sober_swag/reporting/input/dictionary.rb +37 -0
- data/lib/sober_swag/reporting/input/either.rb +51 -0
- data/lib/sober_swag/reporting/input/enum.rb +44 -0
- data/lib/sober_swag/reporting/input/format.rb +39 -0
- data/lib/sober_swag/reporting/input/interface.rb +87 -0
- data/lib/sober_swag/reporting/input/list.rb +44 -0
- data/lib/sober_swag/reporting/input/mapped.rb +36 -0
- data/lib/sober_swag/reporting/input/merge_objects.rb +72 -0
- data/lib/sober_swag/reporting/input/null.rb +34 -0
- data/lib/sober_swag/reporting/input/number.rb +19 -0
- data/lib/sober_swag/reporting/input/object/property.rb +53 -0
- data/lib/sober_swag/reporting/input/object.rb +100 -0
- data/lib/sober_swag/reporting/input/pattern.rb +46 -0
- data/lib/sober_swag/reporting/input/referenced.rb +38 -0
- data/lib/sober_swag/reporting/input/struct.rb +271 -0
- data/lib/sober_swag/reporting/input/text.rb +42 -0
- data/lib/sober_swag/reporting/input.rb +54 -0
- data/lib/sober_swag/reporting/invalid_schema_error.rb +21 -0
- data/lib/sober_swag/reporting/output/base.rb +25 -0
- data/lib/sober_swag/reporting/output/bool.rb +25 -0
- data/lib/sober_swag/reporting/output/defer.rb +69 -0
- data/lib/sober_swag/reporting/output/described.rb +42 -0
- data/lib/sober_swag/reporting/output/dictionary.rb +46 -0
- data/lib/sober_swag/reporting/output/interface.rb +83 -0
- data/lib/sober_swag/reporting/output/list.rb +54 -0
- data/lib/sober_swag/reporting/output/merge_objects.rb +97 -0
- data/lib/sober_swag/reporting/output/null.rb +25 -0
- data/lib/sober_swag/reporting/output/number.rb +25 -0
- data/lib/sober_swag/reporting/output/object/property.rb +45 -0
- data/lib/sober_swag/reporting/output/object.rb +54 -0
- data/lib/sober_swag/reporting/output/partitioned.rb +77 -0
- data/lib/sober_swag/reporting/output/pattern.rb +50 -0
- data/lib/sober_swag/reporting/output/referenced.rb +42 -0
- data/lib/sober_swag/reporting/output/struct.rb +262 -0
- data/lib/sober_swag/reporting/output/text.rb +25 -0
- data/lib/sober_swag/reporting/output/via_map.rb +67 -0
- data/lib/sober_swag/reporting/output/viewed.rb +72 -0
- data/lib/sober_swag/reporting/output.rb +54 -0
- data/lib/sober_swag/reporting/report/base.rb +57 -0
- data/lib/sober_swag/reporting/report/either.rb +36 -0
- data/lib/sober_swag/reporting/report/error.rb +15 -0
- data/lib/sober_swag/reporting/report/list.rb +28 -0
- data/lib/sober_swag/reporting/report/merged_object.rb +25 -0
- data/lib/sober_swag/reporting/report/object.rb +29 -0
- data/lib/sober_swag/reporting/report/output.rb +14 -0
- data/lib/sober_swag/reporting/report/value.rb +28 -0
- data/lib/sober_swag/reporting/report.rb +16 -0
- data/lib/sober_swag/reporting.rb +11 -0
- data/lib/sober_swag/version.rb +1 -1
- data/lib/sober_swag.rb +1 -0
- 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
|