sober_swag 0.2.0 → 0.7.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 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