sober_swag 0.21.0 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) 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 +104 -113
  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/input_object.rb +1 -0
  22. data/lib/sober_swag/output_object/field_syntax.rb +2 -0
  23. data/lib/sober_swag/reporting/compiler.rb +39 -0
  24. data/lib/sober_swag/reporting/input/base.rb +11 -0
  25. data/lib/sober_swag/reporting/input/bool.rb +19 -0
  26. data/lib/sober_swag/reporting/input/converting/bool.rb +24 -0
  27. data/lib/sober_swag/reporting/input/converting/date.rb +30 -0
  28. data/lib/sober_swag/reporting/input/converting/date_time.rb +28 -0
  29. data/lib/sober_swag/reporting/input/converting/decimal.rb +24 -0
  30. data/lib/sober_swag/reporting/input/converting/integer.rb +19 -0
  31. data/lib/sober_swag/reporting/input/converting.rb +16 -0
  32. data/lib/sober_swag/reporting/input/defer.rb +29 -0
  33. data/lib/sober_swag/reporting/input/described.rb +38 -0
  34. data/lib/sober_swag/reporting/input/dictionary.rb +37 -0
  35. data/lib/sober_swag/reporting/input/either.rb +51 -0
  36. data/lib/sober_swag/reporting/input/enum.rb +44 -0
  37. data/lib/sober_swag/reporting/input/format.rb +39 -0
  38. data/lib/sober_swag/reporting/input/in_range.rb +61 -0
  39. data/lib/sober_swag/reporting/input/interface.rb +113 -0
  40. data/lib/sober_swag/reporting/input/list.rb +44 -0
  41. data/lib/sober_swag/reporting/input/mapped.rb +36 -0
  42. data/lib/sober_swag/reporting/input/merge_objects.rb +72 -0
  43. data/lib/sober_swag/reporting/input/multiple_of.rb +36 -0
  44. data/lib/sober_swag/reporting/input/null.rb +34 -0
  45. data/lib/sober_swag/reporting/input/number.rb +19 -0
  46. data/lib/sober_swag/reporting/input/object/property.rb +53 -0
  47. data/lib/sober_swag/reporting/input/object.rb +100 -0
  48. data/lib/sober_swag/reporting/input/pattern.rb +46 -0
  49. data/lib/sober_swag/reporting/input/referenced.rb +38 -0
  50. data/lib/sober_swag/reporting/input/struct.rb +272 -0
  51. data/lib/sober_swag/reporting/input/text.rb +42 -0
  52. data/lib/sober_swag/reporting/input.rb +56 -0
  53. data/lib/sober_swag/reporting/invalid_schema_error.rb +21 -0
  54. data/lib/sober_swag/reporting/output/base.rb +25 -0
  55. data/lib/sober_swag/reporting/output/bool.rb +25 -0
  56. data/lib/sober_swag/reporting/output/defer.rb +69 -0
  57. data/lib/sober_swag/reporting/output/described.rb +42 -0
  58. data/lib/sober_swag/reporting/output/dictionary.rb +46 -0
  59. data/lib/sober_swag/reporting/output/enum.rb +47 -0
  60. data/lib/sober_swag/reporting/output/in_range.rb +64 -0
  61. data/lib/sober_swag/reporting/output/interface.rb +98 -0
  62. data/lib/sober_swag/reporting/output/list.rb +54 -0
  63. data/lib/sober_swag/reporting/output/merge_objects.rb +97 -0
  64. data/lib/sober_swag/reporting/output/null.rb +25 -0
  65. data/lib/sober_swag/reporting/output/number.rb +25 -0
  66. data/lib/sober_swag/reporting/output/object/property.rb +45 -0
  67. data/lib/sober_swag/reporting/output/object.rb +54 -0
  68. data/lib/sober_swag/reporting/output/partitioned.rb +77 -0
  69. data/lib/sober_swag/reporting/output/pattern.rb +50 -0
  70. data/lib/sober_swag/reporting/output/referenced.rb +42 -0
  71. data/lib/sober_swag/reporting/output/struct.rb +287 -0
  72. data/lib/sober_swag/reporting/output/text.rb +25 -0
  73. data/lib/sober_swag/reporting/output/via_map.rb +67 -0
  74. data/lib/sober_swag/reporting/output/viewed.rb +72 -0
  75. data/lib/sober_swag/reporting/output.rb +56 -0
  76. data/lib/sober_swag/reporting/report/base.rb +57 -0
  77. data/lib/sober_swag/reporting/report/either.rb +36 -0
  78. data/lib/sober_swag/reporting/report/error.rb +15 -0
  79. data/lib/sober_swag/reporting/report/list.rb +28 -0
  80. data/lib/sober_swag/reporting/report/merged_object.rb +25 -0
  81. data/lib/sober_swag/reporting/report/object.rb +29 -0
  82. data/lib/sober_swag/reporting/report/output.rb +14 -0
  83. data/lib/sober_swag/reporting/report/value.rb +28 -0
  84. data/lib/sober_swag/reporting/report.rb +16 -0
  85. data/lib/sober_swag/reporting.rb +11 -0
  86. data/lib/sober_swag/version.rb +1 -1
  87. data/lib/sober_swag.rb +1 -0
  88. metadata +69 -2
@@ -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
@@ -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,47 @@
1
+ module SoberSwag
2
+ module Reporting
3
+ module Output
4
+ ##
5
+ # Models outputting an enum.
6
+ class Enum < Base
7
+ def initialize(output, values)
8
+ @output = output
9
+ @values = values
10
+ end
11
+
12
+ ##
13
+ # @return [Interface]
14
+ attr_reader :output
15
+
16
+ ##
17
+ # @return [Array]
18
+ attr_reader :values
19
+
20
+ def call(value)
21
+ output.call(value)
22
+ end
23
+
24
+ def serialize_report(value)
25
+ rep = output.serialize_report(value)
26
+
27
+ return rep if rep.is_a?(Report::Base)
28
+
29
+ return Report::Value.new(['was not an acceptable enum member']) unless values.include?(rep)
30
+
31
+ rep
32
+ end
33
+
34
+ def swagger_schema
35
+ schema, found = output.swagger_schema
36
+ merged =
37
+ if schema.key?(:$ref)
38
+ { allOf: [schema] }
39
+ else
40
+ schema
41
+ end.merge(enum: values)
42
+ [merged, found]
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,64 @@
1
+ module SoberSwag
2
+ module Reporting
3
+ module Output
4
+ ##
5
+ # Specify that an output will be within a certain range.
6
+ # This gets translated to `minimum` and `maximum` keys in swagger.
7
+ class InRange < Base
8
+ def initialize(output, range)
9
+ @output = output
10
+ @range = range
11
+ end
12
+
13
+ ##
14
+ # @return [Interface]
15
+ attr_reader :output
16
+
17
+ ##
18
+ # @return [Range]
19
+ attr_reader :range
20
+
21
+ def call(value)
22
+ output.call(value)
23
+ end
24
+
25
+ def serialize_report(value)
26
+ rep = output.serialize_report(value)
27
+
28
+ return rep if rep.is_a?(Report::Base)
29
+
30
+ return Report::Value.new(['was not in minimum/maximum range']) unless range.member?(rep)
31
+
32
+ rep
33
+ end
34
+
35
+ def swagger_schema
36
+ schema, found = output.swagger_schema
37
+
38
+ merged =
39
+ if schema.key?(:$ref)
40
+ { allOf: [schema] }
41
+ else
42
+ schema
43
+ end.merge(maximum_portion).merge(minimum_portion)
44
+
45
+ [merged, found]
46
+ end
47
+
48
+ def maximum_portion
49
+ return {} unless range.end
50
+
51
+ res = { maximum: range.end }
52
+ res[:exclusiveMaximum] = true if range.exclude_end?
53
+ res
54
+ end
55
+
56
+ def minimum_portion
57
+ return {} unless range.begin
58
+
59
+ { minimum: range.begin }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,98 @@
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
+ ##
34
+ # @return [SoberSwag::Reporting::Output::Enum]
35
+ def enum(*cases)
36
+ Enum.new(self, cases)
37
+ end
38
+
39
+ def referenced(name)
40
+ Referenced.new(self, name)
41
+ end
42
+
43
+ ##
44
+ # @return [SoberSwag::Reporting::Output::InRange]
45
+ # Constrained values: must be within the given range.
46
+ def in_range(range)
47
+ raise ArgumentError, 'need a range' unless range.is_a?(Range)
48
+
49
+ InRange.new(self, range)
50
+ end
51
+
52
+ def list
53
+ List.new(self)
54
+ end
55
+
56
+ ##
57
+ # Partition this serializer into two potentials.
58
+ # If the block given returns *false*, we will use `other` as the serializer.
59
+ # Otherwise, we will use `self`.
60
+ #
61
+ # This might be useful to serialize a sum type:
62
+ #
63
+ # ```ruby
64
+ # ResolutionOutput = TransferOutput.partitioned(RefundOutput) { |to_serialize| to_serialize.is_a?(Transfer)
65
+ # ```
66
+ #
67
+ # @param other [Interface] serializer to use if the block returns false
68
+ # @yieldreturn [true,false] false if we should use the other serializer
69
+ # @return [Interface]
70
+ def partitioned(other, &block)
71
+ raise ArgumentError, 'need a block' if block.nil?
72
+
73
+ Partitioned.new(
74
+ block,
75
+ self,
76
+ other
77
+ )
78
+ end
79
+
80
+ def nilable
81
+ Partitioned.new(
82
+ :nil?.to_proc,
83
+ Null.new,
84
+ self
85
+ )
86
+ end
87
+
88
+ def array
89
+ List.new(self)
90
+ end
91
+
92
+ def described(description)
93
+ Described.new(self, description)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ 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