openapi_blocks 0.3.1 → 0.4.1
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/CHANGELOG.md +41 -4
- data/README.md +99 -73
- data/README.pt-BR.md +146 -131
- data/app/controllers/openapi_blocks/spec_controller.rb +2 -2
- data/lib/openapi_blocks/auto_serialize.rb +54 -0
- data/lib/openapi_blocks/base.rb +6 -35
- data/lib/openapi_blocks/builder.rb +34 -2
- data/lib/openapi_blocks/concerns/documentable.rb +26 -0
- data/lib/openapi_blocks/concerns/schemable.rb +45 -0
- data/lib/openapi_blocks/configuration.rb +8 -1
- data/lib/openapi_blocks/controller.rb +7 -27
- data/lib/openapi_blocks/railtie.rb +10 -4
- data/lib/openapi_blocks/registry.rb +76 -0
- data/lib/openapi_blocks/serialization.rb +197 -0
- data/lib/openapi_blocks/serializer.rb +12 -182
- data/lib/openapi_blocks/spec/components.rb +6 -4
- data/lib/openapi_blocks/version.rb +1 -1
- data/lib/openapi_blocks.rb +7 -3
- metadata +11 -5
- data/lib/openapi_blocks/resource.rb +0 -20
data/README.pt-BR.md
CHANGED
|
@@ -1,38 +1,16 @@
|
|
|
1
1
|
# OpenapiBlocks
|
|
2
2
|
|
|
3
|
-
OpenapiBlocks é uma gem Rails que gera automaticamente documentação OpenAPI 3.0/3.1 a partir dos seus
|
|
3
|
+
OpenapiBlocks é uma gem Rails que gera automaticamente documentação OpenAPI 3.0/3.1 a partir dos seus models ActiveRecord, validações ActiveModel e rotas do Rails — inspirada no [ActiveModel::Serializer](https://github.com/rails-api/active_model_serializers).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
English version: [README.md](README.md)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Versão padrão do OpenAPI: `3.1.0` (suportado: `3.1.0`, `3.0.3`).
|
|
9
|
-
- A Swagger UI é servida no caminho onde a engine foi montada e usa endpoints do mesmo origin (same-origin) para evitar CORS — a UI mostra uma lista de servidores, mas buscará o spec a partir da URL montada.
|
|
10
|
-
- A saída YAML é normalizada para chaves em string (`deep_stringify_keys`) para que o campo `openapi` seja reconhecido pelo Swagger UI.
|
|
11
|
-
- O DSL `association` usa `read_only: true` para marcar associações como somente resposta e excluí-las dos schemas `*Input`; associações/atributos `read_only` continuam presentes nas respostas.
|
|
12
|
-
- O `tags` é gerado no nível do documento a partir dos paths e pode ser customizado via `tags` nas classes e operações.
|
|
13
|
-
- Referências de schema aceitam `Symbol` (ex.: `schema: :user`) e arrays com items como símbolos (ex.: `items: :user`).
|
|
14
|
-
# OpenapiBlocks
|
|
15
|
-
|
|
16
|
-
OpenapiBlocks é uma gem Rails que gera automaticamente documentação OpenAPI 3.0/3.1 a partir dos seus modelos ActiveRecord, validações do ActiveModel e rotas do Rails — inspirada em ActiveModel::Serializer.
|
|
17
|
-
|
|
18
|
-
Sem anotações manuais. Sem ruído de DSL nos controllers. Basta declarar o que deve ser exposto e o spec é gerado automaticamente. Inclui um serializer interno de alto desempenho — aproximadamente 3.6× mais rápido que `as_json` com escalabilidade linear consistente.
|
|
19
|
-
|
|
20
|
-
## Principais mudanças (recentes)
|
|
21
|
-
- `OpenapiBlocks::Resource` e `OpenapiBlocks::Controller` foram introduzidos para separar responsabilidades de serialização e documentação.
|
|
22
|
-
- Versão padrão do OpenAPI: `3.1.0` (suportado: `3.1.0`, `3.0.3`).
|
|
23
|
-
- Scalar UI agora é servido em `/docs/scalar` ao lado da Swagger UI em `/docs`.
|
|
24
|
-
- A Swagger UI usa endpoints same-origin para evitar problemas de CORS ao usar "Try it out"; a UI mostra servidores configurados, mas busca o spec a partir da URL montada da engine.
|
|
25
|
-
- A saída YAML é normalizada para chaves em string (`deep_stringify_keys`) para que o campo `openapi` seja reconhecido pelo Swagger UI.
|
|
26
|
-
- O DSL `association` utiliza `read_only: true` para marcar associações como somente-resposta e excluí-las dos schemas `*Input`; atributos/associações `read_only` continuam presentes em respostas.
|
|
27
|
-
- `tags` são gerados no nível do documento a partir dos paths e podem ser customizados via `tags` nas classes e operações.
|
|
28
|
-
- Referências de schema aceitam `Symbol` (ex.: `schema: :user`) e arrays com `items` como símbolos (ex.: `items: :user`).
|
|
29
|
-
- O serializer compila um método extrator monolítico por classe em tempo de boot usando `class_eval`, eliminando ramificações por objeto e chamadas lambda em tempo de execução.
|
|
7
|
+
Sem anotações manuais. Sem DSL nos controllers. Basta declarar o que expor e a spec é gerada automaticamente. Inclui um serializer de alta performance — ~3.6× mais rápido que `as_json` com escalabilidade linear de 10 a 5000 registros.
|
|
30
8
|
|
|
31
9
|
---
|
|
32
10
|
|
|
33
11
|
## Instalação
|
|
34
12
|
|
|
35
|
-
Adicione ao seu Gemfile
|
|
13
|
+
Adicione ao seu `Gemfile`:
|
|
36
14
|
|
|
37
15
|
```ruby
|
|
38
16
|
gem "openapi_blocks"
|
|
@@ -48,7 +26,7 @@ bundle install
|
|
|
48
26
|
|
|
49
27
|
## Configuração
|
|
50
28
|
|
|
51
|
-
### 1. Monte
|
|
29
|
+
### 1. Monte o Engine
|
|
52
30
|
|
|
53
31
|
```ruby
|
|
54
32
|
# config/routes.rb
|
|
@@ -62,28 +40,30 @@ end
|
|
|
62
40
|
Isso expõe:
|
|
63
41
|
|
|
64
42
|
```
|
|
65
|
-
GET /docs -> Scalar UI
|
|
43
|
+
GET /docs -> Scalar UI (padrão)
|
|
66
44
|
GET /docs/swagger -> Swagger UI
|
|
67
|
-
GET /docs/openapi.json -> OpenAPI
|
|
68
|
-
GET /docs/openapi.yaml -> OpenAPI
|
|
45
|
+
GET /docs/openapi.json -> Spec OpenAPI em JSON
|
|
46
|
+
GET /docs/openapi.yaml -> Spec OpenAPI em YAML
|
|
69
47
|
```
|
|
70
48
|
|
|
71
49
|
### 2. Configure o initializer
|
|
72
50
|
|
|
51
|
+
`OpenapiBlocks.configure` é obrigatório. A gem lança `OpenapiBlocks::Error` na primeira requisição se nunca foi chamado ou se `info.title` / `info.version` estiverem em branco.
|
|
52
|
+
|
|
73
53
|
```ruby
|
|
74
54
|
# config/initializers/openapi_blocks.rb
|
|
75
55
|
OpenapiBlocks.configure do |config|
|
|
76
|
-
config.openapi_version = "3.1.0" # "3.0.3" ou "3.1.0"
|
|
56
|
+
config.openapi_version = "3.1.0" # obrigatório — "3.0.3" ou "3.1.0"
|
|
77
57
|
|
|
78
58
|
config.info do
|
|
79
|
-
title "Minha API"
|
|
80
|
-
version "1.0.0"
|
|
81
|
-
description "Documentação
|
|
59
|
+
title "Minha API" # obrigatório
|
|
60
|
+
version "1.0.0" # obrigatório
|
|
61
|
+
description "Documentação gerada automaticamente"
|
|
82
62
|
|
|
83
63
|
contact do
|
|
84
|
-
name "
|
|
85
|
-
email "api@
|
|
86
|
-
url "https://
|
|
64
|
+
name "Meu Time"
|
|
65
|
+
email "api@minhaempresa.com.br"
|
|
66
|
+
url "https://minhaempresa.com.br"
|
|
87
67
|
end
|
|
88
68
|
|
|
89
69
|
license do
|
|
@@ -94,7 +74,7 @@ OpenapiBlocks.configure do |config|
|
|
|
94
74
|
|
|
95
75
|
config.servers do
|
|
96
76
|
server do
|
|
97
|
-
url "https://api.
|
|
77
|
+
url "https://api.minhaempresa.com.br"
|
|
98
78
|
description "Produção"
|
|
99
79
|
end
|
|
100
80
|
|
|
@@ -104,7 +84,8 @@ OpenapiBlocks.configure do |config|
|
|
|
104
84
|
end
|
|
105
85
|
end
|
|
106
86
|
|
|
107
|
-
config.watch
|
|
87
|
+
config.watch = :development # recarrega automaticamente em desenvolvimento
|
|
88
|
+
config.auto_serialize = true # opcional — veja Serialização Automática abaixo
|
|
108
89
|
|
|
109
90
|
# opcional: esquemas de segurança
|
|
110
91
|
config.security do
|
|
@@ -118,27 +99,28 @@ end
|
|
|
118
99
|
|
|
119
100
|
## Uso
|
|
120
101
|
|
|
121
|
-
OpenapiBlocks
|
|
102
|
+
OpenapiBlocks oferece duas classes base com responsabilidades distintas:
|
|
122
103
|
|
|
123
|
-
- `OpenapiBlocks::
|
|
124
|
-
- `OpenapiBlocks::Controller` — define operações
|
|
125
|
-
- `OpenapiBlocks::Base` — classe legada que combina ambas as responsabilidades. Ainda suportada.
|
|
104
|
+
- `OpenapiBlocks::Serializer` — define o model, campos, associações e lógica de serialização. Fica em `app/serializers/`.
|
|
105
|
+
- `OpenapiBlocks::Controller` — define operações, parâmetros e respostas para documentação. Fica em `app/openapi/`.
|
|
106
|
+
- `OpenapiBlocks::Base` — classe base legada que combina ambas as responsabilidades. Ainda suportada.
|
|
126
107
|
|
|
127
|
-
###
|
|
108
|
+
### Recomendado: Serializer + Controller
|
|
128
109
|
|
|
129
110
|
```
|
|
130
111
|
app/
|
|
112
|
+
serializers/
|
|
113
|
+
user_serializer.rb -> serialização + schema
|
|
114
|
+
post_serializer.rb
|
|
131
115
|
openapi/
|
|
132
|
-
|
|
133
|
-
user_openapi.rb -> documentação da API
|
|
134
|
-
post_resource.rb
|
|
116
|
+
user_openapi.rb -> documentação da API
|
|
135
117
|
post_openapi.rb
|
|
136
118
|
```
|
|
137
119
|
|
|
138
120
|
```ruby
|
|
139
|
-
# app/
|
|
140
|
-
class
|
|
141
|
-
#
|
|
121
|
+
# app/serializers/user_serializer.rb
|
|
122
|
+
class UserSerializer < OpenapiBlocks::Serializer
|
|
123
|
+
# model User inferido automaticamente pelo nome da classe
|
|
142
124
|
|
|
143
125
|
ignore :password_digest, :reset_password_token
|
|
144
126
|
|
|
@@ -148,39 +130,39 @@ class UserResource < OpenapiBlocks::Resource
|
|
|
148
130
|
attribute :access_token, type: :string, read_only: true
|
|
149
131
|
attribute :nickname, type: :string
|
|
150
132
|
|
|
151
|
-
# método definido aqui — chamado na instância do
|
|
133
|
+
# método definido aqui — chamado na instância do serializer
|
|
152
134
|
def full_name
|
|
153
135
|
"#{object.name} (#{object.email})"
|
|
154
136
|
end
|
|
155
137
|
|
|
156
|
-
# ou omita o método e ele
|
|
138
|
+
# ou omita o método e ele delega para o model automaticamente
|
|
157
139
|
end
|
|
158
140
|
```
|
|
159
141
|
|
|
160
142
|
```ruby
|
|
161
143
|
# app/openapi/user_openapi.rb
|
|
162
144
|
class UserOpenapi < OpenapiBlocks::Controller
|
|
163
|
-
resource
|
|
145
|
+
resource UserSerializer
|
|
164
146
|
controller UsersController
|
|
165
147
|
|
|
166
|
-
tags "
|
|
148
|
+
tags "Usuários"
|
|
167
149
|
|
|
168
150
|
operation :index do
|
|
169
|
-
summary "
|
|
170
|
-
description "
|
|
151
|
+
summary "Lista todos os usuários"
|
|
152
|
+
description "Retorna uma lista paginada de usuários ativos"
|
|
171
153
|
|
|
172
|
-
parameter :page, in: :query, type: :integer, description: "
|
|
173
|
-
parameter :per_page, in: :query, type: :integer, description: "
|
|
154
|
+
parameter :page, in: :query, type: :integer, description: "Número da página"
|
|
155
|
+
parameter :per_page, in: :query, type: :integer, description: "Itens por página"
|
|
174
156
|
|
|
175
|
-
response 200, description: "
|
|
176
|
-
response 401, description: "
|
|
157
|
+
response 200, description: "Lista de usuários", schema: { type: :array, items: :User }
|
|
158
|
+
response 401, description: "Não autorizado"
|
|
177
159
|
end
|
|
178
160
|
|
|
179
161
|
operation :show do
|
|
180
|
-
summary "
|
|
162
|
+
summary "Busca um usuário"
|
|
181
163
|
|
|
182
|
-
response 200, description: "
|
|
183
|
-
response 404, description: "
|
|
164
|
+
response 200, description: "Usuário encontrado", schema: :User
|
|
165
|
+
response 404, description: "Não encontrado"
|
|
184
166
|
|
|
185
167
|
no_security!
|
|
186
168
|
end
|
|
@@ -190,20 +172,20 @@ end
|
|
|
190
172
|
```ruby
|
|
191
173
|
# app/controllers/users_controller.rb
|
|
192
174
|
def index
|
|
193
|
-
render json:
|
|
175
|
+
render json: UserSerializer.serialize(User.includes(:posts))
|
|
194
176
|
end
|
|
195
177
|
|
|
196
178
|
def show
|
|
197
|
-
render json:
|
|
179
|
+
render json: UserSerializer.serialize(User.find(params[:id]))
|
|
198
180
|
end
|
|
199
181
|
```
|
|
200
182
|
|
|
201
|
-
### Base (
|
|
183
|
+
### Legado: Base (classe única)
|
|
202
184
|
|
|
203
185
|
```ruby
|
|
204
186
|
# app/openapi/user_openapi.rb
|
|
205
187
|
class UserOpenapi < OpenapiBlocks::Base
|
|
206
|
-
tags "
|
|
188
|
+
tags "Usuários"
|
|
207
189
|
|
|
208
190
|
ignore :password_digest
|
|
209
191
|
|
|
@@ -212,8 +194,8 @@ class UserOpenapi < OpenapiBlocks::Base
|
|
|
212
194
|
attribute :full_name, type: :string, read_only: true
|
|
213
195
|
|
|
214
196
|
operation :index do
|
|
215
|
-
summary "
|
|
216
|
-
response 200, description: "
|
|
197
|
+
summary "Lista todos os usuários"
|
|
198
|
+
response 200, description: "Lista de usuários", schema: { type: :array, items: :User }
|
|
217
199
|
end
|
|
218
200
|
end
|
|
219
201
|
```
|
|
@@ -227,35 +209,67 @@ end
|
|
|
227
209
|
|
|
228
210
|
---
|
|
229
211
|
|
|
212
|
+
## Serialização Automática
|
|
213
|
+
|
|
214
|
+
Quando `config.auto_serialize = true`, o OpenapiBlocks intercepta todas as chamadas `render json:` e aplica automaticamente o serializer registrado — sem precisar chamar o serializer explicitamente nos controllers.
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
# config/initializers/openapi_blocks.rb
|
|
218
|
+
config.auto_serialize = true
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
# app/controllers/users_controller.rb
|
|
223
|
+
def index
|
|
224
|
+
render json: User.all # serializado automaticamente pelo UserSerializer
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def show
|
|
228
|
+
render json: @user # serializado automaticamente pelo UserSerializer
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
O registro do serializer é automático por convenção (`UserSerializer` -> `User`). Para registro explícito:
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
class AdminUserSerializer < OpenapiBlocks::Serializer
|
|
236
|
+
serializes User # mapeia explicitamente este serializer para o model User
|
|
237
|
+
end
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Se nenhum serializer for encontrado, o OpenapiBlocks usa o comportamento padrão do Rails e registra um aviso no log.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
230
244
|
## Serializer
|
|
231
245
|
|
|
232
|
-
O serializer
|
|
246
|
+
O serializer compila um método extrator monolítico por classe no boot usando `class_eval`. Sem loops, sem indireção via lambda e sem branching por objeto em tempo de execução.
|
|
233
247
|
|
|
234
248
|
### Performance (200 registros, arm64, Ruby 4.0)
|
|
235
249
|
|
|
236
|
-
| Método
|
|
237
|
-
|
|
238
|
-
| serialize
|
|
239
|
-
| to_json
|
|
240
|
-
| as_json
|
|
241
|
-
| oj+as_json | 1 126 | 888
|
|
250
|
+
| Método | i/s | μs/i | vs serialize |
|
|
251
|
+
|------------|-------|------|--------------|
|
|
252
|
+
| serialize | 4 239 | 235 | — |
|
|
253
|
+
| to_json | 1 444 | 692 | 2.94× mais lento |
|
|
254
|
+
| as_json | 1 186 | 843 | 3.58× mais lento |
|
|
255
|
+
| oj+as_json | 1 126 | 888 | 3.77× mais lento |
|
|
242
256
|
|
|
243
|
-
|
|
257
|
+
A escalabilidade é linear — a vantagem de 3.6× sobre o `as_json` se mantém de 10 a 5000 registros.
|
|
244
258
|
|
|
245
|
-
### Atributos virtuais e resolução de
|
|
259
|
+
### Atributos virtuais e resolução de métodos
|
|
246
260
|
|
|
247
|
-
| Declarado com
|
|
248
|
-
|
|
249
|
-
| `attribute :full_name` | sim
|
|
250
|
-
| `attribute :full_name` | não
|
|
251
|
-
| coluna no
|
|
261
|
+
| Declarado com | Método no serializer? | Chama |
|
|
262
|
+
|------------------------|-----------------------|-----------------------------------------|
|
|
263
|
+
| `attribute :full_name` | sim | `serializer_instance.full_name` |
|
|
264
|
+
| `attribute :full_name` | não | `object.full_name` (delegado ao model) |
|
|
265
|
+
| coluna no banco | — | `object.attribute` (direto) |
|
|
252
266
|
|
|
253
|
-
### Resolução
|
|
267
|
+
### Resolução do serializer de associações
|
|
254
268
|
|
|
255
|
-
Para cada associação, a
|
|
269
|
+
Para cada associação, o serializer resolve a classe na seguinte ordem:
|
|
256
270
|
|
|
257
|
-
1. `
|
|
258
|
-
2. `PostOpenapi` —
|
|
271
|
+
1. `PostSerializer` — tem `serialize`, usado diretamente.
|
|
272
|
+
2. `PostOpenapi` — é um `Controller`, delega para o `_resource`.
|
|
259
273
|
3. Fallback — chama `as_json` no valor da associação.
|
|
260
274
|
|
|
261
275
|
---
|
|
@@ -273,16 +287,16 @@ class User < ApplicationRecord
|
|
|
273
287
|
end
|
|
274
288
|
```
|
|
275
289
|
|
|
276
|
-
OpenapiBlocks gera:
|
|
290
|
+
O OpenapiBlocks gera:
|
|
277
291
|
|
|
278
|
-
- `User`
|
|
279
|
-
- `UserInput`
|
|
280
|
-
- `required` a partir
|
|
281
|
-
- `minLength
|
|
282
|
-
- `minimum
|
|
283
|
-
- `enum` a partir
|
|
284
|
-
- `format: "email"` a partir
|
|
285
|
-
- Todos os paths a partir
|
|
292
|
+
- Schema `User` a partir das colunas e tipos do `db/schema.rb`
|
|
293
|
+
- Schema `UserInput` para os request bodies de `POST`, `PUT` e `PATCH` (sem `id`, `created_at`, `updated_at` e campos `read_only`)
|
|
294
|
+
- Campos `required` a partir das validações `presence: true`
|
|
295
|
+
- `minLength`, `maxLength` a partir das validações `length`
|
|
296
|
+
- `minimum`, `maximum` a partir das validações `numericality`
|
|
297
|
+
- `enum` a partir das validações `inclusion`
|
|
298
|
+
- `format: "email"` a partir das validações de formato
|
|
299
|
+
- Todos os paths a partir do `config/routes.rb`
|
|
286
300
|
|
|
287
301
|
---
|
|
288
302
|
|
|
@@ -292,20 +306,20 @@ Configure esquemas de segurança globais no initializer:
|
|
|
292
306
|
|
|
293
307
|
```ruby
|
|
294
308
|
config.security do
|
|
295
|
-
bearer_token format: "JWT"
|
|
296
|
-
api_key name: "X-API-Key", in: :header
|
|
309
|
+
bearer_token format: "JWT" # Authorization: Bearer <token>
|
|
310
|
+
api_key name: "X-API-Key", in: :header # X-API-Key: <key>
|
|
297
311
|
end
|
|
298
312
|
```
|
|
299
313
|
|
|
300
|
-
|
|
314
|
+
Sobrescreva a segurança por operação:
|
|
301
315
|
|
|
302
316
|
```ruby
|
|
303
317
|
operation :index do
|
|
304
|
-
security :bearerAuth
|
|
318
|
+
security :bearerAuth # só bearer nesta operação
|
|
305
319
|
end
|
|
306
320
|
|
|
307
321
|
operation :show do
|
|
308
|
-
no_security!
|
|
322
|
+
no_security! # endpoint público — sem autenticação
|
|
309
323
|
end
|
|
310
324
|
```
|
|
311
325
|
|
|
@@ -314,60 +328,61 @@ end
|
|
|
314
328
|
## Associações
|
|
315
329
|
|
|
316
330
|
```ruby
|
|
317
|
-
association :company
|
|
318
|
-
association :posts, type: :array
|
|
319
|
-
association :posts, type: :array, read_only: true
|
|
331
|
+
association :company # belongs_to — $ref para schema Company
|
|
332
|
+
association :posts, type: :array # has_many — array de $ref para schema Post
|
|
333
|
+
association :posts, type: :array, read_only: true # excluído do UserInput (somente resposta)
|
|
320
334
|
```
|
|
321
335
|
|
|
322
336
|
---
|
|
323
337
|
|
|
324
338
|
## Atributos Virtuais
|
|
325
339
|
|
|
326
|
-
Atributos virtuais são campos que existem
|
|
340
|
+
Atributos virtuais são campos que existem na resposta da API mas não no banco de dados.
|
|
327
341
|
|
|
328
|
-
| Opção
|
|
329
|
-
|
|
330
|
-
| `read_only: true`
|
|
331
|
-
| `read_only: false` | Campos que o cliente pode enviar e receber | SIM
|
|
342
|
+
| Opção | Descrição | Aparece em User | Aparece em UserInput |
|
|
343
|
+
|--------------------|----------------------------------------|:---------------:|:--------------------:|
|
|
344
|
+
| `read_only: true` | Campos calculados ou gerados pelo sistema | SIM | NÃO |
|
|
345
|
+
| `read_only: false` | Campos que o cliente pode enviar e receber | SIM | SIM |
|
|
332
346
|
|
|
333
347
|
```ruby
|
|
334
|
-
attribute :full_name, type: :string, read_only: true
|
|
335
|
-
attribute :access_token, type: :string, read_only: true
|
|
336
|
-
attribute :nickname, type: :string
|
|
348
|
+
attribute :full_name, type: :string, read_only: true # somente resposta
|
|
349
|
+
attribute :access_token, type: :string, read_only: true # somente resposta
|
|
350
|
+
attribute :nickname, type: :string # requisição e resposta
|
|
337
351
|
```
|
|
338
352
|
|
|
339
353
|
---
|
|
340
354
|
|
|
341
|
-
## Mapeamento de
|
|
342
|
-
|
|
343
|
-
| Tipo
|
|
344
|
-
|
|
345
|
-
| integer
|
|
346
|
-
| bigint
|
|
347
|
-
| float
|
|
348
|
-
| decimal
|
|
349
|
-
| string
|
|
350
|
-
| text
|
|
351
|
-
| boolean
|
|
352
|
-
| date
|
|
353
|
-
| datetime
|
|
354
|
-
| uuid
|
|
355
|
-
| json / jsonb
|
|
355
|
+
## Mapeamento de Tipos
|
|
356
|
+
|
|
357
|
+
| Tipo ActiveRecord | Tipo OpenAPI |
|
|
358
|
+
|-------------------|------------------------|
|
|
359
|
+
| `integer` | `integer` / `int32` |
|
|
360
|
+
| `bigint` | `integer` / `int64` |
|
|
361
|
+
| `float` | `number` / `float` |
|
|
362
|
+
| `decimal` | `number` / `double` |
|
|
363
|
+
| `string` | `string` |
|
|
364
|
+
| `text` | `string` |
|
|
365
|
+
| `boolean` | `boolean` |
|
|
366
|
+
| `date` | `string` / `date` |
|
|
367
|
+
| `datetime` | `string` / `date-time` |
|
|
368
|
+
| `uuid` | `string` / `uuid` |
|
|
369
|
+
| `json` / `jsonb` | `object` |
|
|
356
370
|
|
|
357
371
|
---
|
|
358
372
|
|
|
359
|
-
##
|
|
373
|
+
## Recarregamento Automático em Desenvolvimento
|
|
360
374
|
|
|
361
|
-
OpenapiBlocks
|
|
375
|
+
O OpenapiBlocks monitora mudanças em:
|
|
362
376
|
|
|
363
377
|
```
|
|
378
|
+
app/serializers/**/*.rb
|
|
364
379
|
app/openapi/**/*.rb
|
|
365
380
|
app/models/**/*.rb
|
|
366
381
|
config/routes.rb
|
|
367
382
|
db/schema.rb
|
|
368
383
|
```
|
|
369
384
|
|
|
370
|
-
|
|
385
|
+
A spec é regenerada automaticamente na próxima requisição a `/docs/openapi.json` sempre que algum desses arquivos for alterado. Sem precisar reiniciar o servidor.
|
|
371
386
|
|
|
372
387
|
---
|
|
373
388
|
|
|
@@ -380,4 +395,4 @@ O spec é regenerado automaticamente na próxima requisição para `/docs/openap
|
|
|
380
395
|
|
|
381
396
|
## Licença
|
|
382
397
|
|
|
383
|
-
MIT
|
|
398
|
+
[MIT](LICENSE.txt)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require "action_controller/api"
|
|
4
4
|
|
|
5
5
|
module OpenapiBlocks
|
|
6
|
-
class SpecController < ActionController::API # rubocop:disable Style/Documentation
|
|
6
|
+
class SpecController < ActionController::API # rubocop:disable Style/Documentation,Metrics/ClassLength
|
|
7
7
|
SWAGGER_UI_CSS = "https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui.css"
|
|
8
8
|
SWAGGER_UI_STANDALONE_JS = "https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-standalone-preset.js"
|
|
9
9
|
SWAGGER_UI_JS = "https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-bundle.js"
|
|
@@ -30,7 +30,7 @@ module OpenapiBlocks
|
|
|
30
30
|
|
|
31
31
|
private
|
|
32
32
|
|
|
33
|
-
def scalar_html
|
|
33
|
+
def scalar_html # rubocop:disable Metrics/MethodLength
|
|
34
34
|
spec_url = "#{swagger_spec_base_url}.json"
|
|
35
35
|
title = "#{OpenapiBlocks.configuration.info.title} - Scalar"
|
|
36
36
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenapiBlocks
|
|
4
|
+
module AutoSerialize # rubocop:disable Style/Documentation
|
|
5
|
+
def render(options = nil, extra = nil, &) # rubocop:disable Metrics/MethodLength
|
|
6
|
+
if auto_serialize_candidate?(options)
|
|
7
|
+
object = options[:json]
|
|
8
|
+
serializer = Registry.resolve(object)
|
|
9
|
+
|
|
10
|
+
if serializer
|
|
11
|
+
log_serializer(object, serializer)
|
|
12
|
+
options = options.merge(json: serializer.serialize(object))
|
|
13
|
+
else
|
|
14
|
+
warn_no_serializer(object)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def auto_serialize_candidate?(options)
|
|
24
|
+
OpenapiBlocks.configuration.auto_serialize &&
|
|
25
|
+
options.is_a?(Hash) &&
|
|
26
|
+
options.key?(:json)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def log_serializer(object, serializer)
|
|
30
|
+
model = extract_model(object)
|
|
31
|
+
Rails.logger.debug(
|
|
32
|
+
"[OpenapiBlocks] #{model.name} serialized by #{serializer.name}"
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def warn_no_serializer(object)
|
|
37
|
+
model = extract_model(object)
|
|
38
|
+
return unless model
|
|
39
|
+
|
|
40
|
+
Rails.logger.warn(
|
|
41
|
+
"[OpenapiBlocks] No serializer found for #{model.name}. " \
|
|
42
|
+
"Falling back to default Rails rendering. " \
|
|
43
|
+
"Create #{model.name}Serializer or use `serializes #{model.name}` explicitly."
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def extract_model(object)
|
|
48
|
+
case object
|
|
49
|
+
when Array then object.first&.class
|
|
50
|
+
else object.respond_to?(:klass) ? object.klass : object.class
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/openapi_blocks/base.rb
CHANGED
|
@@ -1,48 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module OpenapiBlocks
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
# <b>DEPRECATED:</b> please use <tt>OpenapiBlocks::Controllers</tt> and <tt>OpenapiBlocks::Resources</tt> instead.
|
|
5
|
+
class Base
|
|
6
|
+
include Concerns::Schemable
|
|
7
|
+
include Concerns::Documentable
|
|
8
|
+
include Serialization
|
|
6
9
|
|
|
7
10
|
class << self
|
|
8
|
-
attr_reader :_model, :_ignored, :_associations, :_virtual_attributes, :_operations, :_tags
|
|
9
|
-
|
|
10
|
-
def model(klass = nil)
|
|
11
|
-
klass ? @_model = klass : @_model ||= infer_model # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def ignore(*attributes)
|
|
15
|
-
@_ignored ||= []
|
|
16
|
-
@_ignored.concat(attributes.map(&:to_s))
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def association(name, type: nil, read_only: false)
|
|
20
|
-
@_associations ||= []
|
|
21
|
-
@_associations << { name: name, type: type, read_only: read_only }
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def attribute(name, **)
|
|
25
|
-
@_virtual_attributes ||= []
|
|
26
|
-
@_virtual_attributes << ({ name: name, ** })
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def operation(action, &block)
|
|
30
|
-
@_operations ||= {}
|
|
31
|
-
builder = OperationBuilder.new
|
|
32
|
-
builder.instance_eval(&block) if block
|
|
33
|
-
@_operations[action] = builder
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def tags(*values)
|
|
37
|
-
values.any? ? @_tags = values : @_tags
|
|
38
|
-
end
|
|
39
|
-
|
|
40
11
|
private
|
|
41
12
|
|
|
42
13
|
def infer_model
|
|
43
14
|
model_name = name
|
|
44
15
|
.gsub(/Openapi$/, "")
|
|
45
|
-
.gsub(/
|
|
16
|
+
.gsub(/Serializer$/, "")
|
|
46
17
|
.split("::")
|
|
47
18
|
.last
|
|
48
19
|
|
|
@@ -4,21 +4,53 @@ require_relative "spec/document"
|
|
|
4
4
|
|
|
5
5
|
module OpenapiBlocks
|
|
6
6
|
class Builder # rubocop:disable Style/Documentation
|
|
7
|
+
REQUIRED_CONFIG_ERROR = <<~MSG
|
|
8
|
+
OpenapiBlocks is not configured. Add an initializer:
|
|
9
|
+
|
|
10
|
+
# config/initializers/openapi_blocks.rb
|
|
11
|
+
OpenapiBlocks.configure do |config|
|
|
12
|
+
config.openapi_version = "3.1.0" # required: "3.0.3" or "3.1.0"
|
|
13
|
+
|
|
14
|
+
config.info do
|
|
15
|
+
title "My API" # required
|
|
16
|
+
version "1.0.0" # required
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
MSG
|
|
20
|
+
|
|
7
21
|
def self.build
|
|
8
22
|
new.build
|
|
9
23
|
end
|
|
10
24
|
|
|
11
25
|
def build
|
|
26
|
+
validate_configuration!
|
|
12
27
|
Spec::Document.new(openapi_classes).build
|
|
13
28
|
end
|
|
14
29
|
|
|
15
30
|
private
|
|
16
31
|
|
|
32
|
+
def validate_configuration! # rubocop:disable Metrics/CyclomaticComplexity
|
|
33
|
+
config = OpenapiBlocks.configuration
|
|
34
|
+
errors = []
|
|
35
|
+
|
|
36
|
+
unless config.configured?
|
|
37
|
+
errors << "config.openapi_version or config.info must be defined — call OpenapiBlocks.configure"
|
|
38
|
+
end
|
|
39
|
+
errors << "config.info.title is required" if config.info&.title.blank?
|
|
40
|
+
errors << "config.info.version is required" if config.info&.version.blank?
|
|
41
|
+
|
|
42
|
+
return if errors.empty?
|
|
43
|
+
|
|
44
|
+
raise Error, "#{REQUIRED_CONFIG_ERROR}\nMissing:\n#{errors.map { |e| " - #{e}" }.join("\n")}"
|
|
45
|
+
end
|
|
46
|
+
|
|
17
47
|
def openapi_classes
|
|
18
48
|
ObjectSpace.each_object(Class).select do |klass|
|
|
19
49
|
name = Module.instance_method(:name).bind_call(klass)
|
|
20
|
-
name&.end_with?("Openapi")
|
|
21
|
-
|
|
50
|
+
next unless name&.end_with?("Openapi")
|
|
51
|
+
|
|
52
|
+
klass < OpenapiBlocks::Base ||
|
|
53
|
+
klass < OpenapiBlocks::Controller
|
|
22
54
|
end
|
|
23
55
|
end
|
|
24
56
|
end
|