sober_swag 0.2.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb2b4cc3cb2635010c4c3c0950e6ce87272046bf2e4cdbf08cf2e299518444b0
4
- data.tar.gz: c407225c2c1c5abfadc3e074e5aba98ffb929dc47dda16f5f32293a73669c444
3
+ metadata.gz: a005a30f258a97e03e6a8c69429a3bf3d8f35477ffe0dbe7ba4afb030e36986d
4
+ data.tar.gz: 0d9aaed05874fec05ebccbe4b987a9c0d46eaeb3fefe9633b34340a28f956c75
5
5
  SHA512:
6
- metadata.gz: 3dbeeaec1ac6280e3029d779b31490a1661e436e87f833b4e5dce4c3eef96031e18988148aa48ebd1d849fe85353e93c1308f64a013c25d03424ad5b81041c8e
7
- data.tar.gz: 51d0d80744d371cc51a2ba5497a9e8942d26a3e96a97625069476b12b612562fff00a0e371441c7851662e2f181a5ff9e33f0d4dcaecb92c233d6a8c4c07f66f
6
+ metadata.gz: 10659925df20f3eeab802f8c8648c5f589a509d247d9017076893f67501dc4ba1f97e1c3f21958dcedc1e1aff950d8375e74825f038e1e360c984e01a4656c61
7
+ data.tar.gz: be74588c605a26165363e88ab95dbfcb4a83cb4d169a224544b74389c69049a769bcde8fb14e6338f7fac92419a317fd9a2332352e7a420be95060c20523e666
@@ -2,4 +2,3 @@ check_name: 'Rubocop Lint'
2
2
  versions:
3
3
  rubocop: 'latest'
4
4
  rubocop-rspec: 'latest'
5
-
@@ -17,6 +17,9 @@ jobs:
17
17
  test:
18
18
 
19
19
  runs-on: ubuntu-latest
20
+ strategy:
21
+ matrix:
22
+ ruby: [ '2.6', '2.7' ]
20
23
 
21
24
  steps:
22
25
  - uses: actions/checkout@v2
@@ -26,25 +29,31 @@ jobs:
26
29
  # uses: ruby/setup-ruby@v1
27
30
  uses: ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0
28
31
  with:
29
- ruby-version: 2.7
32
+ ruby-version: ${{ matrix.ruby }}
30
33
  - uses: actions/cache@v2
31
34
  with:
32
35
  path: vendor/bundle
33
- key: ${{ runner.os }}-gem-deps-${{ hashFiles('**/Gemfile.lock') }}
36
+ key: ${{ runner.os }}-${{ matrix.ruby }}-gem-deps-${{ hashFiles('**/Gemfile.lock') }}
34
37
  restore-keys: |
35
- ${{ runner.os }}-gem-deps-
38
+ ${{ runner.os }}-${{ matrix.ruby }}-gem-deps-
36
39
  - name: Install dependencies
37
40
  run: |
38
41
  bundle config path vendor/bundle
39
42
  bundle install
40
43
  - name: Run tests
41
- run: bundle exec rake
44
+ run: COVERAGE=1 bundle exec rake
45
+ - name: Upload Coverage
46
+ uses: actions/upload-artifact@master
47
+ if: always()
48
+ with:
49
+ name: coverage-report
50
+ path: coverage
42
51
  - uses: actions/cache@v2
43
52
  with:
44
53
  path: example/vendor/bundle
45
- key: ${{ runner.os }}-example-deps-${{ hashFiles('example/**/Gemfile.lock') }}
54
+ key: ${{ runner.os }}-${{ matrix.ruby }}-example-deps-${{ hashFiles('example/**/Gemfile.lock') }}
46
55
  restore-keys: |
47
- ${{ runner.os }}-example-deps-
56
+ ${{ runner.os }}-${{ matrix.ruby }}-example-deps-
48
57
  - name: Install example dependencies for example
49
58
  working-directory: example
50
59
  run: |
@@ -52,4 +61,4 @@ jobs:
52
61
  bundle install
53
62
  - name: Run specs for example
54
63
  working-directory: example
55
- run: rake
64
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -10,5 +10,6 @@
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
12
  .ruby-version
13
+ Gemfile.lock
13
14
 
14
15
  *.gem
@@ -3,7 +3,7 @@ Style/FrozenStringLiteralComment:
3
3
  Style/BlockDelimiters:
4
4
  EnforcedStyle: braces_for_chaining
5
5
  AllCops:
6
- TargetRubyVersion: 2.7.1
6
+ TargetRubyVersion: 2.6.0
7
7
  Exclude:
8
8
  - 'bin/bundle'
9
9
  - 'example/bin/bundle'
@@ -31,6 +31,8 @@ Metrics/AbcSize:
31
31
  Style/Documentation:
32
32
  Exclude:
33
33
  - 'example/db/migrate/**/*'
34
+ Metrics/PerceivedComplexity:
35
+ Enabled: false
34
36
  Layout/EmptyLinesAroundAttributeAccessor:
35
37
  Enabled: true
36
38
  Layout/SpaceAroundMethodCallOperator:
data/README.md CHANGED
@@ -3,16 +3,12 @@
3
3
  ![Ruby Test Status](https://github.com/SonderMindOrg/sober_swag/workflows/Ruby/badge.svg?branch=master)
4
4
  ![Linters Status](https://github.com/SonderMindOrg/sober_swag/workflows/Linters/badge.svg?branch=master)
5
5
 
6
- ***NOTE: THIS GEM IS HIGHLY EXPERIMENTAL AND PROBABLY SHOULD NOT YET BE USED IN PRODUCTION***.
7
-
8
6
  SoberSwag is a combination of [Dry-Types](https://dry-rb.org/gems/dry-types/1.2/) and [Swagger](https://swagger.io/) that makes your Rails APIs more awesome.
9
- Other tools generate documenation from a DSL.
7
+ Other tools generate documentation from a DSL.
10
8
  This generates documentation from *types*, which (conveniently) also lets you get supercharged strong-params-on-steroids.
11
9
 
12
10
  An introductory presentation is available [here](https://www.icloud.com/keynote/0bxP3Dn8ETNO0lpsSQSVfEL6Q#SoberSwagPresentation).
13
11
 
14
- This gem uses pattern matching, and is thus only compatible with Ruby 2.7 or later.
15
-
16
12
  ## Types for a fully-automated API
17
13
 
18
14
  SoberSwag lets you type your API using describe blocks.
@@ -20,22 +16,22 @@ In any controller that includes `SoberSwag::Controller`, you get access to the s
20
16
  This lets you type your API endpoint:
21
17
 
22
18
  ```ruby
23
- class PeopleController < ApplicationController
24
- include SoberSwag::Controller
25
- define :patch, :update, '/people/{id}' do
26
- query_params do
27
- attribute? :include_extra_info, Types::Params::Bool
28
- end
29
- request_body do
30
- attribute? :name, Types::Params::String
31
- attribute? :age, Types::Params::Integer
32
- end
33
- path_params { attribute :id, Types::Params::Integer }
19
+ class PeopleController < ApplicationController
20
+ include SoberSwag::Controller
21
+ define :patch, :update, '/people/{id}' do
22
+ query_params do
23
+ attribute? :include_extra_info, Types::Params::Bool
34
24
  end
25
+ request_body do
26
+ attribute? :name, Types::Params::String
27
+ attribute? :age, Types::Params::Integer
28
+ end
29
+ path_params { attribute :id, Types::Params::Integer }
35
30
  end
31
+ end
36
32
  ```
37
33
 
38
- We can now us this information to generate swagger documentation, available at the `swagger` action on this controller.
34
+ We can now use this information to generate swagger documentation, available at the `swagger` action on this controller.
39
35
  More than that, we can use this information *inside* our controller methods:
40
36
 
41
37
  ```ruby
@@ -51,7 +47,7 @@ You define the type of parameters you accept, and we reject anything that doesn'
51
47
  ### Typed Responses
52
48
 
53
49
  Want to go further and type your responses too?
54
- Use SoberSwag blueprints, a serializer library heavily inspired by [OutputObjecter](https://github.com/procore/blueprinter)
50
+ Use SoberSwag output objects, a serializer library heavily inspired by [Blueprinter](https://github.com/procore/blueprinter)
55
51
 
56
52
  ```ruby
57
53
  PersonOutputObject = SoberSwag::OutputObject.define do
@@ -89,27 +85,75 @@ end
89
85
 
90
86
  Support for easily typing "render the activerecord errors for me please" is (unfortunately) under development.
91
87
 
92
- ### SoberSwag Structs
88
+ ### SoberSwag Input Objects
93
89
 
94
90
  Input parameters (including path, query, and request body) are typed using [dry-struct](https://dry-rb.org/gems/dry-struct/1.0/).
95
- You don't have to do them inline: you can define them in another file, like so:
91
+ You don't have to do them inline. You can define them in another file, like so:
96
92
 
97
93
  ```ruby
98
- User = SoberSwag.struct do
94
+ User = SoberSwag.input_object do
99
95
  attribute :name, SoberSwag::Types::String
100
96
  # use ? if attributes are not required
101
97
  attribute? :favorite_movie, SoberSwag::Types::String
102
98
  # use .optional if attributes may be null
103
- attribute :age, SoberSwag::Types::Params::::Integer.optional
99
+ attribute :age, SoberSwag::Types::Params::Integer.optional
100
+ end
101
+ ```
102
+
103
+ Then, in your controller, just do:
104
+
105
+ ```ruby
106
+ class PeopleController < ApplicationController
107
+ include SoberSwag::Controller
108
+
109
+ define :path, :update, '/people/{id}' do
110
+ request_body(User)
111
+ path_params { attribute :id, Types::Params::Integer }
112
+ response(:ok, 'the updated person', PersonOutputObject)
113
+ end
114
+ def update
115
+ # same as above!
116
+ end
104
117
  end
105
118
  ```
106
119
 
107
120
  Under the hood, this literally just generates a subclass of `Dry::Struct`.
108
121
  We use the DSL-like method just to make working with Rails' reloading less annoying.
109
122
 
123
+ #### Adding additional documentation
124
+
125
+ You can use the `.meta` attribute on a type to add additional documentation.
126
+ Some keys are considered "well-known" and will be present on the swagger output.
127
+ For example:
128
+
129
+
130
+ ```ruby
131
+ User = SoberSwag.input_object do
132
+ attribute? :name, SoberSwag::Types::String.meta(description: <<~MARKDOWN, deprecated: true)
133
+ The given name of the students, with strings encoded as escaped-ASCII.
134
+ This is used by an internal Cobol microservice from 1968.
135
+ Please use unicode_name instead unless you are that microservice.
136
+ MARKDOWN
137
+ attribute? :unicode_name, SoberSwag::Types::String
138
+ end
139
+ ```
140
+
141
+ This will output the swagger you expect, with a description and a deprecated flag.
142
+
143
+ #### Adding Default Values
144
+
145
+ Sometimes it makes sense to specify a default value.
146
+ Don't worry, we've got you covered:
147
+
148
+ ```ruby
149
+ QueryInput = SoberSwag.input_object do
150
+ attribute :allow_first, SoberSwag::Types::Params::Bool.default(false) # smartly alters type-definition to establish that passing this is not required.
151
+ end
152
+ ```
153
+
110
154
  ## Special Thanks
111
155
 
112
- This gem is a mismatch of ideas from various sources.
156
+ This gem is a mishmash of ideas from various sources.
113
157
  The biggest thanks is owed to the [dry-rb](https://github.com/dry-rb) project, upon which the typing of SoberSwag is based.
114
158
  On an API design level, much is owed to [blueprinter](https://github.com/procore/blueprinter) for the serializers.
115
159
  The idea of a strongly-typed API came from the Haskell framework [servant](https://www.servant.dev/).
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby -W:no-experimental
1
+ #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'bundler/setup'
@@ -34,6 +34,15 @@ class LinkedList < SoberSwag::InputObject
34
34
  attribute? :next, LinkedList
35
35
  end
36
36
 
37
+ Foo = SoberSwag::OutputObject.define do
38
+ field :name, primitive(:String)
39
+ field :age, primitive(:String)
40
+
41
+ view :foo do
42
+ field :bar, primitive(:String).optional
43
+ end
44
+ end
45
+
37
46
  # (If you use this, don't forget to add pry to your Gemfile!)
38
47
  require 'pry'
39
48
  Pry.start
@@ -4,11 +4,11 @@ Serializers are a way to transform from one type to another.
4
4
  For example, you might want to change an ActiveRecord object to a JSON struct.
5
5
  You might also want to change an internal date-interval into a two-element array of dates, or some custom text format.
6
6
  You can do all of these things with SoberSwag serializers.
7
- Furthermore, Serializers document the *type* that they serialize, so you can use it to degenerate documentation.
7
+ Furthermore, Serializers document the *type* that they serialize, so you can use it to generate documentation.
8
8
 
9
9
  ## The Basics
10
10
 
11
- All serializers are inherted from [`SoberSwag::Serializer::Base`](../lib/sober_swag/serializer/base.rb).
11
+ All serializers are inherited from [`SoberSwag::Serializer::Base`](../lib/sober_swag/serializer/base.rb).
12
12
  This is an abstract class that implements several methods, most of which will be documented later.
13
13
  The two that are most interesting, however, are `#type` and `#serialize`.
14
14
 
@@ -55,12 +55,12 @@ In the future, we might add some "debug mode" sorta thing that will do type-chec
55
55
 
56
56
  ### Mapped
57
57
 
58
- Sometimes, you can create a serilaizer via a *proc*.
58
+ Sometimes, you can create a serializer via a *proc*.
59
59
  For example, let's say that I want a serializer that takes a `Date` and returns a string.
60
60
  I can do this:
61
61
 
62
62
  ```ruby
63
- date_string = SoberSwag::Serializer.Primitive(:String).via_map { |d| d.to_s }
63
+ date_string = SoberSwag::Serializer.primitive(:String).via_map { |d| d.to_s }
64
64
  ```
65
65
 
66
66
  This is implemented via [`SoberSwag::Serializer::Mapped`](../lib/sober_swag/serializer/mapped.rb).
@@ -87,7 +87,7 @@ my_serializer.optional.serialize(nil) # => nil
87
87
  # ^ nils become nil
88
88
  ```
89
89
 
90
- This properly changes the `type` to be a nillable type, as well.
90
+ This properly changes the `type` to be a nilable type, as well.
91
91
 
92
92
  ### Array
93
93
 
@@ -121,9 +121,9 @@ Let's define an output object:
121
121
 
122
122
  ```ruby
123
123
  StudentOutputObject = SoberSwag::OutputObject.define do
124
- field :first_name, Primitive(:String)
125
- field :last_name, Primitive(:String)
126
- field :recent_grades, Primitive(:Integer).array do |student|
124
+ field :first_name, primitive(:String)
125
+ field :last_name, primitive(:String)
126
+ field :recent_grades, primitive(:Integer).array do |student|
127
127
  student.graded_assignments.limit(100).pluck(:grade)
128
128
  end
129
129
  end
@@ -143,10 +143,10 @@ Let's take a look at their use:
143
143
 
144
144
  ```ruby
145
145
  StudentOutputObject = SoberSwag::OutputObject.define do
146
- field :first_name, Primitive(:String)
147
- field :last_name, Primitive(:String)
146
+ field :first_name, primitive(:String)
147
+ field :last_name, primitive(:String)
148
148
  view :detail do
149
- field :recent_grades, Primitive(:Integer).array do |student|
149
+ field :recent_grades, primitive(:Integer).array do |student|
150
150
  student.graded_assignments.limit(100).pluck(:grade)
151
151
  end
152
152
  end
@@ -189,7 +189,7 @@ StudentOutputObject = SoberSwag::OutputObject.define do
189
189
  end
190
190
  ```
191
191
 
192
- This can cause a circular dependecy.
192
+ This can cause a circular dependency.
193
193
  To break this, you can use a lambda:
194
194
 
195
195
  ```ruby
@@ -200,4 +200,4 @@ StudentOutputObject = SoberSwag::OutputObject.define do
200
200
  end
201
201
  ```
202
202
 
203
- For clarity (and to prevent infinitely-looping serializers on accident, we reccomend you *always* use an explicit view for dependent output objects.
203
+ For clarity (and to prevent infinitely-looping serializers on accident, we recommend you *always* use an explicit view for dependent output objects.
@@ -1,8 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
  git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3
3
 
4
- ruby '2.7.1'
5
-
6
4
  # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
7
5
  gem 'rails', '~> 6.0.2', '>= 6.0.2.2'
8
6
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- sober_swag (0.1.0)
4
+ sober_swag (0.5.0)
5
5
  activesupport
6
6
  dry-struct (~> 1.0)
7
7
  dry-types (~> 1.2)
@@ -225,8 +225,5 @@ DEPENDENCIES
225
225
  sqlite3 (~> 1.4)
226
226
  tzinfo-data
227
227
 
228
- RUBY VERSION
229
- ruby 2.7.1p83
230
-
231
228
  BUNDLED WITH
232
229
  2.1.4
@@ -61,16 +61,18 @@ class PeopleController < ApplicationController
61
61
 
62
62
  define :get, :index, '/people/' do
63
63
  query_params do
64
- attribute? :first_name, Types::String
65
- attribute? :last_name, Types::String
66
- attribute? :view, Types::String.enum('base', 'detail')
64
+ attribute? :filters do
65
+ attribute? :first_name, Types::String
66
+ attribute? :last_name, Types::String
67
+ end
68
+ attribute :view, Types::String.default('base'.freeze).enum('base', 'detail')
67
69
  end
68
70
  response(:ok, 'all the people', PersonOutputObject.array)
69
71
  end
70
72
  def index
71
73
  @people = Person.all
72
- @people = @people.where('UPPER(first_name) LIKE UPPER(?)', "%#{parsed_query.first_name}%") if parsed_query.first_name
73
- @people = @people.where('UPPER(last_name) LIKE UPPER(?)', "%#{parsed_query.last_name}%") if parsed_query.last_name
74
+ @people = @people.where('UPPER(first_name) LIKE UPPER(?)', "%#{parsed_query.filters.first_name}%") if parsed_query.filters&.first_name
75
+ @people = @people.where('UPPER(last_name) LIKE UPPER(?)', "%#{parsed_query.filters.last_name}%") if parsed_query.filters&.last_name
74
76
  respond!(:ok, @people.includes(:posts), serializer_opts: { view: parsed_query.view })
75
77
  end
76
78
 
@@ -39,12 +39,20 @@ class PostsController < ApplicationController
39
39
  define :get, :index, '/posts/' do
40
40
  query_params do
41
41
  attribute? :view, ViewTypes
42
+ attribute :include_first, SoberSwag::Types::Params::Bool.default(false).meta(description: <<~MARKDOWN)
43
+ For historical reasons the first-ever post is the entire text of *Finnegan's Wake.*
44
+ Unfortunately, our contractors wound up depending on this quirk to complete dark arcane ceremonies,
45
+ so we can't remove it. Thus, by default, we don't include the first post unless you explicitly ask us to
46
+ (maybe you feel like some classic literature?).
47
+ MARKDOWN
42
48
  end
43
49
  response(:ok, 'all the posts', PostOutputObject.array)
44
50
  end
45
51
  def index
46
52
  @posts = Post.all
47
53
 
54
+ @posts = @posts.where('id > 1') unless parsed_query.include_first
55
+
48
56
  respond!(:ok, @posts.includes(:person), serializer_opts: { view: parsed_query.view })
49
57
  end
50
58
 
@@ -35,13 +35,13 @@ RSpec.describe 'Index action for people' do
35
35
  end
36
36
 
37
37
  context 'with a good first-name search' do
38
- let(:request) { get '/people', params: { first_name: 'A' } }
38
+ let(:request) { get '/people', params: { filters: { first_name: 'A' } } }
39
39
 
40
40
  it_behaves_like 'a request with the person'
41
41
  end
42
42
 
43
43
  context 'with a good last-name search' do
44
- let(:request) { get '/people', params: { last_name: 'G' } }
44
+ let(:request) { get '/people', params: { filters: { last_name: 'G' } } }
45
45
 
46
46
  it_behaves_like 'a request with the person'
47
47
  end
@@ -22,6 +22,8 @@ module SoberSwag
22
22
  end
23
23
 
24
24
  def primitive_def(value)
25
+ value = value.primitive if value.is_a?(Dry::Types::Nominal)
26
+
25
27
  return nil unless value.is_a?(Class)
26
28
 
27
29
  if (name = primitive_name(value))
@@ -71,13 +73,16 @@ module SoberSwag
71
73
  end
72
74
 
73
75
  def path_schema
74
- path_schema_stub.map { |e| e.merge(in: :path) }
76
+ path_schema_stub.map do |e|
77
+ ensure_uncomplicated(e[:name], e[:schema])
78
+ e.merge(in: :path)
79
+ end
75
80
  rescue TooComplicatedError => e
76
81
  raise TooComplicatedForPathError, e.message
77
82
  end
78
83
 
79
84
  def query_schema
80
- path_schema_stub.map { |e| e.merge(in: :query) }
85
+ path_schema_stub.map { |e| e.merge(in: :query, style: :deepObject, explode: true) }
81
86
  rescue TooComplicatedError => e
82
87
  raise TooComplicatedForQueryError, e.message
83
88
  end
@@ -129,7 +134,7 @@ module SoberSwag
129
134
  when Dry::Types::Sum
130
135
  { oneOf: normalize(parsed_type).elements.map { |t| self.class.new(t.value).schema_stub } }
131
136
  else
132
- raise ArgumentError, "Cannot generate a schema stub for #{type} (#{type.class})"
137
+ raise SoberSwag::Compiler::Error, "Cannot generate a schema stub for #{type} (#{type.class})"
133
138
  end
134
139
  end
135
140
 
@@ -148,14 +153,17 @@ module SoberSwag
148
153
 
149
154
  def rewrite_sums(object) # rubocop:disable Metrics/MethodLength
150
155
  case object
151
- in Nodes::Sum[Nodes::OneOf[*lhs], Nodes::OneOf[*rhs]]
152
- Nodes::OneOf.new(lhs + rhs)
153
- in Nodes::Sum[Nodes::OneOf[*args], rhs]
154
- Nodes::OneOf.new(args + [rhs])
155
- in Nodes::Sum[lhs, Nodes::OneOf[*args]]
156
- Nodes::OneOf.new([lhs] + args)
157
- in Nodes::Sum[lhs, rhs]
158
- Nodes::OneOf.new([lhs, rhs])
156
+ when Nodes::Sum
157
+ lhs, rhs = object.deconstruct
158
+ if lhs.is_a?(Nodes::OneOf) && rhs.is_a?(Nodes::OneOf)
159
+ Nodes::OneOf.new(lhs.deconstruct + rhs.deconstruct)
160
+ elsif lhs.is_a?(Nodes::OneOf)
161
+ Nodes::OneOf.new([*lhs.deconstruct, rhs])
162
+ elsif rhs.is_a?(Nodes::OneOf)
163
+ Nodes::OneOf.new([lhs, *rhs.deconstruct])
164
+ else
165
+ Nodes::OneOf.new([lhs, rhs])
166
+ end
159
167
  else
160
168
  object
161
169
  end
@@ -163,59 +171,69 @@ module SoberSwag
163
171
 
164
172
  def flatten_one_ofs(object)
165
173
  case object
166
- in Nodes::OneOf[*args]
167
- Nodes::OneOf.new(args.uniq)
168
- else
174
+ when Nodes::OneOf
175
+ Nodes::OneOf.new(object.deconstruct.uniq)
176
+ else
169
177
  object
170
178
  end
171
179
  end
172
180
 
173
181
  def to_object_schema(object) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
174
182
  case object
175
- in Nodes::List[element]
183
+ when Nodes::List
176
184
  {
177
185
  type: :array,
178
- items: element
186
+ items: object.deconstruct.first
179
187
  }
180
- in Nodes::Enum[values]
188
+ when Nodes::Enum
181
189
  {
182
190
  type: :string,
183
- enum: values
191
+ enum: object.deconstruct.first
192
+ }
193
+ when Nodes::OneOf
194
+ if object.deconstruct.include?({ type: 'null' })
195
+ rejected = object.deconstruct.reject { |e| e[:type] == 'null' }
196
+ if rejected.length == 1
197
+ rejected.first.merge(nullable: true)
198
+ else
199
+ { oneOf: rejected, nullable: true }
200
+ end
201
+ else
202
+ { oneOf: object.deconstruct }
203
+ end
204
+ when Nodes::Object
205
+ # openAPI requires that you give a list of required attributes
206
+ # (which IMO is the *totally* wrong thing to do but whatever)
207
+ # so we must do this garbage
208
+ required = object.deconstruct.filter { |(_, b)| b[:required] }.map(&:first)
209
+ {
210
+ type: :object,
211
+ properties: object.deconstruct.map { |(a, b)|
212
+ [a, b.reject { |k, _| k == :required }]
213
+ }.to_h,
214
+ required: required
184
215
  }
185
- in Nodes::OneOf[{ type: 'null' }, b]
186
- b.merge(nullable: true)
187
- in Nodes::OneOf[a, { type: 'null' }]
188
- a.merge(nullable: true)
189
- in Nodes::OneOf[*attrs] if attrs.include?(type: 'null')
190
- { oneOf: attrs.reject { |e| e[:type] == 'null' }, nullable: true }
191
- in Nodes::OneOf[*cases]
192
- { oneOf: cases }
193
- in Nodes::Object[*attrs]
194
- # openAPI requires that you give a list of required attributes
195
- # (which IMO is the *totally* wrong thing to do but whatever)
196
- # so we must do this garbage
197
- required = attrs.filter { |(_, b)| b[:required] }.map(&:first)
198
- {
199
- type: :object,
200
- properties: attrs.map { |(a, b)|
201
- [a, b.reject { |k, _| k == :required }]
202
- }.to_h,
203
- required: required
204
- }
205
- in Nodes::Attribute[name, true, value]
206
- [name, value.merge(required: true)]
207
- in Nodes::Attribute[name, false, value]
208
- [name, value]
216
+ when Nodes::Attribute
217
+ name, req, value = object.deconstruct
218
+ if req
219
+ [name, value.merge(required: true)]
220
+ else
221
+ [name, value]
222
+ end
209
223
  # can't match on value directly as ruby uses `===` to match,
210
224
  # and classes use `===` to mean `is an instance of`, as
211
225
  # opposed to direct equality lmao
212
- in Nodes::Primitive[value:, metadata:] if self.class.primitive?(value)
213
- md = self.class.primitive_def(value)
214
- METADATA_KEYS.select(&metadata.method(:key?)).reduce(md) do |definition, key|
215
- definition.merge(key => metadata[key])
216
- end
217
- in Nodes::Primitive[value:]
218
- { '$ref': self.class.get_ref(value) }
226
+ when Nodes::Primitive
227
+ value = object.value
228
+ metadata = object.metadata
229
+ if self.class.primitive?(value)
230
+ md = self.class.primitive_def(value)
231
+ METADATA_KEYS.select(&metadata.method(:key?)).reduce(md) do |definition, key|
232
+ definition.merge(key => metadata[key])
233
+ end
234
+ else
235
+ { '$ref': self.class.get_ref(value) }
236
+ end
219
237
  else
220
238
  raise ArgumentError, "Got confusing node #{object} (#{object.class})"
221
239
  end
@@ -224,13 +242,10 @@ module SoberSwag
224
242
  def path_schema_stub
225
243
  @path_schema_stub ||=
226
244
  object_schema[:properties].map do |k, v|
227
- ensure_uncomplicated(k, v)
245
+ # ensure_uncomplicated(k, v)
228
246
  {
229
247
  name: k,
230
248
  schema: v.reject { |key, _| %i[required nullable].include?(key) },
231
- # rubocop:disable Style/DoubleNegation
232
- allowEmptyValue: !object_schema[:required].include?(k) || !!v[:nullable], # if it's required, no empties, but if *nullabe*, empties are okay
233
- # rubocop:enable Style/DoubleNegation
234
249
  required: object_schema[:required].include?(k) || false
235
250
  }
236
251
  end
@@ -239,6 +254,8 @@ module SoberSwag
239
254
  def ensure_uncomplicated(key, value)
240
255
  return if value[:type]
241
256
 
257
+ return value[:oneOf].each { |member| ensure_uncomplicated(key, member) } if value[:oneOf]
258
+
242
259
  raise TooComplicatedError, <<~ERROR
243
260
  Property #{key} has object-schema #{value}, but this type of param should be simple (IE a primitive of some kind)
244
261
  ERROR
@@ -44,6 +44,10 @@ module SoberSwag
44
44
  def map
45
45
  raise ArgumentError, 'Base is abstract'
46
46
  end
47
+
48
+ def flatten_one_ofs
49
+ raise ArgumentError, 'Base is abstract'
50
+ end
47
51
  end
48
52
  end
49
53
  end
@@ -10,7 +10,7 @@ module SoberSwag
10
10
  ##
11
11
  # Given a symbol to this, we will use a primitive name
12
12
  def primitive(name)
13
- SoberSwag::Serializer.Primitive(SoberSwag::Types.const_get(name))
13
+ SoberSwag::Serializer.primitive(SoberSwag::Types.const_get(name))
14
14
  end
15
15
  end
16
16
  end
@@ -10,6 +10,9 @@ module SoberSwag
10
10
 
11
11
  def to_syntax # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
12
12
  case @node
13
+ when Dry::Types::Default
14
+ # we handle this elsewhere, so
15
+ bind(Parser.new(@node.type))
13
16
  when Dry::Types::Array::Member
14
17
  Nodes::List.new(bind(Parser.new(@node.member)))
15
18
  when Dry::Types::Enum
@@ -21,7 +24,7 @@ module SoberSwag
21
24
  when Dry::Types::Schema::Key
22
25
  Nodes::Attribute.new(
23
26
  @node.name,
24
- @node.required?,
27
+ @node.required? && !@node.type.default?,
25
28
  bind(Parser.new(@node.type))
26
29
  )
27
30
  when Dry::Types::Sum
@@ -18,7 +18,7 @@ module SoberSwag
18
18
  # in values raw.
19
19
  #
20
20
  # @param contained {Class} Dry::Type to use
21
- def Primitive(contained) # rubocop:disable Naming/MethodName
21
+ def primitive(contained)
22
22
  SoberSwag::Serializer::Primitive.new(contained)
23
23
  end
24
24
  end
@@ -47,7 +47,11 @@ module SoberSwag
47
47
  end
48
48
 
49
49
  def lazy_type
50
- left.lazy_type | right.lazy_type
50
+ if left.lazy_type == right.lazy_type
51
+ left.lazy_type
52
+ else
53
+ left.lazy_type | right.lazy_type
54
+ end
51
55
  end
52
56
 
53
57
  def lazy_type?
@@ -8,7 +8,9 @@ module SoberSwag
8
8
  @map_f = map_f
9
9
  end
10
10
 
11
- def serialize(object, options)
11
+ attr_reader :base, :map_f
12
+
13
+ def serialize(object, options = {})
12
14
  @base.serialize(@map_f.call(object), options)
13
15
  end
14
16
 
@@ -9,8 +9,12 @@ module SoberSwag
9
9
  Rails.application.routes.routes.map { |route|
10
10
  route.defaults[:controller]
11
11
  }.to_set.reject(&:nil?).map { |controller|
12
- "#{controller}_controller".classify.constantize
13
- }.filter { |controller| controller.ancestors.include?(SoberSwag::Controller) }
12
+ begin
13
+ "#{controller}_controller".classify.constantize
14
+ rescue StandardError
15
+ nil
16
+ end
17
+ }.compact.filter { |controller| controller.ancestors.include?(SoberSwag::Controller) }
14
18
  end
15
19
 
16
20
  ##
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SoberSwag
4
- VERSION = '0.2.0'
4
+ VERSION = '0.7.0'
5
5
  end
@@ -19,6 +19,8 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.metadata['homepage_uri'] = spec.homepage
21
21
 
22
+ spec.required_ruby_version = '>= 2.6.0'
23
+
22
24
  # Specify which files should be added to the gem when it is released.
23
25
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
26
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
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.2.0
4
+ version: 0.7.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: 2020-07-16 00:00:00.000000000 Z
11
+ date: 2020-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -166,7 +166,6 @@ files:
166
166
  - ".ruby-version"
167
167
  - ".travis.yml"
168
168
  - Gemfile
169
- - Gemfile.lock
170
169
  - LICENSE.txt
171
170
  - README.md
172
171
  - Rakefile
@@ -288,14 +287,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
288
287
  requirements:
289
288
  - - ">="
290
289
  - !ruby/object:Gem::Version
291
- version: '0'
290
+ version: 2.6.0
292
291
  required_rubygems_version: !ruby/object:Gem::Requirement
293
292
  requirements:
294
293
  - - ">="
295
294
  - !ruby/object:Gem::Version
296
295
  version: '0'
297
296
  requirements: []
298
- rubygems_version: 3.1.2
297
+ rubygems_version: 3.0.3
299
298
  signing_key:
300
299
  specification_version: 4
301
300
  summary: Generate swagger types from dry-types
@@ -1,116 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- sober_swag (0.1.0)
5
- activesupport
6
- dry-struct (~> 1.0)
7
- dry-types (~> 1.2)
8
-
9
- GEM
10
- remote: https://rubygems.org/
11
- specs:
12
- activesupport (6.0.3.1)
13
- concurrent-ruby (~> 1.0, >= 1.0.2)
14
- i18n (>= 0.7, < 2)
15
- minitest (~> 5.1)
16
- tzinfo (~> 1.1)
17
- zeitwerk (~> 2.2, >= 2.2.2)
18
- ast (2.4.1)
19
- coderay (1.1.2)
20
- concurrent-ruby (1.1.6)
21
- diff-lcs (1.3)
22
- docile (1.3.2)
23
- dry-configurable (0.11.3)
24
- concurrent-ruby (~> 1.0)
25
- dry-core (~> 0.4, >= 0.4.7)
26
- dry-equalizer (~> 0.2)
27
- dry-container (0.7.2)
28
- concurrent-ruby (~> 1.0)
29
- dry-configurable (~> 0.1, >= 0.1.3)
30
- dry-core (0.4.9)
31
- concurrent-ruby (~> 1.0)
32
- dry-equalizer (0.3.0)
33
- dry-inflector (0.2.0)
34
- dry-logic (1.0.6)
35
- concurrent-ruby (~> 1.0)
36
- dry-core (~> 0.2)
37
- dry-equalizer (~> 0.2)
38
- dry-struct (1.3.0)
39
- dry-core (~> 0.4, >= 0.4.4)
40
- dry-equalizer (~> 0.3)
41
- dry-types (~> 1.3)
42
- ice_nine (~> 0.11)
43
- dry-types (1.4.0)
44
- concurrent-ruby (~> 1.0)
45
- dry-container (~> 0.3)
46
- dry-core (~> 0.4, >= 0.4.4)
47
- dry-equalizer (~> 0.3)
48
- dry-inflector (~> 0.1, >= 0.1.2)
49
- dry-logic (~> 1.0, >= 1.0.2)
50
- i18n (1.8.2)
51
- concurrent-ruby (~> 1.0)
52
- ice_nine (0.11.2)
53
- method_source (0.9.2)
54
- minitest (5.14.1)
55
- parallel (1.19.2)
56
- parser (2.7.1.4)
57
- ast (~> 2.4.1)
58
- pry (0.12.2)
59
- coderay (~> 1.1.0)
60
- method_source (~> 0.9.0)
61
- rainbow (3.0.0)
62
- rake (13.0.1)
63
- regexp_parser (1.7.1)
64
- rexml (3.2.4)
65
- rspec (3.9.0)
66
- rspec-core (~> 3.9.0)
67
- rspec-expectations (~> 3.9.0)
68
- rspec-mocks (~> 3.9.0)
69
- rspec-core (3.9.1)
70
- rspec-support (~> 3.9.1)
71
- rspec-expectations (3.9.0)
72
- diff-lcs (>= 1.2.0, < 2.0)
73
- rspec-support (~> 3.9.0)
74
- rspec-mocks (3.9.1)
75
- diff-lcs (>= 1.2.0, < 2.0)
76
- rspec-support (~> 3.9.0)
77
- rspec-support (3.9.2)
78
- rubocop (0.88.0)
79
- parallel (~> 1.10)
80
- parser (>= 2.7.1.1)
81
- rainbow (>= 2.2.2, < 4.0)
82
- regexp_parser (>= 1.7)
83
- rexml
84
- rubocop-ast (>= 0.1.0, < 1.0)
85
- ruby-progressbar (~> 1.7)
86
- unicode-display_width (>= 1.4.0, < 2.0)
87
- rubocop-ast (0.1.0)
88
- parser (>= 2.7.0.1)
89
- rubocop-rspec (1.42.0)
90
- rubocop (>= 0.87.0)
91
- ruby-progressbar (1.10.1)
92
- simplecov (0.18.5)
93
- docile (~> 1.1)
94
- simplecov-html (~> 0.11)
95
- simplecov-html (0.12.2)
96
- thread_safe (0.3.6)
97
- tzinfo (1.2.7)
98
- thread_safe (~> 0.1)
99
- unicode-display_width (1.7.0)
100
- zeitwerk (2.3.0)
101
-
102
- PLATFORMS
103
- ruby
104
-
105
- DEPENDENCIES
106
- bundler (~> 2.0)
107
- pry
108
- rake (~> 13.0)
109
- rspec (~> 3.0)
110
- rubocop
111
- rubocop-rspec
112
- simplecov
113
- sober_swag!
114
-
115
- BUNDLED WITH
116
- 2.1.4