api-regulator 0.1.26 → 0.1.28
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/Gemfile.lock +1 -1
- data/README.md +480 -92
- data/lib/api_regulator/api.rb +4 -3
- data/lib/api_regulator/controller_mixin.rb +8 -8
- data/lib/api_regulator/dsl.rb +2 -1
- data/lib/api_regulator/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4adbf41faf31da6c2dfd0ac4fa34753b1b145e4a54fe150716b248c0bb2ca8f
|
4
|
+
data.tar.gz: 3bf906b1cc68f8729c8593fda4af679525999b6d4ed27f8036b96a9b4a90466a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 92cf6b80afec1af5113edb3ac6dfd64211931a3c47ade5b5aad032ed6c35343357cc158eebe7e5a27fb4a65ca600584bb64cbfaf36eb6532ebd721c98588157d
|
7
|
+
data.tar.gz: 1a5e008679121223946897c93b2a89efd1be92c267b4b5ffb07e484c35938236f842ba1fd2e9eea9c5de60bc1a5e931a95f207e348abe4062fde7a4f2d6a1227
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,25 +1,21 @@
|
|
1
1
|
# ApiRegulator
|
2
2
|
|
3
|
-
ApiRegulator is a Ruby gem designed to **document** and **
|
3
|
+
ApiRegulator is a Ruby gem designed to **document**, **validate**, and **generate OpenAPI schemas** for Rails APIs. It provides a clean, Rails-friendly DSL for defining API endpoints, parameter validations, response schemas, and webhook definitions directly in your controllers, while automatically generating OpenAPI 3.1.0-compliant documentation.
|
4
4
|
|
5
|
-
ApiRegulator
|
5
|
+
ApiRegulator leverages **Active Model validations** for parameter validation, making it familiar and intuitive for Rails developers.
|
6
6
|
|
7
7
|
## Features
|
8
8
|
|
9
|
-
-
|
10
|
-
|
11
|
-
|
12
|
-
-
|
13
|
-
|
14
|
-
|
15
|
-
-
|
16
|
-
|
17
|
-
|
18
|
-
-
|
19
|
-
Share common response schemas across multiple endpoints for DRY definitions.
|
20
|
-
|
21
|
-
## ToDo
|
22
|
-
- [ ] Handling of extra undocumented params
|
9
|
+
- **🎯 API Documentation DSL**: Define API endpoints, request parameters, and response schemas using an intuitive, Rails-friendly DSL
|
10
|
+
- **✅ Dynamic Request Validation**: Automatically validate incoming requests against defined parameters using Active Model validations
|
11
|
+
- **📖 OpenAPI 3.1.0 Generation**: Generate fully compliant OpenAPI documentation ready for Swagger, Postman, or ReadMe
|
12
|
+
- **🔗 Webhook Documentation**: Define and document webhook payloads with the same DSL
|
13
|
+
- **🏷️ API Versioning**: Support multiple API versions with selective parameter inclusion
|
14
|
+
- **🔒 Security Schemes**: Define authentication and authorization schemes
|
15
|
+
- **🔄 Shared Schemas**: Reuse common response and parameter schemas across multiple endpoints
|
16
|
+
- **📚 ReadMe Integration**: Built-in tasks for uploading documentation to ReadMe.com
|
17
|
+
- **🛡️ Parameter Validation**: Strict validation with helpful error messages for unexpected parameters
|
18
|
+
- **📄 Custom Page Management**: Upload and manage documentation pages alongside API specs
|
23
19
|
|
24
20
|
## Installation
|
25
21
|
|
@@ -31,131 +27,523 @@ gem 'api_regulator'
|
|
31
27
|
|
32
28
|
Run `bundle install` to install the gem.
|
33
29
|
|
34
|
-
|
35
30
|
## Setup
|
36
31
|
|
37
|
-
1.
|
38
|
-
|
39
|
-
Add the following to `config/initializers/api_regulator.rb`:
|
32
|
+
### 1. Create an Initializer
|
40
33
|
|
41
|
-
|
42
|
-
ApiRegulator.configure do |config|
|
43
|
-
config.api_base_url = "/api/v1" # Set a common base path for your API endpoints
|
44
|
-
config.docs_path = Rails.root.join("doc").to_s # Path for folder for docs
|
45
|
-
config.app_name = "My API" # shows in docs
|
34
|
+
Add the following to `config/initializers/api_regulator.rb`:
|
46
35
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
36
|
+
```ruby
|
37
|
+
ApiRegulator.configure do |config|
|
38
|
+
config.api_base_url = "/api/v1"
|
39
|
+
config.docs_path = Rails.root.join("doc").to_s
|
40
|
+
config.app_name = "My API"
|
41
|
+
|
42
|
+
# Optional: Configure multiple API versions
|
43
|
+
config.versions = {
|
44
|
+
"v1.0" => "abc123", # ReadMe API spec ID
|
45
|
+
"v1.0-internal" => "abc345" # Internal version
|
46
|
+
}
|
47
|
+
config.default_version = "v1.0-internal"
|
48
|
+
|
49
|
+
# Optional: Define API servers
|
50
|
+
config.servers = [
|
51
|
+
{ url: "https://stg.example.com", description: "Staging", "x-default": true },
|
52
|
+
{ url: "https://example.com", description: "Production" }
|
53
|
+
]
|
54
|
+
end
|
55
|
+
```
|
53
56
|
|
54
|
-
|
55
|
-
{ url: "https://stg.example.com", description: "Staging", "x-default": true },
|
56
|
-
{ url: "https://example.com", description: "Production" }
|
57
|
-
]
|
58
|
-
end
|
59
|
-
```
|
57
|
+
### 2. Include the DSL in Your Base Controller
|
60
58
|
|
61
|
-
|
59
|
+
```ruby
|
60
|
+
class Api::ApplicationController < ActionController::API
|
61
|
+
include ApiRegulator::DSL
|
62
|
+
include ApiRegulator::ControllerMixin
|
63
|
+
end
|
64
|
+
```
|
62
65
|
|
63
|
-
|
66
|
+
### 3. Define Security Schemes (Optional)
|
64
67
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
68
|
+
```ruby
|
69
|
+
# In your initializer
|
70
|
+
ApiRegulator.security_schemes = {
|
71
|
+
bearer_auth: {
|
72
|
+
type: "http",
|
73
|
+
scheme: "bearer",
|
74
|
+
bearerFormat: "JWT"
|
75
|
+
},
|
76
|
+
api_key: {
|
77
|
+
type: "apiKey",
|
78
|
+
in: "header",
|
79
|
+
name: "X-API-Key"
|
80
|
+
}
|
81
|
+
}
|
82
|
+
```
|
71
83
|
|
72
84
|
## Usage
|
73
85
|
|
74
|
-
|
75
|
-
|
76
|
-
Use the DSL in your controllers to define API endpoints, request parameters, and response schemas.
|
86
|
+
### Basic API Definition
|
77
87
|
|
78
88
|
```ruby
|
79
89
|
class Api::V1::CustomersController < Api::ApplicationController
|
80
|
-
api self, :create, "
|
90
|
+
api self, :create, desc: "Create a new customer", title: "Create Customer" do
|
91
|
+
# Path parameters
|
92
|
+
param :organization_id, :string, location: :path, presence: true
|
93
|
+
|
94
|
+
# Query parameters
|
95
|
+
param :expand, :string, location: :query, desc: "Comma-separated list of fields to expand"
|
96
|
+
|
97
|
+
# Body parameters
|
81
98
|
param :customer, presence: true do
|
82
99
|
param :first_name, :string, presence: true
|
83
100
|
param :last_name, :string, presence: true
|
84
|
-
param :email, :string, presence: true, format: { with:
|
85
|
-
param :ssn, :string, presence: true, length: {
|
101
|
+
param :email, :string, presence: true, format: { with: ApiRegulator::Formats::EMAIL }
|
102
|
+
param :ssn, :string, presence: true, length: { is: 9 }
|
103
|
+
param :date_of_birth, :string, format: { with: ApiRegulator::Formats::DATE }
|
104
|
+
|
105
|
+
# Nested objects
|
106
|
+
param :address, :object do
|
107
|
+
param :street, :string, presence: true
|
108
|
+
param :city, :string, presence: true
|
109
|
+
param :state, :string, presence: true, length: { is: 2 }
|
110
|
+
param :zip_code, :string, format: { with: ApiRegulator::Formats::ZIP_CODE }
|
111
|
+
end
|
112
|
+
|
113
|
+
# Arrays
|
114
|
+
param :phone_numbers, :array, item_type: :string
|
115
|
+
param :emergency_contacts, :array do
|
116
|
+
param :name, :string, presence: true
|
117
|
+
param :relationship, :string, presence: true
|
118
|
+
param :phone, :string, presence: true
|
119
|
+
end
|
86
120
|
end
|
87
121
|
|
88
|
-
|
122
|
+
# Response definitions
|
123
|
+
response 201, "Customer successfully created" do
|
89
124
|
param :customer do
|
90
|
-
param :id, :string, desc: "Customer
|
91
|
-
param :email, :string, desc: "Customer email"
|
125
|
+
param :id, :string, desc: "Customer UUID"
|
126
|
+
param :email, :string, desc: "Customer email address"
|
127
|
+
param :created_at, :string, desc: "ISO 8601 timestamp"
|
92
128
|
end
|
93
129
|
end
|
94
130
|
|
95
131
|
response 422, ref: :validation_errors
|
132
|
+
response 401, ref: :unauthorized_error
|
96
133
|
end
|
97
134
|
|
98
135
|
def create
|
99
|
-
validate_params! #
|
100
|
-
|
101
|
-
customer = Customer.create!(api_params[:customer])
|
102
|
-
render json: customer, status: :
|
136
|
+
validate_params! # Validates against DSL definition
|
137
|
+
|
138
|
+
customer = Customer.create!(api_params[:customer])
|
139
|
+
render json: { customer: customer }, status: :created
|
103
140
|
end
|
104
141
|
end
|
105
142
|
```
|
106
143
|
|
107
|
-
|
144
|
+
### Advanced Parameter Options
|
145
|
+
|
146
|
+
#### Conditional Requirements
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
param :ssn, :string, presence: {
|
150
|
+
required_on: [:create, :update], # Required only for these actions
|
151
|
+
required_except_on: [:show] # Required except for these actions
|
152
|
+
}
|
153
|
+
```
|
108
154
|
|
109
|
-
|
155
|
+
#### Version-Specific Parameters
|
110
156
|
|
111
157
|
```ruby
|
158
|
+
param :legacy_field, :string, versions: [:v1], desc: "Only available in v1"
|
159
|
+
param :new_feature, :boolean, versions: [:v2, :v3], desc: "Available in v2 and v3"
|
160
|
+
```
|
161
|
+
|
162
|
+
#### Type Validation and Formatting
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
param :age, :integer, numericality: { greater_than: 0, less_than: 150 }
|
166
|
+
param :website, :string, format: { with: ApiRegulator::Formats::URI }
|
167
|
+
param :score, :number, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }
|
168
|
+
param :is_active, :boolean
|
169
|
+
param :tags, :array, item_type: :string, inclusion: { in: ["vip", "standard", "premium"] }
|
170
|
+
```
|
171
|
+
|
172
|
+
#### Arbitrary Keys Support
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
param :metadata, :object, allow_arbitrary_keys: true do
|
176
|
+
param :created_by, :string # Known fields can still be defined
|
177
|
+
end
|
178
|
+
```
|
179
|
+
|
180
|
+
#### Nullable Fields
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
param :middle_name, :string, presence: { allow_nil: true }
|
184
|
+
param :optional_date, :string, format: { with: ApiRegulator::Formats::DATE, allow_nil: true }
|
185
|
+
```
|
186
|
+
|
187
|
+
### Shared Schemas
|
188
|
+
|
189
|
+
Define reusable schemas in your initializer:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
# Common error responses
|
112
193
|
ApiRegulator.register_shared_schema :validation_errors, "Validation error response" do
|
113
|
-
param :errors, :array, desc: "Array of validation
|
194
|
+
param :errors, :array, desc: "Array of validation error messages" do
|
195
|
+
param :field, :string, desc: "Field name that failed validation"
|
196
|
+
param :message, :string, desc: "Human-readable error message"
|
197
|
+
param :code, :string, desc: "Error code for programmatic handling"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
ApiRegulator.register_shared_schema :unauthorized_error, "Authentication required" do
|
202
|
+
param :error, :string, desc: "Error message"
|
203
|
+
param :code, :integer, desc: "HTTP status code"
|
204
|
+
end
|
205
|
+
|
206
|
+
# Common response objects
|
207
|
+
ApiRegulator.register_shared_schema :pagination_meta, "Pagination metadata" do
|
208
|
+
param :current_page, :integer, desc: "Current page number"
|
209
|
+
param :per_page, :integer, desc: "Items per page"
|
210
|
+
param :total_pages, :integer, desc: "Total number of pages"
|
211
|
+
param :total_count, :integer, desc: "Total number of items"
|
114
212
|
end
|
115
213
|
```
|
116
214
|
|
117
|
-
|
215
|
+
Use shared schemas in your API definitions:
|
216
|
+
|
118
217
|
```ruby
|
119
|
-
|
218
|
+
api self, :index do
|
219
|
+
param :page, :integer, location: :query, desc: "Page number"
|
220
|
+
param :per_page, :integer, location: :query, desc: "Items per page"
|
221
|
+
|
222
|
+
response 200, "List of customers" do
|
223
|
+
param :customers, :array do
|
224
|
+
ref :customer_summary # Reference another shared schema
|
225
|
+
end
|
226
|
+
ref :pagination_meta
|
227
|
+
end
|
120
228
|
end
|
121
229
|
```
|
122
230
|
|
123
|
-
|
231
|
+
### Webhook Documentation
|
124
232
|
|
125
|
-
|
233
|
+
Define webhook payloads using the same DSL:
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
class WebhookDefinitions < Api::ApplicationController
|
237
|
+
webhook :customer_created,
|
238
|
+
desc: "Fired when a new customer is created",
|
239
|
+
title: "Customer Created",
|
240
|
+
tags: ["customers", "webhooks"] do
|
241
|
+
|
242
|
+
param :event, :string, desc: "Event name"
|
243
|
+
param :timestamp, :string, desc: "ISO 8601 timestamp"
|
244
|
+
param :data do
|
245
|
+
param :customer do
|
246
|
+
param :id, :string, desc: "Customer UUID"
|
247
|
+
param :email, :string, desc: "Customer email"
|
248
|
+
param :created_at, :string, desc: "ISO 8601 timestamp"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
example :basic_example, {
|
253
|
+
event: "customer.created",
|
254
|
+
timestamp: "2024-01-15T10:30:00Z",
|
255
|
+
data: {
|
256
|
+
customer: {
|
257
|
+
id: "cust_abc123",
|
258
|
+
email: "john@example.com",
|
259
|
+
created_at: "2024-01-15T10:30:00Z"
|
260
|
+
}
|
261
|
+
}
|
262
|
+
}, default: true
|
263
|
+
end
|
264
|
+
end
|
265
|
+
```
|
126
266
|
|
267
|
+
### API Examples
|
127
268
|
|
128
|
-
|
129
|
-
```bash
|
130
|
-
rake api_docs:generate # uses default (or no) version
|
131
|
-
VERSION=v1.0-internal rake api_docs:generate # specifies the to generate
|
132
|
-
```
|
269
|
+
Add examples to your API definitions:
|
133
270
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
271
|
+
```ruby
|
272
|
+
api self, :create do
|
273
|
+
param :customer do
|
274
|
+
param :name, :string, presence: true
|
275
|
+
param :email, :string, presence: true
|
276
|
+
end
|
277
|
+
|
278
|
+
example :successful_creation, {
|
279
|
+
customer: {
|
280
|
+
name: "John Doe",
|
281
|
+
email: "john@example.com"
|
282
|
+
}
|
283
|
+
}, default: true
|
284
|
+
|
285
|
+
example :with_optional_fields, {
|
286
|
+
customer: {
|
287
|
+
name: "Jane Smith",
|
288
|
+
email: "jane@example.com",
|
289
|
+
phone: "+1-555-0123"
|
290
|
+
}
|
291
|
+
}
|
292
|
+
end
|
293
|
+
```
|
294
|
+
|
295
|
+
## Validation and Error Handling
|
296
|
+
|
297
|
+
### Automatic Parameter Validation
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
def create
|
301
|
+
validate_params! # Raises ApiRegulator::InvalidParams or ApiRegulator::UnexpectedParams
|
302
|
+
|
303
|
+
# Access validated and type-converted parameters
|
304
|
+
customer_data = api_params[:customer]
|
305
|
+
# Process with confidence that data is valid
|
306
|
+
end
|
307
|
+
```
|
308
|
+
|
309
|
+
### Custom Error Handling
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
class Api::ApplicationController < ActionController::API
|
313
|
+
include ApiRegulator::DSL
|
314
|
+
include ApiRegulator::ControllerMixin
|
315
|
+
|
316
|
+
rescue_from ApiRegulator::InvalidParams do |exception|
|
317
|
+
render json: {
|
318
|
+
errors: exception.errors.messages,
|
319
|
+
message: "Validation failed"
|
320
|
+
}, status: :unprocessable_entity
|
321
|
+
end
|
322
|
+
|
323
|
+
rescue_from ApiRegulator::UnexpectedParams do |exception|
|
324
|
+
render json: {
|
325
|
+
errors: exception.details,
|
326
|
+
message: "Unexpected parameters provided"
|
327
|
+
}, status: :bad_request
|
328
|
+
end
|
329
|
+
end
|
330
|
+
```
|
139
331
|
|
140
|
-
|
141
|
-
```bash
|
142
|
-
rake api_docs:publish
|
143
|
-
```
|
332
|
+
## OpenAPI Documentation Generation
|
144
333
|
|
145
|
-
|
146
|
-
1. Fork the repository.
|
147
|
-
2. Create your feature branch (git checkout -b feature/new-feature).
|
148
|
-
3. Commit your changes (git commit -am 'Add some feature').
|
149
|
-
4. Push to the branch (git push origin feature/new-feature).
|
150
|
-
5. Open a pull request.
|
334
|
+
### Generate Documentation
|
151
335
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
336
|
+
```bash
|
337
|
+
# Generate for default version
|
338
|
+
rake api_docs:generate
|
339
|
+
|
340
|
+
# Generate for specific version
|
341
|
+
VERSION=v1.0 rake api_docs:generate
|
342
|
+
|
343
|
+
# Generate for all configured versions
|
344
|
+
rake api_docs:generate_all
|
345
|
+
```
|
346
|
+
|
347
|
+
### Upload to ReadMe
|
348
|
+
|
349
|
+
Set up your ReadMe credentials:
|
350
|
+
|
351
|
+
```bash
|
352
|
+
export RDME_API_KEY="your_readme_api_key"
|
353
|
+
```
|
354
|
+
|
355
|
+
```bash
|
356
|
+
# Upload API specification
|
357
|
+
rake api_docs:upload
|
358
|
+
|
359
|
+
# Upload custom documentation pages (with YAML frontmatter)
|
360
|
+
rake api_docs:upload_pages
|
361
|
+
|
362
|
+
# Generate and upload everything
|
363
|
+
rake api_docs:publish
|
364
|
+
```
|
365
|
+
|
366
|
+
### Custom Documentation Pages
|
367
|
+
|
368
|
+
Create markdown files in your `docs_path` with YAML frontmatter:
|
369
|
+
|
370
|
+
```markdown
|
371
|
+
---
|
372
|
+
title: "Getting Started"
|
373
|
+
slug: "getting-started"
|
374
|
+
category: "documentation"
|
375
|
+
hidden: false
|
376
|
+
---
|
377
|
+
|
378
|
+
# Getting Started
|
379
|
+
|
380
|
+
Your API documentation content here...
|
381
|
+
```
|
382
|
+
|
383
|
+
### ReadMe Integration Features
|
384
|
+
|
385
|
+
```bash
|
386
|
+
# Fetch available categories
|
387
|
+
rake api_docs:fetch_categories
|
388
|
+
|
389
|
+
# Upload with specific version
|
390
|
+
VERSION=v2.0 rake api_docs:upload
|
391
|
+
```
|
392
|
+
|
393
|
+
## Built-in Format Validators
|
394
|
+
|
395
|
+
ApiRegulator includes common format validators:
|
396
|
+
|
397
|
+
```ruby
|
398
|
+
ApiRegulator::Formats::DATE # ISO 8601 date format
|
399
|
+
ApiRegulator::Formats::DATETIME # ISO 8601 datetime format
|
400
|
+
ApiRegulator::Formats::EMAIL # Email address validation
|
401
|
+
ApiRegulator::Formats::ZIP_CODE # US ZIP code (12345 or 12345-6789)
|
402
|
+
ApiRegulator::Formats::URI # URI format validation
|
403
|
+
```
|
404
|
+
|
405
|
+
Usage example:
|
406
|
+
|
407
|
+
```ruby
|
408
|
+
param :email, :string, format: { with: ApiRegulator::Formats::EMAIL }
|
409
|
+
param :website, :string, format: { with: ApiRegulator::Formats::URI }
|
410
|
+
param :birth_date, :string, format: { with: ApiRegulator::Formats::DATE }
|
411
|
+
```
|
412
|
+
|
413
|
+
## Testing Your APIs
|
414
|
+
|
415
|
+
ApiRegulator integrates seamlessly with RSpec:
|
416
|
+
|
417
|
+
```ruby
|
418
|
+
RSpec.describe Api::V1::CustomersController do
|
419
|
+
describe "POST /api/v1/customers" do
|
420
|
+
it "validates required parameters" do
|
421
|
+
post "/api/v1/customers", params: {}
|
422
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
423
|
+
end
|
424
|
+
|
425
|
+
it "creates customer with valid parameters" do
|
426
|
+
params = {
|
427
|
+
customer: {
|
428
|
+
first_name: "John",
|
429
|
+
last_name: "Doe",
|
430
|
+
email: "john@example.com"
|
431
|
+
}
|
432
|
+
}
|
433
|
+
|
434
|
+
post "/api/v1/customers", params: params
|
435
|
+
expect(response).to have_http_status(:created)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
```
|
440
|
+
|
441
|
+
## Configuration Reference
|
442
|
+
|
443
|
+
```ruby
|
444
|
+
ApiRegulator.configure do |config|
|
445
|
+
# Base URL for all API endpoints (default: "api/v1")
|
446
|
+
config.api_base_url = "/api/v1"
|
447
|
+
|
448
|
+
# Application name shown in documentation (default: "API Documentation")
|
449
|
+
config.app_name = "My API"
|
450
|
+
|
451
|
+
# Directory for documentation files (default: "doc")
|
452
|
+
config.docs_path = Rails.root.join("doc").to_s
|
453
|
+
|
454
|
+
# Version mapping for ReadMe (optional)
|
455
|
+
config.versions = {
|
456
|
+
"v1.0" => "readme_spec_id_1",
|
457
|
+
"v2.0" => "readme_spec_id_2"
|
458
|
+
}
|
459
|
+
|
460
|
+
# Default version when none specified (optional)
|
461
|
+
config.default_version = "v1.0"
|
462
|
+
|
463
|
+
# Server definitions for OpenAPI spec (optional)
|
464
|
+
config.servers = [
|
465
|
+
{ url: "https://api.example.com", description: "Production" },
|
466
|
+
{ url: "https://staging-api.example.com", description: "Staging" }
|
467
|
+
]
|
468
|
+
end
|
469
|
+
```
|
470
|
+
|
471
|
+
## Advanced Usage
|
472
|
+
|
473
|
+
### Multiple API Versions
|
474
|
+
|
475
|
+
```ruby
|
476
|
+
# Define version-specific endpoints
|
477
|
+
api self, :create, versions: [:v1, :v2] do
|
478
|
+
param :name, :string, presence: true
|
479
|
+
param :email, :string, presence: true, versions: [:v1, :v2]
|
480
|
+
param :phone, :string, versions: [:v2] # Only in v2
|
481
|
+
end
|
482
|
+
```
|
483
|
+
|
484
|
+
### Security Requirements
|
485
|
+
|
486
|
+
```ruby
|
487
|
+
api self, :create do
|
488
|
+
# This endpoint requires authentication
|
489
|
+
security [{ bearer_auth: [] }]
|
490
|
+
|
491
|
+
param :customer do
|
492
|
+
param :name, :string, presence: true
|
493
|
+
end
|
494
|
+
end
|
495
|
+
```
|
496
|
+
|
497
|
+
### Custom Validation Context
|
498
|
+
|
499
|
+
```ruby
|
500
|
+
def update
|
501
|
+
# Validate with specific context
|
502
|
+
validate_params!
|
503
|
+
|
504
|
+
# The validation context is automatically set to the action name (:update)
|
505
|
+
# This allows conditional validations based on the action
|
506
|
+
end
|
507
|
+
```
|
508
|
+
|
509
|
+
## Error Reference
|
510
|
+
|
511
|
+
- `ApiRegulator::InvalidParams`: Raised when request parameters fail validation
|
512
|
+
- `ApiRegulator::UnexpectedParams`: Raised when unexpected parameters are provided
|
513
|
+
- `ApiRegulator::ValidationError`: Base class for validation errors
|
514
|
+
|
515
|
+
## Development and Contributing
|
516
|
+
|
517
|
+
1. Fork the repository
|
518
|
+
2. Create your feature branch (`git checkout -b feature/new-feature`)
|
519
|
+
3. Write tests for your changes
|
520
|
+
4. Ensure all tests pass (`bundle exec rspec`)
|
521
|
+
5. Commit your changes (`git commit -am 'Add some feature'`)
|
522
|
+
6. Push to the branch (`git push origin feature/new-feature`)
|
523
|
+
7. Open a pull request
|
524
|
+
|
525
|
+
### Running Tests
|
526
|
+
|
527
|
+
```bash
|
528
|
+
bundle install
|
529
|
+
bundle exec rspec
|
530
|
+
```
|
531
|
+
|
532
|
+
## Releasing New Versions
|
533
|
+
|
534
|
+
ApiRegulator is published to [RubyGems](https://rubygems.org/gems/api-regulator). To release:
|
535
|
+
|
536
|
+
1. Update version in `lib/api_regulator/version.rb`
|
537
|
+
2. Run `bundle install` to update `Gemfile.lock`
|
538
|
+
3. Create PR and merge after review
|
539
|
+
4. Trigger the [Release Gem workflow](https://github.com/Stellarcred/api-regulator/actions/workflows/release.yml)
|
540
|
+
5. Verify publication on [RubyGems](https://rubygems.org/gems/api-regulator)
|
541
|
+
|
542
|
+
## Requirements
|
543
|
+
|
544
|
+
- Ruby >= 3.0
|
545
|
+
- Rails >= 8.0 (ActiveSupport and ActiveModel)
|
159
546
|
|
160
547
|
## License
|
548
|
+
|
161
549
|
This gem is available as open-source software under the [MIT License](https://mit-license.org/).
|
data/lib/api_regulator/api.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
module ApiRegulator
|
2
2
|
class Api
|
3
3
|
attr_reader :controller_class, :controller_path, :controller_name, :action_name, :description,
|
4
|
-
:title, :params, :responses, :versions, :examples
|
4
|
+
:title, :params, :responses, :versions, :examples, :tags
|
5
5
|
|
6
|
-
def initialize(controller_class, action_name, desc: nil, title: nil, versions: [], &block)
|
6
|
+
def initialize(controller_class, action_name, desc: nil, title: nil, versions: [], tags: [], &block)
|
7
7
|
@controller_class = controller_class
|
8
8
|
@controller_name = controller_class.name
|
9
9
|
@controller_path = controller_class.controller_path
|
10
10
|
@action_name = action_name.to_s
|
11
11
|
@description = desc
|
12
12
|
@title = title
|
13
|
+
@tags = tags.presence || default_tags
|
13
14
|
@versions = Array(versions).map(&:to_sym)
|
14
15
|
|
15
16
|
@params = []
|
@@ -88,7 +89,7 @@ module ApiRegulator
|
|
88
89
|
"#{controller_path.gsub("/", "-")}-#{action_name}"
|
89
90
|
end
|
90
91
|
|
91
|
-
def
|
92
|
+
def default_tags
|
92
93
|
[
|
93
94
|
controller_name
|
94
95
|
.demodulize
|
@@ -29,11 +29,11 @@ module ApiRegulator
|
|
29
29
|
def api_params
|
30
30
|
path_params = api_definition.params.select(&:path?).each_with_object({}) do |param, hash|
|
31
31
|
hash[param.name.to_sym] = params[param.name] if params.key?(param.name)
|
32
|
-
end
|
32
|
+
end
|
33
33
|
|
34
34
|
query_params = api_definition.params.select(&:query?).each_with_object({}) do |param, hash|
|
35
35
|
hash[param.name.to_sym] = params[param.name] if params.key?(param.name)
|
36
|
-
end
|
36
|
+
end
|
37
37
|
|
38
38
|
body_params = api_definition.params.select(&:body?)
|
39
39
|
permitted_body = body_params.each_with_object({}) do |param, hash|
|
@@ -42,28 +42,28 @@ module ApiRegulator
|
|
42
42
|
allowed_params = [build_permitted_keys(param.children)]
|
43
43
|
|
44
44
|
if param.required?
|
45
|
-
nested = params.expect(param.name => allowed_params).map { |set| set.to_h
|
45
|
+
nested = params.expect(param.name => allowed_params).map { |set| set.to_h }
|
46
46
|
hash[param.name.to_sym] = nested
|
47
47
|
else
|
48
|
-
nested = params.permit(param.name => allowed_params).map { |set| set.to_h
|
48
|
+
nested = params.permit(param.name => allowed_params).map { |set| set.to_h }
|
49
49
|
hash.merge!(nested)
|
50
50
|
end
|
51
51
|
when :object
|
52
52
|
allowed_params = build_permitted_keys(param.children)
|
53
53
|
|
54
54
|
if param.required?
|
55
|
-
nested = params.expect(param.name => allowed_params).to_h
|
55
|
+
nested = params.expect(param.name => allowed_params).to_h
|
56
56
|
hash[param.name.to_sym] = nested
|
57
57
|
else
|
58
|
-
nested = params.permit(param.name => allowed_params).to_h
|
58
|
+
nested = params.permit(param.name => allowed_params).to_h
|
59
59
|
hash.merge!(nested)
|
60
60
|
end
|
61
61
|
else
|
62
62
|
hash[param.name.to_sym] = params[param.name]
|
63
63
|
end
|
64
|
-
end
|
64
|
+
end
|
65
65
|
|
66
|
-
path_params.merge(query_params).merge(permitted_body).
|
66
|
+
path_params.merge(query_params).merge(permitted_body).deep_symbolize_keys
|
67
67
|
end
|
68
68
|
|
69
69
|
private
|
data/lib/api_regulator/dsl.rb
CHANGED
@@ -9,7 +9,7 @@ module ApiRegulator
|
|
9
9
|
end
|
10
10
|
|
11
11
|
module ClassMethods
|
12
|
-
def api(controller_class, action, desc: nil, title: nil, versions: [], &block)
|
12
|
+
def api(controller_class, action, desc: nil, title: nil, versions: [], tags: [], &block)
|
13
13
|
@api_definitions ||= []
|
14
14
|
|
15
15
|
api_definition = Api.new(
|
@@ -18,6 +18,7 @@ module ApiRegulator
|
|
18
18
|
desc: desc,
|
19
19
|
title: title,
|
20
20
|
versions: versions,
|
21
|
+
tags: tags,
|
21
22
|
&block
|
22
23
|
)
|
23
24
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api-regulator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.28
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Geoff Massanek
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-06-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|