sober_swag 0.15.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/dependabot.yml +15 -0
- data/.github/workflows/lint.yml +4 -9
- data/.github/workflows/ruby.yml +2 -6
- data/.gitignore +4 -0
- data/.rubocop.yml +50 -5
- data/.yardopts +7 -0
- data/CHANGELOG.md +29 -1
- data/Gemfile +8 -0
- data/README.md +155 -4
- data/bin/rspec +29 -0
- data/docs/serializers.md +18 -13
- data/example/Gemfile +2 -2
- data/example/app/controllers/people_controller.rb +4 -0
- data/example/app/controllers/posts_controller.rb +5 -0
- 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 +49 -3
- data/lib/sober_swag/compiler/paths.rb +20 -0
- data/lib/sober_swag/compiler/primitive.rb +20 -1
- data/lib/sober_swag/compiler/type.rb +105 -22
- data/lib/sober_swag/controller.rb +42 -15
- data/lib/sober_swag/controller/route.rb +133 -28
- data/lib/sober_swag/input_object.rb +117 -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 +27 -11
- 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
- data/sober_swag.gemspec +2 -2
- metadata +13 -10
@@ -10,19 +10,49 @@ module SoberSwag
|
|
10
10
|
# This is a very weird, not-very-Ruby-like abstraction, *upon which* we can build abstractions that are actually use for users.
|
11
11
|
# It lets you build abstractions like "Use this serializer if a type has this class, otherwise use this other one."
|
12
12
|
# When composed together, you can make arbitrary decision trees.
|
13
|
+
#
|
14
|
+
# This class is heavily inspired by
|
15
|
+
# the [Decideable](https://hackage.haskell.org/package/contravariant-1.5.3/docs/Data-Functor-Contravariant-Divisible.html#t:Decidable)
|
16
|
+
# typeclass from Haskell.
|
13
17
|
class Conditional < Base
|
14
18
|
##
|
15
19
|
# Error thrown when a chooser proc returns a non left-or-right value.
|
16
20
|
class BadChoiceError < Error; end
|
17
21
|
|
22
|
+
##
|
23
|
+
# Create a new conditional serializer, from a "chooser" proc, a "left" serializer, and a "right" serializer.
|
24
|
+
#
|
25
|
+
# @param chooser [Proc,Lambda] the proc that chooses which "side" to use
|
26
|
+
# @param left [SoberSwag::Serializer::Base] a serializer for the "left" side
|
27
|
+
# @param right [SoberSwag::Serializer::Base] a serializer for the "right" side
|
18
28
|
def initialize(chooser, left, right)
|
19
29
|
@chooser = chooser
|
20
30
|
@left = left
|
21
31
|
@right = right
|
22
32
|
end
|
23
33
|
|
24
|
-
|
34
|
+
##
|
35
|
+
# @return [Proc,Lambda] the "chooser" proc.
|
36
|
+
attr_reader :chooser
|
37
|
+
|
38
|
+
##
|
39
|
+
# @return [SoberSwag::Serializer::Base] the serializer to use if the "chooser" proc chooses `:right`.
|
40
|
+
# Also called the "left-side serializer."
|
41
|
+
attr_reader :left
|
25
42
|
|
43
|
+
##
|
44
|
+
# @return [SoberSwag::Serializer::Base] the serializer to use if the "chooser" proc chooses `:right`.
|
45
|
+
# Also called the "right-side serializer."
|
46
|
+
attr_reader :right
|
47
|
+
|
48
|
+
##
|
49
|
+
# First, call {#chooser} with `object` and `options` to see what serializer to use, and *what* to serialize.
|
50
|
+
# Then, if it returns `[:left, val]`, use {#left} to serialize `val`.
|
51
|
+
# Otherwise, if it returns `[:right, val]`, use {#right} to serialize `val`.
|
52
|
+
# If it returns neither, throw {BadChoiceError}.
|
53
|
+
#
|
54
|
+
# @raise [BadChoiceError] if {#chooser} did not choose what side to use
|
55
|
+
# @return [Hash] a JSON-compatible object
|
26
56
|
def serialize(object, options = {})
|
27
57
|
tag, val = chooser.call(object, options)
|
28
58
|
case tag
|
@@ -58,6 +88,8 @@ module SoberSwag
|
|
58
88
|
left.lazy_type? || right.lazy_type?
|
59
89
|
end
|
60
90
|
|
91
|
+
##
|
92
|
+
# Finalize both {#left} and {#right}
|
61
93
|
def finalize_lazy_type!
|
62
94
|
[left, right].each(&:finalize_lazy_type!)
|
63
95
|
end
|
@@ -1,13 +1,18 @@
|
|
1
1
|
module SoberSwag
|
2
2
|
module Serializer
|
3
3
|
##
|
4
|
-
#
|
5
|
-
# name/serializer pairs.
|
4
|
+
# Extracts a JSON hash from a list of {SoberSwag::OutputObject::Field} structs.
|
6
5
|
class FieldList < Base
|
6
|
+
##
|
7
|
+
# Create a new field-list serializer.
|
8
|
+
#
|
9
|
+
# @param field_list [Array<SoberSwag::OutputObject::Field>] descriptions of each field
|
7
10
|
def initialize(field_list)
|
8
11
|
@field_list = field_list
|
9
12
|
end
|
10
13
|
|
14
|
+
##
|
15
|
+
# @return [Array<SoberSwag::OutputObject::Field>] the list of fields to use.
|
11
16
|
attr_reader :field_list
|
12
17
|
|
13
18
|
##
|
@@ -16,16 +21,27 @@ module SoberSwag
|
|
16
21
|
SoberSwag::Serializer.Primitive(SoberSwag::Types.const_get(symbol))
|
17
22
|
end
|
18
23
|
|
24
|
+
##
|
25
|
+
# Serialize an object to a JSON hash by using each field in the list.
|
26
|
+
# @param object [Object] object to serialize
|
27
|
+
# @param options [Hash] arbitrary options
|
28
|
+
# @return [Hash] serialized object.
|
19
29
|
def serialize(object, options = {})
|
20
30
|
field_list.map { |field|
|
21
31
|
[field.name, field.serializer.serialize(object, options)]
|
22
32
|
}.to_h
|
23
33
|
end
|
24
34
|
|
35
|
+
##
|
36
|
+
# Construct a Dry::Struct from the fields given.
|
37
|
+
# This Struct will be swagger-able.
|
38
|
+
# @return [Dry::Struct]
|
25
39
|
def type
|
26
40
|
@type ||= make_struct_type!
|
27
41
|
end
|
28
42
|
|
43
|
+
##
|
44
|
+
# These types are always constructed lazily.
|
29
45
|
def lazy_type?
|
30
46
|
true
|
31
47
|
end
|
@@ -3,12 +3,21 @@ module SoberSwag
|
|
3
3
|
##
|
4
4
|
# A new serializer by mapping over the serialization function
|
5
5
|
class Mapped < Base
|
6
|
+
##
|
7
|
+
# Create a new mapped serializer.
|
8
|
+
# @param base [SoberSwag::Serializer::Base] a serializer to use after mapping
|
9
|
+
# @param map_f [Proc,Lambda] a mapping function to use before serialization
|
6
10
|
def initialize(base, map_f)
|
7
11
|
@base = base
|
8
12
|
@map_f = map_f
|
9
13
|
end
|
10
14
|
|
11
|
-
|
15
|
+
##
|
16
|
+
# @return [SoberSwag::Serializer::Base] serializer to use after mapping
|
17
|
+
attr_reader :base
|
18
|
+
##
|
19
|
+
# @return [Proc, Lambda, #call] function to use before serialization
|
20
|
+
attr_reader :map_f
|
12
21
|
|
13
22
|
def serialize(object, options = {})
|
14
23
|
@base.serialize(@map_f.call(object), options)
|
@@ -5,11 +5,20 @@ module SoberSwag
|
|
5
5
|
# this can be used to make a serializer of type 'A | nil'.
|
6
6
|
#
|
7
7
|
# Or, put another way, makes serializers not crash on nil values.
|
8
|
+
# If {#serialize} is passed nil, it will return `nil` immediately, and not
|
9
|
+
# try to call the serializer of {#inner}.
|
8
10
|
class Optional < Base
|
11
|
+
##
|
12
|
+
# An error thrown when trying to nest optional serializers.
|
13
|
+
class NestedOptionalError < Error; end
|
14
|
+
##
|
15
|
+
# @param inner [SoberSwag::Serializer::Base] the serializer to use for non-nil values
|
9
16
|
def initialize(inner)
|
10
17
|
@inner = inner
|
11
18
|
end
|
12
19
|
|
20
|
+
##
|
21
|
+
# @return [SoberSwag::Serializer::Base] the serializer to use for non-nil values.
|
13
22
|
attr_reader :inner
|
14
23
|
|
15
24
|
def lazy_type?
|
@@ -24,6 +33,9 @@ module SoberSwag
|
|
24
33
|
@inner.finalize_lazy_type!
|
25
34
|
end
|
26
35
|
|
36
|
+
##
|
37
|
+
# If `object` is nil, return `nil`.
|
38
|
+
# Otherwise, call `inner.serialize(object, options)`.
|
27
39
|
def serialize(object, options = {})
|
28
40
|
if object.nil?
|
29
41
|
object
|
@@ -36,8 +48,13 @@ module SoberSwag
|
|
36
48
|
inner.type.optional
|
37
49
|
end
|
38
50
|
|
51
|
+
##
|
52
|
+
# Since nesting optional types is bad, this will always raise an ArgumentError
|
53
|
+
#
|
54
|
+
# @raise [NestedOptionalError] always
|
55
|
+
# @return [void] nothing, always raises.
|
39
56
|
def optional(*)
|
40
|
-
raise
|
57
|
+
raise NestedOptionalError, 'no nesting optionals please'
|
41
58
|
end
|
42
59
|
end
|
43
60
|
end
|
@@ -4,6 +4,9 @@ module SoberSwag
|
|
4
4
|
# A class that does *no* serialization: you give it a type,
|
5
5
|
# and it will pass any serialized input on verbatim.
|
6
6
|
class Primitive < Base
|
7
|
+
##
|
8
|
+
# Construct a primitive serializer with a description of the type it serializes to.
|
9
|
+
# @param type [Class] a swagger-able type
|
7
10
|
def initialize(type)
|
8
11
|
@type = type
|
9
12
|
end
|
data/lib/sober_swag/server.rb
CHANGED
@@ -21,42 +21,58 @@ module SoberSwag
|
|
21
21
|
# Start up.
|
22
22
|
#
|
23
23
|
# @param controller_proc [Proc] a proc that, when called, gives a list of {SoberSwag::Controller}s to document
|
24
|
-
# @param cache [Bool | Proc] if we should cache our
|
24
|
+
# @param cache [Bool | Proc] if we should cache our definitions (default false)
|
25
|
+
# @param redoc_version [String] what version of the redoc library to use to display UI (default 'next', the latest version).
|
25
26
|
def initialize(
|
26
27
|
controller_proc: RAILS_CONTROLLER_PROC,
|
27
|
-
cache: false
|
28
|
+
cache: false,
|
29
|
+
redoc_version: 'next'
|
28
30
|
)
|
29
31
|
@controller_proc = controller_proc
|
30
32
|
@cache = cache
|
33
|
+
@html = EFFECT_HTML.gsub(/REDOC_VERSION/, redoc_version)
|
31
34
|
end
|
32
35
|
|
33
36
|
EFFECT_HTML = <<~HTML.freeze
|
34
37
|
<!DOCTYPE html>
|
35
38
|
<html>
|
36
39
|
<head>
|
37
|
-
<title>
|
38
|
-
|
39
|
-
<
|
40
|
+
<title>ReDoc</title>
|
41
|
+
<!-- needed for adaptive design -->
|
42
|
+
<meta charset="utf-8"/>
|
43
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
44
|
+
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
45
|
+
|
46
|
+
<!--
|
47
|
+
ReDoc doesn't change outer page styles
|
48
|
+
-->
|
49
|
+
<style>
|
50
|
+
body {
|
51
|
+
margin: 0;
|
52
|
+
padding: 0;
|
53
|
+
}
|
54
|
+
</style>
|
40
55
|
</head>
|
41
56
|
<body>
|
42
|
-
<
|
43
|
-
</
|
44
|
-
<script>
|
45
|
-
SwaggerUIBundle({url: 'SCRIPT_NAME', dom_id: '#swagger'})
|
46
|
-
</script>
|
57
|
+
<redoc spec-url='SCRIPT_NAME'></redoc>
|
58
|
+
<script src="https://cdn.jsdelivr.net/npm/redoc@REDOC_VERSION/bundles/redoc.standalone.js"> </script>
|
47
59
|
</body>
|
48
60
|
</html>
|
49
61
|
HTML
|
50
62
|
|
63
|
+
##
|
64
|
+
# Standard Rack call method.
|
51
65
|
def call(env)
|
52
66
|
req = Rack::Request.new(env)
|
53
67
|
if req.path_info&.match?(/json/si) || req.get_header('Accept')&.match?(/json/si)
|
54
68
|
[200, { 'Content-Type' => 'application/json' }, [generate_json_string]]
|
55
69
|
else
|
56
|
-
[200, { 'Content-Type' => 'text/html' }, [
|
70
|
+
[200, { 'Content-Type' => 'text/html' }, [@html.gsub(/SCRIPT_NAME/, "#{env['SCRIPT_NAME']}.json")]]
|
57
71
|
end
|
58
72
|
end
|
59
73
|
|
74
|
+
private
|
75
|
+
|
60
76
|
def generate_json_string
|
61
77
|
if cache?
|
62
78
|
@json_string ||= JSON.dump(generate_swagger)
|
@@ -9,24 +9,38 @@ module SoberSwag
|
|
9
9
|
# Modules that include {SoberSwag::Type::Named}
|
10
10
|
# will automatically extend this module.
|
11
11
|
module ClassMethods
|
12
|
+
##
|
13
|
+
# Is this type a "wrapper" for another type?
|
12
14
|
def alias?
|
13
15
|
false
|
14
16
|
end
|
15
17
|
|
18
|
+
##
|
19
|
+
# The type this type is a wrapper for
|
16
20
|
def alias_of
|
17
21
|
nil
|
18
22
|
end
|
19
23
|
|
24
|
+
##
|
25
|
+
# The "root" type along the alias chain
|
20
26
|
def root_alias
|
21
27
|
alias_of || self
|
22
28
|
end
|
23
29
|
|
30
|
+
##
|
31
|
+
# @overload description()
|
32
|
+
# @return [String] a human-readable description of this type
|
33
|
+
# @overload description(arg)
|
34
|
+
# @param arg [String] a human-readable description of this type
|
35
|
+
# @return [String] `arg`
|
24
36
|
def description(arg = nil)
|
25
37
|
@description = arg if arg
|
26
38
|
@description
|
27
39
|
end
|
28
40
|
end
|
29
41
|
|
42
|
+
##
|
43
|
+
# When included, extends {SoberSwag::Type::Named::ClassMethods}
|
30
44
|
def self.included(mod)
|
31
45
|
mod.extend(ClassMethods)
|
32
46
|
end
|
@@ -3,6 +3,10 @@ module SoberSwag
|
|
3
3
|
##
|
4
4
|
# An array that will be parsed from comma-separated values in a string, if given a string.
|
5
5
|
module CommaArray
|
6
|
+
##
|
7
|
+
# Get a parser that will parse comma-separated values of another type.
|
8
|
+
# @param other [Class] a swagger-able type to parse into
|
9
|
+
# @return [SoberSwag::Types::CommaArray]
|
6
10
|
def self.of(other)
|
7
11
|
SoberSwag::Types::Array.of(other).constructor { |val|
|
8
12
|
if val.is_a?(::String)
|
data/lib/sober_swag/version.rb
CHANGED
data/sober_swag.gemspec
CHANGED
@@ -39,7 +39,7 @@ Gem::Specification.new do |spec|
|
|
39
39
|
spec.add_development_dependency 'pry-byebug'
|
40
40
|
spec.add_development_dependency 'rake', '~> 13.0'
|
41
41
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
42
|
-
spec.add_development_dependency 'rubocop'
|
43
|
-
spec.add_development_dependency 'rubocop-rspec'
|
42
|
+
spec.add_development_dependency 'rubocop', '~> 0.93.1'
|
43
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.44.1'
|
44
44
|
spec.add_development_dependency 'simplecov'
|
45
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sober_swag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.20.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anthony Super
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -126,30 +126,30 @@ dependencies:
|
|
126
126
|
name: rubocop
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- - "
|
129
|
+
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version:
|
131
|
+
version: 0.93.1
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- - "
|
136
|
+
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version:
|
138
|
+
version: 0.93.1
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: rubocop-rspec
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
|
-
- - "
|
143
|
+
- - "~>"
|
144
144
|
- !ruby/object:Gem::Version
|
145
|
-
version:
|
145
|
+
version: 1.44.1
|
146
146
|
type: :development
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
|
-
- - "
|
150
|
+
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
|
-
version:
|
152
|
+
version: 1.44.1
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
154
|
name: simplecov
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -172,6 +172,7 @@ extensions: []
|
|
172
172
|
extra_rdoc_files: []
|
173
173
|
files:
|
174
174
|
- ".github/config/rubocop_linter_action.yml"
|
175
|
+
- ".github/dependabot.yml"
|
175
176
|
- ".github/workflows/lint.yml"
|
176
177
|
- ".github/workflows/ruby.yml"
|
177
178
|
- ".gitignore"
|
@@ -179,12 +180,14 @@ files:
|
|
179
180
|
- ".rubocop.yml"
|
180
181
|
- ".ruby-version"
|
181
182
|
- ".travis.yml"
|
183
|
+
- ".yardopts"
|
182
184
|
- CHANGELOG.md
|
183
185
|
- Gemfile
|
184
186
|
- LICENSE.txt
|
185
187
|
- README.md
|
186
188
|
- Rakefile
|
187
189
|
- bin/console
|
190
|
+
- bin/rspec
|
188
191
|
- bin/setup
|
189
192
|
- docs/serializers.md
|
190
193
|
- example/.gitignore
|