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,69 @@
|
|
1
|
+
module SoberSwag
|
2
|
+
module Reporting
|
3
|
+
module Output
|
4
|
+
##
|
5
|
+
# Defer loading of an output for mutual recursion and/or loading time speed.
|
6
|
+
# Probably just do this for mutual recursion though.
|
7
|
+
#
|
8
|
+
# Note: this *does not* save you from infinite schema generation.
|
9
|
+
# This type *must* return some sort of {Referenced} type in order to do that!
|
10
|
+
#
|
11
|
+
# The common use case for this is mutual recursion.
|
12
|
+
# Something like...
|
13
|
+
#
|
14
|
+
# ```ruby
|
15
|
+
# class PersonOutput < SoberSwag::Reporting::Output::Struct
|
16
|
+
# field :first_name, SoberSwag::Reporting::Output.text
|
17
|
+
# view :detail do
|
18
|
+
# field :classes, SoberSwag::Reporting::Output::Defer.new { ClassroomOutput.view(:base).array }
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# class ClassroomOutut < SoberSwag::Reporting::Output::Struct
|
23
|
+
# field :class_name, SoberSwag::Reporting::Output.text
|
24
|
+
#
|
25
|
+
# view :detail do
|
26
|
+
# field :students, SoberSwag::Reporting::Output::Defer.new { PersonOutput.view(:base).array }
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
# ```
|
30
|
+
class Defer < Base
|
31
|
+
##
|
32
|
+
# Nicer initialization: uses a block.
|
33
|
+
#
|
34
|
+
# @yieldreturn [Interface] serializer to use.
|
35
|
+
def self.defer(&block)
|
36
|
+
new(block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(other_lazy)
|
40
|
+
@other_lazy = other_lazy
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :other_lazy
|
44
|
+
|
45
|
+
##
|
46
|
+
# @return [Interface]
|
47
|
+
def other
|
48
|
+
@other ||= other_lazy.call
|
49
|
+
end
|
50
|
+
|
51
|
+
def call(input)
|
52
|
+
other.call(input)
|
53
|
+
end
|
54
|
+
|
55
|
+
def serialize_report(input)
|
56
|
+
other.serialize_report(input)
|
57
|
+
end
|
58
|
+
|
59
|
+
def view(view)
|
60
|
+
other.view(view)
|
61
|
+
end
|
62
|
+
|
63
|
+
def swagger_schema
|
64
|
+
other.swagger_schema
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module SoberSwag
|
2
|
+
module Reporting
|
3
|
+
module Output
|
4
|
+
##
|
5
|
+
# Add a description onto an object.
|
6
|
+
class Described < Base
|
7
|
+
def initialize(output, description)
|
8
|
+
@output = output
|
9
|
+
@description = description
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# @return [Interface] output to describe
|
14
|
+
attr_reader :output
|
15
|
+
|
16
|
+
##
|
17
|
+
# @return [String] description of output
|
18
|
+
attr_reader :description
|
19
|
+
|
20
|
+
def call(value)
|
21
|
+
output.call(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def serialize_report(value)
|
25
|
+
output.serialize_report(value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def swagger_schema
|
29
|
+
schema, found = output.swagger_schema
|
30
|
+
|
31
|
+
merged =
|
32
|
+
if schema.key?(:$ref)
|
33
|
+
{ allOf: [schema] }
|
34
|
+
else
|
35
|
+
schema
|
36
|
+
end.merge(description: description)
|
37
|
+
[merged, found]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module SoberSwag
|
2
|
+
module Reporting
|
3
|
+
module Output
|
4
|
+
##
|
5
|
+
# Output a dictionary of key-value pairs.
|
6
|
+
class Dictionary < Base
|
7
|
+
def self.of(valout)
|
8
|
+
new(valout)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(value_output)
|
12
|
+
@value_output = value_output
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :value_output
|
16
|
+
|
17
|
+
def call(item)
|
18
|
+
item.transform_values { |v| value_output.call(v) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def serialize_report(item)
|
22
|
+
return Report::Base.new(['was not a dict']) unless item.is_a?(Hash)
|
23
|
+
|
24
|
+
bad, good = item.map { |k, v|
|
25
|
+
[k, value_output.serialize_report(v)]
|
26
|
+
}.compact.partition { |(_, v)| v.is_a?(Report::Base) }
|
27
|
+
|
28
|
+
return Report::Object.new(bad.to_h) if bad.any?
|
29
|
+
|
30
|
+
good.to_h
|
31
|
+
end
|
32
|
+
|
33
|
+
def swagger_schema
|
34
|
+
schema, found = value_output.swagger_schema
|
35
|
+
[
|
36
|
+
{
|
37
|
+
type: :object,
|
38
|
+
additionalProperties: schema
|
39
|
+
},
|
40
|
+
found
|
41
|
+
]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module SoberSwag
|
2
|
+
module Reporting
|
3
|
+
module Output
|
4
|
+
##
|
5
|
+
# Interface methods for all outputs.
|
6
|
+
module Interface
|
7
|
+
def call!(item)
|
8
|
+
res = serialize_report(item)
|
9
|
+
|
10
|
+
raise Report::Error.new(res) if res.is_a?(Report::Base) # rubocop:disable Style/RaiseArgs
|
11
|
+
|
12
|
+
res
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Show off that this is a reporting output.
|
17
|
+
def reporting?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Delegates to {#call}
|
23
|
+
def serialize(item)
|
24
|
+
call(item)
|
25
|
+
end
|
26
|
+
|
27
|
+
def via_map(&block)
|
28
|
+
raise ArgumentError, 'block argument required' unless block
|
29
|
+
|
30
|
+
ViaMap.new(self, block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def referenced(name)
|
34
|
+
Referenced.new(self, name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def list
|
38
|
+
List.new(self)
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Partition this serializer into two potentials.
|
43
|
+
# If the block given returns *false*, we will use `other` as the serializer.
|
44
|
+
# Otherwise, we will use `self`.
|
45
|
+
#
|
46
|
+
# This might be useful to serialize a sum type:
|
47
|
+
#
|
48
|
+
# ```ruby
|
49
|
+
# ResolutionOutput = TransferOutput.partitioned(RefundOutput) { |to_serialize| to_serialize.is_a?(Transfer)
|
50
|
+
# ```
|
51
|
+
#
|
52
|
+
# @param other [Interface] serializer to use if the block returns false
|
53
|
+
# @yieldreturn [true,false] false if we should use the other serializer
|
54
|
+
# @return [Interface]
|
55
|
+
def partitioned(other, &block)
|
56
|
+
raise ArgumentError, 'need a block' if block.nil?
|
57
|
+
|
58
|
+
Partitioned.new(
|
59
|
+
block,
|
60
|
+
self,
|
61
|
+
other
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def nilable
|
66
|
+
Partitioned.new(
|
67
|
+
:nil?.to_proc,
|
68
|
+
Null.new,
|
69
|
+
self
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def array
|
74
|
+
List.new(self)
|
75
|
+
end
|
76
|
+
|
77
|
+
def described(description)
|
78
|
+
Described.new(self, description)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module SoberSwag
|
2
|
+
module Reporting
|
3
|
+
module Output
|
4
|
+
##
|
5
|
+
# Serialize a list of some other output type.
|
6
|
+
# Passes views down.
|
7
|
+
class List < Base
|
8
|
+
def initialize(element_output)
|
9
|
+
@element_output = element_output
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :element_output
|
13
|
+
|
14
|
+
def view(view)
|
15
|
+
List.new(element_output.view(view))
|
16
|
+
end
|
17
|
+
|
18
|
+
def views
|
19
|
+
element_output.views
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(input)
|
23
|
+
input.map { |i| element_output.call(i) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def swagger_schema
|
27
|
+
schema, found = element_output.swagger_schema
|
28
|
+
[
|
29
|
+
{
|
30
|
+
type: 'array',
|
31
|
+
items: schema
|
32
|
+
},
|
33
|
+
found
|
34
|
+
]
|
35
|
+
end
|
36
|
+
|
37
|
+
def serialize_report(input)
|
38
|
+
return Report::Value.new(['could not be made an array']) unless input.respond_to?(:map)
|
39
|
+
|
40
|
+
errs = {}
|
41
|
+
mapped = input.map.with_index do |item, idx|
|
42
|
+
element_output.serialize_report(item).tap { |e| errs[idx] = e if e.is_a?(Report::Base) }
|
43
|
+
end
|
44
|
+
|
45
|
+
if errs.any?
|
46
|
+
Report::List.new(errs)
|
47
|
+
else
|
48
|
+
mapped
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module SoberSwag
|
2
|
+
module Reporting
|
3
|
+
module Output
|
4
|
+
##
|
5
|
+
# Represents object that are marged with `allOf` in swagger.
|
6
|
+
#
|
7
|
+
# These *have* to be objects, due to how `allOf` works.
|
8
|
+
# This expresses a subtyping relationship.
|
9
|
+
#
|
10
|
+
# Note: non-careful use of this can generate impossible objects,
|
11
|
+
# IE, objects where a certain field has to be *both* a string and an integer or something.
|
12
|
+
# Subtyping is dangerous and should be used with care!
|
13
|
+
#
|
14
|
+
# This class is used in the implementation of {SoberSwag::Reporting::Output::Struct},
|
15
|
+
# in order to model the inheritence relationship structs have.
|
16
|
+
class MergeObjects < Base
|
17
|
+
##
|
18
|
+
# @param parent [Interface] parent interface to use.
|
19
|
+
# Should certainly be some sort of object, or a reference to it.
|
20
|
+
# @param child [Interface] child interface to use.
|
21
|
+
# Should certainly be some sort of object, or a reference to it.
|
22
|
+
def initialize(parent, child)
|
23
|
+
@parent = parent
|
24
|
+
@child = child
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# @return [Interface] first object to merge
|
29
|
+
attr_reader :parent
|
30
|
+
##
|
31
|
+
# @return [Interface] second object to merge
|
32
|
+
attr_reader :child
|
33
|
+
|
34
|
+
##
|
35
|
+
# Serialize with the parent first, then merge in the child.
|
36
|
+
# This *does* mean that parent keys override child keys.
|
37
|
+
#
|
38
|
+
# If `parent` or `child` does not serialize some sort of object, this will result in an error.
|
39
|
+
def call(input)
|
40
|
+
parent.call(input).merge(child.call(input))
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Child views.
|
45
|
+
def views
|
46
|
+
child.views
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Passes on view to the *child object*.
|
51
|
+
def view(view)
|
52
|
+
MergeObjects.new(parent, child.view(view))
|
53
|
+
end
|
54
|
+
|
55
|
+
def serialize_report(value)
|
56
|
+
parent_attrs = parent.serialize_report(value)
|
57
|
+
|
58
|
+
return parent_attrs if parent_attrs.is_a?(Report::Value)
|
59
|
+
|
60
|
+
child_attrs = child.serialize_report(value)
|
61
|
+
|
62
|
+
return child_attrs if child_attrs.is_a?(Report::Value)
|
63
|
+
|
64
|
+
merge_results(parent_attrs, child_attrs)
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Swagger schema.
|
69
|
+
#
|
70
|
+
# This will collapse 'allOf' keys, so a chain of parent methods will be
|
71
|
+
def swagger_schema # rubocop:disable Metrics/MethodLength
|
72
|
+
found = {}
|
73
|
+
mapped = [parent, child].flat_map do |i|
|
74
|
+
schema, item_found = i.swagger_schema
|
75
|
+
found.merge!(item_found)
|
76
|
+
if schema.key?(:allOf)
|
77
|
+
schema[:allOf]
|
78
|
+
else
|
79
|
+
[schema]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
[{ allOf: mapped }, found]
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def merge_results(par, chi)
|
88
|
+
return Report::MergedObject.new(par, chi) if [par, chi].all? { |c| c.is_a?(Report::Base) }
|
89
|
+
return par if par.is_a?(Report::Base)
|
90
|
+
return chi if chi.is_a?(Report::Base)
|
91
|
+
|
92
|
+
par.to_h.merge(chi.to_h)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SoberSwag
|
2
|
+
module Reporting
|
3
|
+
module Output
|
4
|
+
##
|
5
|
+
# Output JSON nulls.
|
6
|
+
class Null < 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 null']) unless result.nil?
|
15
|
+
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
def swagger_schema
|
20
|
+
[{ type: 'null' }, {}]
|
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 numbers of some variety.
|
6
|
+
class Number < 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 number']) unless result.is_a?(Numeric)
|
15
|
+
|
16
|
+
result
|
17
|
+
end
|
18
|
+
|
19
|
+
def swagger_schema
|
20
|
+
[{ type: 'number' }, {}]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module SoberSwag
|
2
|
+
module Reporting
|
3
|
+
module Output
|
4
|
+
class Object
|
5
|
+
##
|
6
|
+
# Definitions for a specific property of an object.
|
7
|
+
class Property
|
8
|
+
def initialize(output, description: nil)
|
9
|
+
@output = output
|
10
|
+
@description = description
|
11
|
+
end
|
12
|
+
##
|
13
|
+
# @return [Interface]
|
14
|
+
attr_reader :output
|
15
|
+
|
16
|
+
##
|
17
|
+
# @return [String,nil]
|
18
|
+
attr_reader :description
|
19
|
+
|
20
|
+
def call(item, view: :base)
|
21
|
+
output.call(item, view: view)
|
22
|
+
end
|
23
|
+
|
24
|
+
def property_schema
|
25
|
+
direct, refined = output.swagger_schema
|
26
|
+
|
27
|
+
if description
|
28
|
+
[add_description(direct), refined]
|
29
|
+
else
|
30
|
+
[direct, refined]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_description(dir)
|
35
|
+
if dir.key?(:$ref)
|
36
|
+
{ allOf: [dir] }
|
37
|
+
else
|
38
|
+
dir
|
39
|
+
end.merge(description: description)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module SoberSwag
|
2
|
+
module Reporting
|
3
|
+
module Output
|
4
|
+
##
|
5
|
+
# Serialize out a JSON object.
|
6
|
+
class Object < Base
|
7
|
+
autoload(:Property, 'sober_swag/reporting/output/object/property')
|
8
|
+
|
9
|
+
##
|
10
|
+
# @param properties [Hash<Symbol,Property>] the properties to serialize
|
11
|
+
def initialize(properties)
|
12
|
+
@properties = properties
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# @param properties [Hash<Symbol,Property>]
|
17
|
+
attr_reader :properties
|
18
|
+
|
19
|
+
def call(item)
|
20
|
+
properties.each.with_object({}) do |(k, v), hash|
|
21
|
+
hash[k] = v.output.call(item)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def serialize_report(item)
|
26
|
+
bad, good = properties.map { |k, prop|
|
27
|
+
[k, prop.output.serialize_report(item)]
|
28
|
+
}.partition { |(_, v)| v.is_a?(Report::Base) }
|
29
|
+
|
30
|
+
return Report::Object.new(bad.to_h) if bad.any?
|
31
|
+
|
32
|
+
good.to_h
|
33
|
+
end
|
34
|
+
|
35
|
+
def swagger_schema # rubocop:disable Metrics/MethodLength
|
36
|
+
props, found = properties.each.with_object([{}, {}]) do |(k, v), (field, f)|
|
37
|
+
prop_type, prop_found = v.property_schema
|
38
|
+
field[k] = prop_type
|
39
|
+
f.merge!(prop_found)
|
40
|
+
end
|
41
|
+
|
42
|
+
[
|
43
|
+
{
|
44
|
+
type: 'object',
|
45
|
+
properties: props,
|
46
|
+
required: properties.keys
|
47
|
+
},
|
48
|
+
found
|
49
|
+
]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module SoberSwag
|
2
|
+
module Reporting
|
3
|
+
module Output
|
4
|
+
##
|
5
|
+
# Partition output into one of two possible cases.
|
6
|
+
# We use a block to decide if we should use the first or the second.
|
7
|
+
# If the block returns a truthy value, we use the first output.
|
8
|
+
# If it returns a falsy value, we use the second.
|
9
|
+
#
|
10
|
+
# This is useful to serialize sum types, or types where it can be EITHER one thing OR another.
|
11
|
+
# IE, if I can resolve a dispute by EITHER transfering money OR refunding a customer, I can do this:
|
12
|
+
#
|
13
|
+
# ```ruby
|
14
|
+
# ResolutionOutput = SoberSwag::Reporting::Output.new(
|
15
|
+
# proc { |x| x.is_a?(Transfer) },
|
16
|
+
# TransferOutput,
|
17
|
+
# RefundOutput
|
18
|
+
# )
|
19
|
+
# ```
|
20
|
+
class Partitioned < Base
|
21
|
+
##
|
22
|
+
# @param partition [#call] block that returns true or false for the input type
|
23
|
+
# @param true_output [Interface] serializer to use if block is true
|
24
|
+
# @param false_output [Interface] serializer to use if block is false
|
25
|
+
def initialize(partition, true_output, false_output)
|
26
|
+
@partition = partition
|
27
|
+
@true_output = true_output
|
28
|
+
@false_output = false_output
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# @return [#call] partitioning block
|
33
|
+
attr_reader :partition
|
34
|
+
|
35
|
+
##
|
36
|
+
# @return [Interface]
|
37
|
+
attr_reader :true_output
|
38
|
+
|
39
|
+
##
|
40
|
+
# @return [Interface]
|
41
|
+
attr_reader :false_output
|
42
|
+
|
43
|
+
def call(item)
|
44
|
+
serializer_for(item).call(item)
|
45
|
+
end
|
46
|
+
|
47
|
+
def serialize_report(item)
|
48
|
+
serializer_for(item).serialize_report(item)
|
49
|
+
end
|
50
|
+
|
51
|
+
def swagger_schema
|
52
|
+
true_schema, true_found = true_output.swagger_schema
|
53
|
+
false_schema, false_found = false_output.swagger_schema
|
54
|
+
|
55
|
+
[
|
56
|
+
{
|
57
|
+
oneOf: (true_schema[:oneOf] || [true_schema]) + (false_schema[:oneOf] || [false_schema])
|
58
|
+
},
|
59
|
+
true_found.merge(false_found)
|
60
|
+
]
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
##
|
66
|
+
# @return [Interface]
|
67
|
+
def serializer_for(item)
|
68
|
+
if partition.call(item)
|
69
|
+
true_output
|
70
|
+
else
|
71
|
+
false_output
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module SoberSwag
|
2
|
+
module Reporting
|
3
|
+
module Output
|
4
|
+
##
|
5
|
+
# Output with a particular pattern.
|
6
|
+
class Pattern < Base
|
7
|
+
def initialize(output, pattern)
|
8
|
+
@output = output
|
9
|
+
@pattern = pattern
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# @return [Interface]
|
14
|
+
attr_reader :output
|
15
|
+
|
16
|
+
##
|
17
|
+
# @return [Regexp]
|
18
|
+
attr_reader :pattern
|
19
|
+
|
20
|
+
def call(input)
|
21
|
+
output.call(input)
|
22
|
+
end
|
23
|
+
|
24
|
+
def serialize_report(value)
|
25
|
+
base = output.serialize_report(value)
|
26
|
+
|
27
|
+
return base if base.is_a?(Report::Error)
|
28
|
+
|
29
|
+
if pattern.match?(base)
|
30
|
+
base
|
31
|
+
else
|
32
|
+
Report::Value.new(['did not match pattern'])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def swagger_schema
|
37
|
+
schema, defs = output.swagger_schema
|
38
|
+
|
39
|
+
merged =
|
40
|
+
if schema.key?(:$ref)
|
41
|
+
{ oneOf: [schema] }
|
42
|
+
else
|
43
|
+
schema
|
44
|
+
end.merge(pattern: pattern.to_s.gsub('?-mix:', ''))
|
45
|
+
[merged, defs]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|