api-regulator 0.1.27 → 1.0.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: 1930002f6e2b1b916f44c2aaf447b624c373f5393297d7aa12b665063ec75a56
4
- data.tar.gz: f98dc9238b359bf019eeffe6f0f5a3a0dc001f8f8af91d80ba042828621cd699
3
+ metadata.gz: 8bcf52fc47c491d424f53b82688cfa8a3f0b78b62460267f1ad81abcf695827d
4
+ data.tar.gz: 1bb55ea5e11bff7fce98874adf0ca5d79db2431eabf4cafdaaf2dec7b4bb6fa4
5
5
  SHA512:
6
- metadata.gz: effb1e8e1cac07f20aa41dd4398b146afa13a59769c687bfc740730ae3a9bf7a84d81805dc11e0a0073c97c3375689d6d78647bef7cc1543ede8c065c1956474
7
- data.tar.gz: a951dc617505560736c62a34aa4eae3d2c99364a39953ee64d337c232b516f9949d1cf90de9859d9d9b33c2036f42fc9421ea7ceabdf5ae81099b4f4bc9b8675
6
+ metadata.gz: c872be90e74a524e316958afc1486464612854794ca86f0fe78aef6ea9a67bc1779870eecd7fc8750987e0a5f3b2ca5a99c7af6047add3f3fb83d0e86c326d8b
7
+ data.tar.gz: e55567f1b2c788bae49bb536aee0aa10e9f6fa6c490ec04632c42a0b6acb0fb2758c54ef6c7944806aeb3453c197cf2bce749977c324d523dbca1c88eb19bf44
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- api-regulator (0.1.27)
4
+ api-regulator (1.0.0)
5
5
  activemodel (~> 8.0)
6
6
  activesupport (~> 8.0)
7
7
 
data/README.md CHANGED
@@ -1,25 +1,21 @@
1
1
  # ApiRegulator
2
2
 
3
- ApiRegulator is a Ruby gem designed to **document** and **validate APIs** in Rails applications. It provides a clean DSL for defining API endpoints, parameter validations, and response schemas directly in your Rails controllers, while also generating OpenAPI 3.1.0-compliant documentation for tools like Swagger and ReadMe.
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 relies on **Active Model validations** for parameter validation, making it familiar and intuitive for Rails developers.
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
- - **API Documentation DSL**:
10
- Define API endpoints, request parameters, and response schemas in an intuitive, Rails-friendly DSL.
11
-
12
- - **Dynamic Validation**:
13
- Automatically validate incoming requests against the defined parameters using **Active Model validations**, reducing boilerplate code.
14
-
15
- - **OpenAPI Documentation**:
16
- Generate fully compliant OpenAPI 3.1.0 JSON files, ready for use with documentation tools like Swagger or ReadMe.
17
-
18
- - **Reusability**:
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 generating OpenAPI specs and seamless integration with rdme CLI for uploading 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,705 @@ gem 'api_regulator'
31
27
 
32
28
  Run `bundle install` to install the gem.
33
29
 
30
+ ### Optional: Install rdme CLI for ReadMe Integration
34
31
 
35
- ## Setup
32
+ For uploading documentation to ReadMe, install the rdme CLI:
36
33
 
37
- 1. **Create an Initializer**:
34
+ ```bash
35
+ npm install -g rdme
36
+ ```
38
37
 
39
- Add the following to `config/initializers/api_regulator.rb`:
38
+ Or as a project dependency:
40
39
 
41
- ```ruby
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
40
+ ```bash
41
+ npm install rdme --save-dev
42
+ ```
46
43
 
47
- # Optional: ReadMe versions and API ID for schema uploads
48
- config.versions = {
49
- "v1.0" => "abc123",
50
- "v1.0-internal" => "abc345"
51
- }
52
- config.default_version = "v1.0-internal"
44
+ ## Setup
53
45
 
54
- config.servers = [
55
- { url: "https://stg.example.com", description: "Staging", "x-default": true },
56
- { url: "https://example.com", description: "Production" }
57
- ]
58
- end
59
- ```
46
+ ### 1. Create an Initializer
60
47
 
61
- 2. **Include the DSL in Your Base Controller**:
48
+ Add the following to `config/initializers/api_regulator.rb`:
62
49
 
63
- Include the DSL and validation methods in your base API controller:
50
+ ```ruby
51
+ ApiRegulator.configure do |config|
52
+ config.api_base_url = "/api/v1"
53
+ config.docs_path = Rails.root.join("doc").to_s
54
+ config.app_name = "My API"
55
+
56
+ # Optional: Configure multiple API versions
57
+ config.versions = {
58
+ "v1.0" => "abc123", # ReadMe API spec ID
59
+ "v1.0-internal" => "abc345" # Internal version
60
+ }
61
+ config.default_version = "v1.0-internal"
62
+
63
+ # Optional: Define API servers
64
+ config.servers = [
65
+ { url: "https://stg.example.com", description: "Staging", "x-default": true },
66
+ { url: "https://example.com", description: "Production" }
67
+ ]
68
+ end
69
+ ```
64
70
 
65
- ```ruby
66
- class Api::ApplicationController < ActionController::API
67
- include ApiRegulator::DSL
68
- include ApiRegulator::ControllerMixin
69
- end
70
- ```
71
+ ### 2. Include the DSL in Your Base Controller
71
72
 
72
- ## Usage
73
+ ```ruby
74
+ class Api::ApplicationController < ActionController::API
75
+ include ApiRegulator::DSL
76
+ include ApiRegulator::ControllerMixin
77
+ end
78
+ ```
73
79
 
74
- **Defining Endpoints**
80
+ ### 3. Define Security Schemes (Optional)
75
81
 
76
- Use the DSL in your controllers to define API endpoints, request parameters, and response schemas.
82
+ ```ruby
83
+ # In your initializer
84
+ ApiRegulator.security_schemes = {
85
+ bearer_auth: {
86
+ type: "http",
87
+ scheme: "bearer",
88
+ bearerFormat: "JWT"
89
+ },
90
+ api_key: {
91
+ type: "apiKey",
92
+ in: "header",
93
+ name: "X-API-Key"
94
+ }
95
+ }
96
+ ```
97
+
98
+ ## Usage
99
+
100
+ ### Basic API Definition
77
101
 
78
102
  ```ruby
79
103
  class Api::V1::CustomersController < Api::ApplicationController
80
- api self, :create, "Enroll a customer" do
104
+ api self, :create, desc: "Create a new customer", title: "Create Customer" do
105
+ # Path parameters
106
+ param :organization_id, :string, location: :path, presence: true
107
+
108
+ # Query parameters
109
+ param :expand, :string, location: :query, desc: "Comma-separated list of fields to expand"
110
+
111
+ # Body parameters
81
112
  param :customer, presence: true do
82
113
  param :first_name, :string, presence: true
83
114
  param :last_name, :string, presence: true
84
- param :email, :string, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
85
- param :ssn, :string, presence: true, length: { minimum: 9, maximum: 9 }
115
+ param :email, :string, presence: true, format: { with: ApiRegulator::Formats::EMAIL }
116
+ param :ssn, :string, presence: true, length: { is: 9 }
117
+ param :date_of_birth, :string, format: { with: ApiRegulator::Formats::DATE }
118
+
119
+ # Nested objects
120
+ param :address, :object do
121
+ param :street, :string, presence: true
122
+ param :city, :string, presence: true
123
+ param :state, :string, presence: true, length: { is: 2 }
124
+ param :zip_code, :string, format: { with: ApiRegulator::Formats::ZIP_CODE }
125
+ end
126
+
127
+ # Arrays
128
+ param :phone_numbers, :array, item_type: :string
129
+ param :emergency_contacts, :array do
130
+ param :name, :string, presence: true
131
+ param :relationship, :string, presence: true
132
+ param :phone, :string, presence: true
133
+ end
86
134
  end
87
135
 
88
- response 200, "Customer successfully enrolled" do
136
+ # Response definitions
137
+ response 201, "Customer successfully created" do
89
138
  param :customer do
90
- param :id, :string, desc: "Customer ID"
91
- param :email, :string, desc: "Customer email"
139
+ param :id, :string, desc: "Customer UUID"
140
+ param :email, :string, desc: "Customer email address"
141
+ param :created_at, :string, desc: "ISO 8601 timestamp"
92
142
  end
93
143
  end
94
144
 
95
145
  response 422, ref: :validation_errors
146
+ response 401, ref: :unauthorized_error
96
147
  end
97
148
 
98
149
  def create
99
- validate_params! # Validate request params against the DSL definition
150
+ validate_params! # Validates against DSL definition
100
151
 
101
- customer = Customer.create!(api_params[:customer]) # Use dynamically generated params
102
- render json: customer, status: :ok
152
+ customer = Customer.create!(api_params[:customer])
153
+ render json: { customer: customer }, status: :created
103
154
  end
104
155
  end
105
156
  ```
106
157
 
107
- ## Shared Schemas
158
+ ### Advanced Parameter Options
159
+
160
+ #### Conditional Requirements
161
+
162
+ ```ruby
163
+ param :ssn, :string, presence: {
164
+ required_on: [:create, :update], # Required only for these actions
165
+ required_except_on: [:show] # Required except for these actions
166
+ }
167
+ ```
108
168
 
109
- Define reusable schemas for common responses in your initializer:
169
+ #### Version-Specific Parameters
110
170
 
111
171
  ```ruby
172
+ param :legacy_field, :string, versions: [:v1], desc: "Only available in v1"
173
+ param :new_feature, :boolean, versions: [:v2, :v3], desc: "Available in v2 and v3"
174
+ ```
175
+
176
+ #### Type Validation and Formatting
177
+
178
+ ```ruby
179
+ param :age, :integer, numericality: { greater_than: 0, less_than: 150 }
180
+ param :website, :string, format: { with: ApiRegulator::Formats::URI }
181
+ param :score, :number, numericality: { greater_than_or_equal_to: 0.0, less_than_or_equal_to: 100.0 }
182
+ param :is_active, :boolean
183
+ param :tags, :array, item_type: :string, inclusion: { in: ["vip", "standard", "premium"] }
184
+ ```
185
+
186
+ #### Arbitrary Keys Support
187
+
188
+ ```ruby
189
+ param :metadata, :object, allow_arbitrary_keys: true do
190
+ param :created_by, :string # Known fields can still be defined
191
+ end
192
+ ```
193
+
194
+ #### Nullable Fields
195
+
196
+ ```ruby
197
+ param :middle_name, :string, presence: { allow_nil: true }
198
+ param :optional_date, :string, format: { with: ApiRegulator::Formats::DATE, allow_nil: true }
199
+ ```
200
+
201
+ ### Shared Schemas
202
+
203
+ Define reusable schemas in your initializer:
204
+
205
+ ```ruby
206
+ # Common error responses
112
207
  ApiRegulator.register_shared_schema :validation_errors, "Validation error response" do
113
- param :errors, :array, desc: "Array of validation errors", items_type: :string
208
+ param :errors, :array, desc: "Array of validation error messages" do
209
+ param :field, :string, desc: "Field name that failed validation"
210
+ param :message, :string, desc: "Human-readable error message"
211
+ param :code, :string, desc: "Error code for programmatic handling"
212
+ end
213
+ end
214
+
215
+ ApiRegulator.register_shared_schema :unauthorized_error, "Authentication required" do
216
+ param :error, :string, desc: "Error message"
217
+ param :code, :integer, desc: "HTTP status code"
218
+ end
219
+
220
+ # Common response objects
221
+ ApiRegulator.register_shared_schema :pagination_meta, "Pagination metadata" do
222
+ param :current_page, :integer, desc: "Current page number"
223
+ param :per_page, :integer, desc: "Items per page"
224
+ param :total_pages, :integer, desc: "Total number of pages"
225
+ param :total_count, :integer, desc: "Total number of items"
226
+ end
227
+ ```
228
+
229
+ Use shared schemas in your API definitions:
230
+
231
+ ```ruby
232
+ api self, :index do
233
+ param :page, :integer, location: :query, desc: "Page number"
234
+ param :per_page, :integer, location: :query, desc: "Items per page"
235
+
236
+ response 200, "List of customers" do
237
+ param :customers, :array do
238
+ ref :customer_summary # Reference another shared schema
239
+ end
240
+ ref :pagination_meta
241
+ end
242
+ end
243
+ ```
244
+
245
+ ### Webhook Documentation
246
+
247
+ Define webhook payloads using the same DSL:
248
+
249
+ ```ruby
250
+ class WebhookDefinitions < Api::ApplicationController
251
+ webhook :customer_created,
252
+ desc: "Fired when a new customer is created",
253
+ title: "Customer Created",
254
+ tags: ["customers", "webhooks"] do
255
+
256
+ param :event, :string, desc: "Event name"
257
+ param :timestamp, :string, desc: "ISO 8601 timestamp"
258
+ param :data do
259
+ param :customer do
260
+ param :id, :string, desc: "Customer UUID"
261
+ param :email, :string, desc: "Customer email"
262
+ param :created_at, :string, desc: "ISO 8601 timestamp"
263
+ end
264
+ end
265
+
266
+ example :basic_example, {
267
+ event: "customer.created",
268
+ timestamp: "2024-01-15T10:30:00Z",
269
+ data: {
270
+ customer: {
271
+ id: "cust_abc123",
272
+ email: "john@example.com",
273
+ created_at: "2024-01-15T10:30:00Z"
274
+ }
275
+ }
276
+ }, default: true
277
+ end
278
+ end
279
+ ```
280
+
281
+ ### API Examples
282
+
283
+ Add examples to your API definitions:
284
+
285
+ ```ruby
286
+ api self, :create do
287
+ param :customer do
288
+ param :name, :string, presence: true
289
+ param :email, :string, presence: true
290
+ end
291
+
292
+ example :successful_creation, {
293
+ customer: {
294
+ name: "John Doe",
295
+ email: "john@example.com"
296
+ }
297
+ }, default: true
298
+
299
+ example :with_optional_fields, {
300
+ customer: {
301
+ name: "Jane Smith",
302
+ email: "jane@example.com",
303
+ phone: "+1-555-0123"
304
+ }
305
+ }
306
+ end
307
+ ```
308
+
309
+ ## Validation and Error Handling
310
+
311
+ ### Automatic Parameter Validation
312
+
313
+ ```ruby
314
+ def create
315
+ validate_params! # Raises ApiRegulator::InvalidParams or ApiRegulator::UnexpectedParams
316
+
317
+ # Access validated and type-converted parameters
318
+ customer_data = api_params[:customer]
319
+ # Process with confidence that data is valid
320
+ end
321
+ ```
322
+
323
+ ### Custom Error Handling
324
+
325
+ ```ruby
326
+ class Api::ApplicationController < ActionController::API
327
+ include ApiRegulator::DSL
328
+ include ApiRegulator::ControllerMixin
329
+
330
+ rescue_from ApiRegulator::InvalidParams do |exception|
331
+ render json: {
332
+ errors: exception.errors.messages,
333
+ message: "Validation failed"
334
+ }, status: :unprocessable_entity
335
+ end
336
+
337
+ rescue_from ApiRegulator::UnexpectedParams do |exception|
338
+ render json: {
339
+ errors: exception.details,
340
+ message: "Unexpected parameters provided"
341
+ }, status: :bad_request
342
+ end
343
+ end
344
+ ```
345
+
346
+ ## OpenAPI Documentation Generation
347
+
348
+ ### Validate OpenAPI Specification
349
+
350
+ Before uploading, validate your OpenAPI specification using rdme:
351
+
352
+ ```bash
353
+ # Validate the generated OpenAPI spec
354
+ rdme openapi validate doc/openapi.yaml
355
+
356
+ # Validate with specific version
357
+ rdme openapi validate doc/openapi.yaml --version=v1.0
358
+ ```
359
+
360
+ ### Generate Documentation
361
+
362
+ ```bash
363
+ # Generate for default version
364
+ rake api_docs:generate
365
+
366
+ # Generate for specific version
367
+ VERSION=v1.0 rake api_docs:generate
368
+
369
+ # Generate for all configured versions
370
+ rake api_docs:generate_all
371
+ ```
372
+
373
+ ### Upload to ReadMe
374
+
375
+ ApiRegulator integrates with [rdme](https://github.com/readmeio/rdme), ReadMe's official CLI tool, for modern, secure, and reliable documentation management.
376
+
377
+ #### Installation
378
+
379
+ Install the rdme CLI globally:
380
+
381
+ ```bash
382
+ npm install -g rdme
383
+ ```
384
+
385
+ Or install it as a project dependency:
386
+
387
+ ```bash
388
+ npm install rdme --save-dev
389
+ ```
390
+
391
+ #### Authentication
392
+
393
+ Authenticate with ReadMe using the CLI:
394
+
395
+ ```bash
396
+ rdme login
397
+ ```
398
+
399
+ Alternatively, set your API key as an environment variable:
400
+
401
+ ```bash
402
+ export RDME_API_KEY="your_readme_api_key"
403
+ ```
404
+
405
+ #### Upload Commands
406
+
407
+ **Recommended approach using rdme CLI:**
408
+
409
+ ```bash
410
+ # Generate and upload everything using rdme (RECOMMENDED)
411
+ rake api_docs:publish_rdme
412
+
413
+ # Or step by step:
414
+ rake api_docs:generate
415
+ rake api_docs:upload_rdme
416
+ rdme docs upload doc/
417
+ ```
418
+
419
+ **Legacy approach (may not work with ReadMe API v2):**
420
+
421
+ ```bash
422
+ # Generate OpenAPI specification
423
+ rake api_docs:generate
424
+
425
+ # Upload using legacy rake tasks (deprecated)
426
+ rake api_docs:upload
427
+ rake api_docs:upload_pages
428
+ ```
429
+
430
+ #### Migration from Legacy Tasks
431
+
432
+ If you're experiencing 404 errors with the legacy upload tasks, migrate to rdme CLI:
433
+
434
+ ```bash
435
+ # Run the migration script
436
+ ./scripts/migrate_to_rdme.sh
437
+
438
+ # Or manually:
439
+ npm install -g rdme
440
+ rdme login
441
+ rake api_docs:publish_rdme
442
+ ```
443
+
444
+ **rdme CLI Features:**
445
+
446
+ - Modern ReadMe API v2 integration
447
+ - Bearer token authentication (more secure)
448
+ - Better performance and reliability
449
+ - Support for ReadMe Refactored features
450
+ - Branch-based versioning
451
+ - Built-in validation and error handling
452
+
453
+ ### Custom Documentation Pages
454
+
455
+ Create markdown files in your `docs_path` with YAML frontmatter:
456
+
457
+ ```markdown
458
+ ---
459
+ title: "Getting Started"
460
+ slug: "getting-started"
461
+ category: "documentation"
462
+ hidden: false
463
+ ---
464
+
465
+ # Getting Started
466
+
467
+ Your API documentation content here...
468
+ ```
469
+
470
+ ### GitHub Actions Integration
471
+
472
+ Use rdme in your GitHub Actions workflow for automated documentation updates:
473
+
474
+ ```yaml
475
+ name: Update API Documentation
476
+
477
+ on:
478
+ push:
479
+ branches: [main]
480
+ pull_request:
481
+ branches: [main]
482
+
483
+ jobs:
484
+ update-docs:
485
+ runs-on: ubuntu-latest
486
+
487
+ steps:
488
+ - name: Checkout code
489
+ uses: actions/checkout@v4
490
+
491
+ - name: Setup Ruby
492
+ uses: ruby/setup-ruby@v1
493
+ with:
494
+ ruby-version: 3.0
495
+
496
+ - name: Install dependencies
497
+ run: |
498
+ gem install bundler
499
+ bundle install
500
+
501
+ - name: Generate and upload API documentation
502
+ run: rake api_docs:publish_rdme
503
+ env:
504
+ RDME_API_KEY: ${{ secrets.RDME_API_KEY }}
505
+ ```
506
+
507
+ ### ReadMe Integration Features
508
+
509
+ ```bash
510
+ # Validate OpenAPI specification
511
+ rdme openapi validate doc/openapi.yaml
512
+
513
+ # Upload OpenAPI spec using rake task (recommended)
514
+ rake api_docs:upload_rdme
515
+
516
+ # Upload with specific version
517
+ VERSION=v2.0 rake api_docs:upload_rdme
518
+
519
+ # Upload directly with rdme CLI
520
+ rdme openapi upload doc/openapi.yaml --version=v2.0
521
+
522
+ # Fetch available categories
523
+ rdme categories list
524
+ ```
525
+
526
+ ## Built-in Format Validators
527
+
528
+ ApiRegulator includes common format validators:
529
+
530
+ ```ruby
531
+ ApiRegulator::Formats::DATE # ISO 8601 date format
532
+ ApiRegulator::Formats::DATETIME # ISO 8601 datetime format
533
+ ApiRegulator::Formats::EMAIL # Email address validation
534
+ ApiRegulator::Formats::ZIP_CODE # US ZIP code (12345 or 12345-6789)
535
+ ApiRegulator::Formats::URI # URI format validation
536
+ ```
537
+
538
+ Usage example:
539
+
540
+ ```ruby
541
+ param :email, :string, format: { with: ApiRegulator::Formats::EMAIL }
542
+ param :website, :string, format: { with: ApiRegulator::Formats::URI }
543
+ param :birth_date, :string, format: { with: ApiRegulator::Formats::DATE }
544
+ ```
545
+
546
+ ## Testing Your APIs
547
+
548
+ ApiRegulator integrates seamlessly with RSpec:
549
+
550
+ ```ruby
551
+ RSpec.describe Api::V1::CustomersController do
552
+ describe "POST /api/v1/customers" do
553
+ it "validates required parameters" do
554
+ post "/api/v1/customers", params: {}
555
+ expect(response).to have_http_status(:unprocessable_entity)
556
+ end
557
+
558
+ it "creates customer with valid parameters" do
559
+ params = {
560
+ customer: {
561
+ first_name: "John",
562
+ last_name: "Doe",
563
+ email: "john@example.com"
564
+ }
565
+ }
566
+
567
+ post "/api/v1/customers", params: params
568
+ expect(response).to have_http_status(:created)
569
+ end
570
+ end
571
+ end
572
+ ```
573
+
574
+ ## Configuration Reference
575
+
576
+ ```ruby
577
+ ApiRegulator.configure do |config|
578
+ # Base URL for all API endpoints (default: "api/v1")
579
+ config.api_base_url = "/api/v1"
580
+
581
+ # Application name shown in documentation (default: "API Documentation")
582
+ config.app_name = "My API"
583
+
584
+ # Directory for documentation files (default: "doc")
585
+ config.docs_path = Rails.root.join("doc").to_s
586
+
587
+ # Version mapping for ReadMe (optional)
588
+ config.versions = {
589
+ "v1.0" => "readme_spec_id_1",
590
+ "v2.0" => "readme_spec_id_2"
591
+ }
592
+
593
+ # Default version when none specified (optional)
594
+ config.default_version = "v1.0"
595
+
596
+ # Server definitions for OpenAPI spec (optional)
597
+ config.servers = [
598
+ { url: "https://api.example.com", description: "Production" },
599
+ { url: "https://staging-api.example.com", description: "Staging" }
600
+ ]
601
+ end
602
+ ```
603
+
604
+ ## Advanced Usage
605
+
606
+ ### Multiple API Versions
607
+
608
+ ```ruby
609
+ # Define version-specific endpoints
610
+ api self, :create, versions: [:v1, :v2] do
611
+ param :name, :string, presence: true
612
+ param :email, :string, presence: true, versions: [:v1, :v2]
613
+ param :phone, :string, versions: [:v2] # Only in v2
614
+ end
615
+ ```
616
+
617
+ ### Security Requirements
618
+
619
+ ```ruby
620
+ api self, :create do
621
+ # This endpoint requires authentication
622
+ security [{ bearer_auth: [] }]
623
+
624
+ param :customer do
625
+ param :name, :string, presence: true
626
+ end
114
627
  end
115
628
  ```
116
629
 
117
- Reference the shared schema in your responses:
630
+ ### Custom Validation Context
631
+
118
632
  ```ruby
119
- response 422, ref: :validation_errors
633
+ def update
634
+ # Validate with specific context
635
+ validate_params!
636
+
637
+ # The validation context is automatically set to the action name (:update)
638
+ # This allows conditional validations based on the action
120
639
  end
121
640
  ```
122
641
 
123
- ## Generating OpenAPI Documentation
642
+ ## Troubleshooting
643
+
644
+ ### Common Issues
645
+
646
+ #### 404 Error When Uploading Pages
647
+
648
+ If you get a 404 error when uploading documentation pages:
649
+
650
+ ```
651
+ Failed to upload page 'Webhooks'!
652
+ Response Code: 404
653
+ ```
654
+
655
+ **Solution:** The legacy rake tasks may not work with ReadMe API v2. Use rdme CLI instead:
656
+
657
+ ```bash
658
+ # Install and authenticate with rdme
659
+ npm install -g rdme
660
+ rdme login
661
+
662
+ # Use the recommended approach
663
+ rake api_docs:publish_rdme
664
+ ```
665
+
666
+ #### Authentication Issues
667
+
668
+ If you get authentication errors:
669
+
670
+ ```bash
671
+ # Check your authentication status
672
+ rdme whoami
673
+
674
+ # Re-authenticate if needed
675
+ rdme logout
676
+ rdme login
677
+ ```
678
+
679
+ #### OpenAPI Validation Errors
680
+
681
+ Validate your OpenAPI spec before uploading:
682
+
683
+ ```bash
684
+ # Validate the generated spec
685
+ rdme openapi validate doc/openapi.yaml
686
+
687
+ # Check for common issues
688
+ rdme openapi validate doc/openapi.yaml --verbose
689
+ ```
690
+
691
+ ## Error Reference
124
692
 
125
- Generate OpenAPI documentation using the provided Rake tasks:
693
+ - `ApiRegulator::InvalidParams`: Raised when request parameters fail validation
694
+ - `ApiRegulator::UnexpectedParams`: Raised when unexpected parameters are provided
695
+ - `ApiRegulator::ValidationError`: Base class for validation errors
126
696
 
697
+ ## Development and Contributing
127
698
 
128
- 1. **Generate the Schema:**
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
- ```
699
+ 1. Fork the repository
700
+ 2. Create your feature branch (`git checkout -b feature/new-feature`)
701
+ 3. Write tests for your changes
702
+ 4. Ensure all tests pass (`bundle exec rspec`)
703
+ 5. Commit your changes (`git commit -am 'Add some feature'`)
704
+ 6. Push to the branch (`git push origin feature/new-feature`)
705
+ 7. Open a pull request
133
706
 
134
- 2. **Upload to ReadMe (Optional)**:
135
- ```bash
136
- rake api_docs:upload
137
- ```
138
- This uploads the OpenAPI file to ReadMe. Ensure you’ve configured the RDME_API_KEY and optional RDME_API_ID.
707
+ ### Running Tests
708
+
709
+ ```bash
710
+ bundle install
711
+ bundle exec rspec
712
+ ```
139
713
 
140
- 3. **Publish Both**:
141
- ```bash
142
- rake api_docs:publish
143
- ```
714
+ ## Releasing New Versions
144
715
 
145
- ## Contributing
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.
716
+ ApiRegulator is published to [RubyGems](https://rubygems.org/gems/api-regulator). To release:
151
717
 
152
- ## Releasing a new version
153
- `api-regulator` is published to [rubygems](https://rubygems.org/gems/api-regulator). To release a new version,
154
- 1. In your PR, bump the version in `lib/api_regulator/version.rb`
155
- 2. Run `bundle install` (this should update your `Gemfile.lock`)
156
- 3. Go through code review and merge your PR
157
- 4. Trigger the [`Release Gem`](https://github.com/Stellarcred/api-regulator/actions/workflows/release.yml) workflow from the main branch.
158
- 5. Verify your new version is available in [rubygems](https://rubygems.org/gems/api-regulator).
718
+ 1. Update version in `lib/api_regulator/version.rb`
719
+ 2. Run `bundle install` to update `Gemfile.lock`
720
+ 3. Create PR and merge after review
721
+ 4. Trigger the [Release Gem workflow](https://github.com/Stellarcred/api-regulator/actions/workflows/release.yml)
722
+ 5. Verify publication on [RubyGems](https://rubygems.org/gems/api-regulator)
723
+
724
+ ## Requirements
725
+
726
+ - Ruby >= 3.0
727
+ - Rails >= 8.0 (ActiveSupport and ActiveModel)
159
728
 
160
729
  ## License
730
+
161
731
  This gem is available as open-source software under the [MIT License](https://mit-license.org/).
@@ -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 tags
92
+ def default_tags
92
93
  [
93
94
  controller_name
94
95
  .demodulize
@@ -4,7 +4,7 @@ module ApiRegulator
4
4
 
5
5
  def initialize
6
6
  # Set default values
7
- @api_base_url = "api/v1"
7
+ @api_base_url = "api/v2"
8
8
  @app_name = "API Documentation"
9
9
  @docs_path = "doc"
10
10
  @servers = []
@@ -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
 
@@ -30,12 +30,12 @@ module ApiRegulator
30
30
  openapi: '3.1.0', # Explicitly target OpenAPI 3.1.0
31
31
  info: {
32
32
  title: ApiRegulator.configuration.app_name,
33
- description: 'Generated by ApiRegulator'
33
+ description: 'Generated by ApiRegulator',
34
+ version: version.presence || '1.0.0'
34
35
  },
35
36
  servers: ApiRegulator.configuration.servers,
36
37
  paths: {}
37
38
  }
38
- final_schema[:info][:version] = version if version.present?
39
39
 
40
40
  add_components
41
41
  add_security
@@ -1,3 +1,3 @@
1
1
  module ApiRegulator
2
- VERSION = "0.1.27"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -23,128 +23,116 @@ namespace :api_docs do
23
23
  end
24
24
  end
25
25
 
26
- desc "Upload OpenAPI schema to ReadMe"
27
- task :upload => :environment do
28
- # ReadMe API key and version
29
- readme_api_key = ENV['RDME_API_KEY'].presence || raise("RDME_API_KEY is not set")
30
- version = ENV['VERSION'] || ApiRegulator.configuration.default_version
31
-
32
- # ReadMe API endpoint
33
- readme_api_endpoint = "https://dash.readme.com/api/v1/api-specification"
26
+ desc "Upload OpenAPI schema to ReadMe using rdme CLI (RECOMMENDED)"
27
+ task upload_rdme: :environment do
28
+ # Check if rdme is available
29
+ unless system("which rdme > /dev/null 2>&1")
30
+ puts "❌ rdme CLI not found. Please install it first:"
31
+ puts " npm install -g rdme"
32
+ puts " rdme login"
33
+ exit 1
34
+ end
34
35
 
35
- # Read the OpenAPI schema file
36
- schema_path = ApiRegulator::OpenApiGenerator.schema_file_path(version: version)
36
+ version = ENV['VERSION'] || ApiRegulator.configuration.default_version
37
+ schema_path = ApiRegulator::OpenApiGenerator.schema_file_path(version: version)
38
+
37
39
  unless File.exist?(schema_path)
38
- raise "OpenAPI schema file not found at #{schema_path}"
40
+ raise "OpenAPI schema file not found at #{schema_path}. Run 'rake api_docs:generate' first."
39
41
  end
40
- openapi_content = File.read(schema_path)
41
42
 
42
- # Upload to ReadMe
43
- require 'net/http'
44
- require 'uri'
45
- require 'json'
43
+ puts "📄 Uploading OpenAPI specification with rdme CLI..."
44
+ puts " Schema: #{schema_path}"
45
+ puts " Version: #{version || 'default'}"
46
+ puts ""
46
47
 
47
- if version.present?
48
- uri = URI.parse("#{readme_api_endpoint}/#{ApiRegulator.configuration.versions[version]}")
49
- request = Net::HTTP::Put.new(uri)
48
+ # Use rdme CLI to upload
49
+ if system("rdme openapi upload #{schema_path}")
50
+ puts "✅ OpenAPI schema successfully uploaded!"
50
51
  else
51
- uri = URI.parse(readme_api_endpoint)
52
- request = Net::HTTP::Post.new(uri)
53
- end
54
- request["Authorization"] = "Basic #{Base64.strict_encode64(readme_api_key)}"
55
- request["Content-Type"] = "application/json"
56
- request["x-readme-version"] = version if version.present?
57
- request.body = {
58
- spec: JSON.parse(openapi_content)
59
- }.to_json
60
-
61
- response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
62
- http.request(request)
52
+ puts "❌ Failed to upload OpenAPI schema"
53
+ exit 1
63
54
  end
55
+ end
64
56
 
65
- if response.code.to_i == 200
66
- puts "OpenAPI schema successfully updated!"
67
- elsif response.code.to_i == 201
68
- puts "OpenAPI schema successfully created!"
69
- puts "To use this for future publishing, add this to your ApiRegulator configs:"
70
- puts ""
71
- puts " config.versions = {"
72
- puts " \"<versionNumber>\": \"#{JSON.parse(response.body)["_id"]}\""
73
- puts " }"
74
- puts ""
57
+ desc "Upload OpenAPI schema to ReadMe (LEGACY - may not work with ReadMe API v2)"
58
+ task :upload => :environment do
59
+ puts "⚠️ WARNING: This rake task is deprecated and may not work with ReadMe API v2."
60
+ puts " Please use the rdme CLI instead:"
61
+ puts " "
62
+ puts " # Install rdme CLI"
63
+ puts " npm install -g rdme"
64
+ puts " "
65
+ puts " # Authenticate with ReadMe"
66
+ puts " rdme login"
67
+ puts " "
68
+ puts " # Upload using rdme CLI"
69
+ puts " rake api_docs:upload_rdme"
70
+ puts " "
71
+ puts " For more information, see: https://github.com/readmeio/rdme"
72
+ puts " "
73
+
74
+ # Check if rdme is available
75
+ if system("which rdme > /dev/null 2>&1")
76
+ puts "✅ rdme CLI is installed. Running: rake api_docs:upload_rdme"
77
+ Rake::Task["api_docs:upload_rdme"].invoke
75
78
  else
76
- puts "Failed to upload OpenAPI schema to ReadMe!"
77
- puts "Response Code: #{response.code}"
78
- puts "Response Body:"
79
- pp JSON.parse(response.body)
79
+ puts " rdme CLI not found. Please install it first:"
80
+ puts " npm install -g rdme"
81
+ exit 1
80
82
  end
81
83
  end
82
84
 
83
- desc 'Generate and upload OpenAPI schema'
85
+ desc 'Generate and upload OpenAPI schema using rdme CLI (RECOMMENDED)'
86
+ task publish_rdme: :environment do
87
+ Rake::Task["api_docs:generate"].invoke
88
+ Rake::Task["api_docs:upload_rdme"].invoke
89
+
90
+ puts "📄 Uploading documentation pages with rdme CLI..."
91
+ system("rdme docs upload #{ApiRegulator.configuration.docs_path}/")
92
+ end
93
+
94
+ desc 'Generate and upload OpenAPI schema (LEGACY - may not work with ReadMe API v2)'
84
95
  task publish: :environment do
85
96
  Rake::Task["api_docs:generate"].invoke
86
97
  Rake::Task["api_docs:upload"].invoke
87
- Rake::Task["api_docs:upload_pages"].invoke
98
+
99
+ # Check if rdme is available for uploading pages
100
+ if system("which rdme > /dev/null 2>&1")
101
+ puts "📄 Uploading documentation pages with rdme CLI..."
102
+ system("rdme docs upload #{ApiRegulator.configuration.docs_path}/")
103
+ else
104
+ puts "⚠️ rdme CLI not found. Skipping documentation pages upload."
105
+ puts " Install rdme CLI to upload documentation pages:"
106
+ puts " npm install -g rdme"
107
+ puts " rdme docs upload #{ApiRegulator.configuration.docs_path}/"
108
+ end
88
109
  end
89
110
 
90
- desc "Upload custom pages to ReadMe"
111
+ desc "Upload custom pages to ReadMe (DEPRECATED - Use rdme CLI instead)"
91
112
  task :upload_pages => :environment do
92
- # Configuration
93
- readme_api_key = ENV['RDME_API_KEY'].presence || raise("RDME_API_KEY is not set")
94
- version = ENV['VERSION'] || ApiRegulator.configuration.default_version
95
- base_readme_api_endpoint = "https://dash.readme.com/api/v1/docs"
96
-
97
- # Discover all documentation files
98
- pages_directory = "#{ApiRegulator.configuration.docs_path}/**/*.md"
99
- page_files = Dir.glob(pages_directory)
100
-
101
- # Iterate through each file
102
- page_files.each do |file_path|
103
- # Extract metadata and body
104
- metadata, body = parse_markdown_file(file_path)
105
- raise "No metadata found in #{file_path}" unless metadata
106
-
107
- # Use metadata to build the API request
108
- slug = metadata["slug"] || File.basename(file_path, ".md").gsub("_", "-")
109
- request_body = {
110
- type: "basic",
111
- categorySlug: "documentation",
112
- hidden: false,
113
- body: body
114
- }.merge(metadata)
115
- request_body["slug"] ||= slug
116
-
117
- raise("Title missing in #{file_path}") unless request_body["title"].present?
118
-
119
- # Build the API request
120
- if check_if_page_exists(slug)
121
- uri = URI.parse("#{base_readme_api_endpoint}/#{slug}")
122
- request = Net::HTTP::Put.new(uri)
123
- else
124
- uri = URI.parse("#{base_readme_api_endpoint}")
125
- request = Net::HTTP::Post.new(uri)
126
- end
127
- request["Authorization"] = "Basic #{Base64.strict_encode64(readme_api_key)}"
128
- request["Content-Type"] = "application/json"
129
- request["x-readme-version"] = version if version.present?
130
- request.body = request_body.compact.to_json
131
-
132
- # Send the request
133
- response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
134
- http.request(request)
135
- end
136
-
137
- # Handle the response
138
- case response.code.to_i
139
- when 200
140
- puts "Page '#{request_body["title"]}' successfully updated!"
141
- when 201
142
- puts "Page '#{request_body["title"]}' successfully created!"
143
- else
144
- puts "Failed to upload page '#{request_body["title"]}'!"
145
- puts "Response Code: #{response.code}"
146
- puts "Response Body: #{response.body}"
147
- end
113
+ puts "⚠️ WARNING: This rake task is deprecated and may not work with ReadMe API v2."
114
+ puts " Please use the rdme CLI instead:"
115
+ puts " "
116
+ puts " # Install rdme CLI"
117
+ puts " npm install -g rdme"
118
+ puts " "
119
+ puts " # Authenticate with ReadMe"
120
+ puts " rdme login"
121
+ puts " "
122
+ puts " # Upload documentation pages"
123
+ puts " rdme docs upload #{ApiRegulator.configuration.docs_path}/"
124
+ puts " "
125
+ puts " For more information, see: https://github.com/readmeio/rdme"
126
+ puts " "
127
+
128
+ # Check if rdme is available
129
+ if system("which rdme > /dev/null 2>&1")
130
+ puts "✅ rdme CLI is installed. Running: rdme docs upload #{ApiRegulator.configuration.docs_path}/"
131
+ system("rdme docs upload #{ApiRegulator.configuration.docs_path}/")
132
+ else
133
+ puts "❌ rdme CLI not found. Please install it first:"
134
+ puts " npm install -g rdme"
135
+ exit 1
148
136
  end
149
137
  end
150
138
 
@@ -152,15 +140,13 @@ namespace :api_docs do
152
140
  task :fetch_categories => :environment do
153
141
  # Configuration
154
142
  readme_api_key = ENV['RDME_API_KEY'].presence || raise("RDME_API_KEY is not set")
155
- version = ENV['VERSION'] || ApiRegulator.configuration.default_version
156
- readme_categories_api_endpoint = "https://dash.readme.com/api/v1/categories"
143
+ readme_categories_api_endpoint = "https://api.readme.com/v2/categories"
157
144
 
158
145
  # Build the API request
159
146
  uri = URI.parse(readme_categories_api_endpoint)
160
147
  request = Net::HTTP::Get.new(uri)
161
- request["Authorization"] = "Basic #{Base64.strict_encode64(readme_api_key)}"
148
+ request["Authorization"] = "Bearer #{readme_api_key}"
162
149
  request["Content-Type"] = "application/json"
163
- request["x-readme-version"] = version if version.present?
164
150
 
165
151
  # Send the request
166
152
  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
@@ -171,7 +157,10 @@ namespace :api_docs do
171
157
  case response.code.to_i
172
158
  when 200
173
159
  puts "Categories"
174
- pp JSON.parse(response.body)
160
+ response_data = JSON.parse(response.body)
161
+ # API v2 response format - returns data in a 'data' wrapper
162
+ categories = response_data.dig("data") || response_data
163
+ pp categories
175
164
  else
176
165
  puts "Failed to fetch categories"
177
166
  puts "Response Code: #{response.code}"
@@ -195,14 +184,13 @@ namespace :api_docs do
195
184
  [metadata, body]
196
185
  end
197
186
 
198
- def check_if_page_exists(slug)
187
+ def check_if_page_exists(slug, branch = "stable")
199
188
  readme_api_key = ENV['RDME_API_KEY'].presence || raise("RDME_API_KEY is not set")
200
- version = ENV['VERSION'] || ApiRegulator.configuration.default_version
201
- uri = URI.parse("https://dash.readme.com/api/v1/docs/#{slug}")
189
+
190
+ uri = URI.parse("https://api.readme.com/v2/custom_pages/#{slug}")
202
191
  request = Net::HTTP::Get.new(uri)
203
- request["Authorization"] = "Basic #{Base64.strict_encode64(readme_api_key)}"
192
+ request["Authorization"] = "Bearer #{readme_api_key}"
204
193
  request["Content-Type"] = "application/json"
205
- request["x-readme-version"] = version if version.present?
206
194
 
207
195
  # Send the request
208
196
  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
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.27
4
+ version: 1.0.0
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-04-17 00:00:00.000000000 Z
11
+ date: 2025-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport