rapitapir 0.1.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.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +57 -0
  4. data/CHANGELOG.md +94 -0
  5. data/CLEANUP_SUMMARY.md +155 -0
  6. data/CONTRIBUTING.md +280 -0
  7. data/LICENSE +21 -0
  8. data/README.md +485 -0
  9. data/debug_hash.rb +20 -0
  10. data/docs/EXTENSION_COMPARISON.md +388 -0
  11. data/docs/SINATRA_EXTENSION.md +467 -0
  12. data/docs/archive/PHASE_1_2_COMPLETE.md +77 -0
  13. data/docs/archive/PHASE_1_3_COMPLETE.md +152 -0
  14. data/docs/archive/PHASE_2_1_OBSERVABILITY_COMPLETED.md +203 -0
  15. data/docs/archive/PHASE_2_SUMMARY.md +209 -0
  16. data/docs/archive/REFACTORING_SUMMARY.md +184 -0
  17. data/docs/archive/phase_1_3_plan.md +136 -0
  18. data/docs/archive/sinatra_extension_summary.md +188 -0
  19. data/docs/archive/sinatra_working_solution.md +113 -0
  20. data/docs/archive/typescript-client-generator-summary.md +259 -0
  21. data/docs/auto-derivation.md +146 -0
  22. data/docs/blueprint.md +1091 -0
  23. data/docs/endpoint-definition.md +211 -0
  24. data/docs/github_pages_fix.md +52 -0
  25. data/docs/github_pages_setup.md +49 -0
  26. data/docs/implementation-status.md +357 -0
  27. data/docs/observability.md +647 -0
  28. data/docs/phase3-plan.md +108 -0
  29. data/docs/sinatra_rapitapir.md +87 -0
  30. data/docs/type_shortcuts.md +146 -0
  31. data/examples/README_ENTERPRISE.md +202 -0
  32. data/examples/authentication_example.rb +192 -0
  33. data/examples/auto_derivation_ruby_friendly.rb +163 -0
  34. data/examples/cli/user_api_endpoints.rb +56 -0
  35. data/examples/client/typescript_client_example.rb +102 -0
  36. data/examples/client/user-api-client.ts +193 -0
  37. data/examples/demo_api.rb +41 -0
  38. data/examples/docs/documentation_example.rb +112 -0
  39. data/examples/docs/user-api-docs.html +789 -0
  40. data/examples/docs/user-api-docs.md +403 -0
  41. data/examples/enhanced_auto_derivation_test.rb +83 -0
  42. data/examples/enterprise_extension_demo.rb +417 -0
  43. data/examples/enterprise_rapitapir_api.rb +662 -0
  44. data/examples/getting_started_extension.rb +218 -0
  45. data/examples/hello_world.rb +74 -0
  46. data/examples/oauth2/.env.example +19 -0
  47. data/examples/oauth2/README.md +205 -0
  48. data/examples/oauth2/generic_oauth2_api.rb +226 -0
  49. data/examples/oauth2/get_token.rb +72 -0
  50. data/examples/oauth2/songs_api_with_auth0.rb +320 -0
  51. data/examples/oauth2/test_api.sh +16 -0
  52. data/examples/oauth2/test_songs_api.sh +110 -0
  53. data/examples/observability/.env.example +35 -0
  54. data/examples/observability/README.md +230 -0
  55. data/examples/observability/README_HONEYCOMB.md +332 -0
  56. data/examples/observability/advanced_setup.rb +384 -0
  57. data/examples/observability/basic_setup.rb +192 -0
  58. data/examples/observability/complete_test.rb +121 -0
  59. data/examples/observability/honeycomb_example.rb +523 -0
  60. data/examples/observability/honeycomb_rapitapir_clean.rb +488 -0
  61. data/examples/observability/honeycomb_rapitapir_example.rb +523 -0
  62. data/examples/observability/honeycomb_working_example.rb +489 -0
  63. data/examples/observability/quick_test.rb +78 -0
  64. data/examples/observability/simple_test.rb +14 -0
  65. data/examples/observability/test_honeycomb_demo.rb +354 -0
  66. data/examples/observability/test_live_honeycomb.rb +111 -0
  67. data/examples/observability/test_validation.rb +78 -0
  68. data/examples/observability/test_working_validation.rb +66 -0
  69. data/examples/openapi/user_api_schema.rb +132 -0
  70. data/examples/production_ready_example.rb +105 -0
  71. data/examples/rails/users_controller.rb +146 -0
  72. data/examples/readme/basic_sinatra_example.rb +128 -0
  73. data/examples/server/user_api.rb +179 -0
  74. data/examples/simple_auto_derivation_demo.rb +44 -0
  75. data/examples/simple_demo_api.rb +18 -0
  76. data/examples/sinatra/user_app.rb +127 -0
  77. data/examples/t_shortcut_demo.rb +59 -0
  78. data/examples/user_api.rb +190 -0
  79. data/examples/working_getting_started.rb +184 -0
  80. data/examples/working_simple_example.rb +195 -0
  81. data/lib/rapitapir/auth/configuration.rb +129 -0
  82. data/lib/rapitapir/auth/context.rb +122 -0
  83. data/lib/rapitapir/auth/errors.rb +104 -0
  84. data/lib/rapitapir/auth/middleware.rb +324 -0
  85. data/lib/rapitapir/auth/oauth2.rb +350 -0
  86. data/lib/rapitapir/auth/schemes.rb +420 -0
  87. data/lib/rapitapir/auth.rb +113 -0
  88. data/lib/rapitapir/cli/command.rb +535 -0
  89. data/lib/rapitapir/cli/server.rb +243 -0
  90. data/lib/rapitapir/cli/validator.rb +373 -0
  91. data/lib/rapitapir/client/generator_base.rb +272 -0
  92. data/lib/rapitapir/client/typescript_generator.rb +350 -0
  93. data/lib/rapitapir/core/endpoint.rb +158 -0
  94. data/lib/rapitapir/core/enhanced_endpoint.rb +235 -0
  95. data/lib/rapitapir/core/input.rb +182 -0
  96. data/lib/rapitapir/core/output.rb +164 -0
  97. data/lib/rapitapir/core/request.rb +19 -0
  98. data/lib/rapitapir/core/response.rb +17 -0
  99. data/lib/rapitapir/docs/html_generator.rb +780 -0
  100. data/lib/rapitapir/docs/markdown_generator.rb +464 -0
  101. data/lib/rapitapir/dsl/endpoint_dsl.rb +116 -0
  102. data/lib/rapitapir/dsl/enhanced_endpoint_dsl.rb +62 -0
  103. data/lib/rapitapir/dsl/enhanced_input.rb +73 -0
  104. data/lib/rapitapir/dsl/enhanced_output.rb +63 -0
  105. data/lib/rapitapir/dsl/enhanced_structures.rb +393 -0
  106. data/lib/rapitapir/dsl/fluent_dsl.rb +72 -0
  107. data/lib/rapitapir/dsl/fluent_endpoint_builder.rb +316 -0
  108. data/lib/rapitapir/dsl/http_verbs.rb +77 -0
  109. data/lib/rapitapir/dsl/input_methods.rb +47 -0
  110. data/lib/rapitapir/dsl/observability_methods.rb +81 -0
  111. data/lib/rapitapir/dsl/output_methods.rb +43 -0
  112. data/lib/rapitapir/dsl/type_resolution.rb +43 -0
  113. data/lib/rapitapir/observability/configuration.rb +108 -0
  114. data/lib/rapitapir/observability/health_check.rb +236 -0
  115. data/lib/rapitapir/observability/logging.rb +270 -0
  116. data/lib/rapitapir/observability/metrics.rb +203 -0
  117. data/lib/rapitapir/observability/middleware.rb +243 -0
  118. data/lib/rapitapir/observability/tracing.rb +143 -0
  119. data/lib/rapitapir/observability.rb +28 -0
  120. data/lib/rapitapir/openapi/schema_generator.rb +403 -0
  121. data/lib/rapitapir/schema.rb +136 -0
  122. data/lib/rapitapir/server/enhanced_rack_adapter.rb +379 -0
  123. data/lib/rapitapir/server/middleware.rb +120 -0
  124. data/lib/rapitapir/server/path_matcher.rb +45 -0
  125. data/lib/rapitapir/server/rack_adapter.rb +215 -0
  126. data/lib/rapitapir/server/rails_adapter.rb +17 -0
  127. data/lib/rapitapir/server/rails_adapter_class.rb +53 -0
  128. data/lib/rapitapir/server/rails_controller.rb +72 -0
  129. data/lib/rapitapir/server/rails_input_processor.rb +73 -0
  130. data/lib/rapitapir/server/rails_response_handler.rb +29 -0
  131. data/lib/rapitapir/server/sinatra_adapter.rb +200 -0
  132. data/lib/rapitapir/server/sinatra_integration.rb +93 -0
  133. data/lib/rapitapir/sinatra/configuration.rb +91 -0
  134. data/lib/rapitapir/sinatra/extension.rb +214 -0
  135. data/lib/rapitapir/sinatra/oauth2_helpers.rb +236 -0
  136. data/lib/rapitapir/sinatra/resource_builder.rb +152 -0
  137. data/lib/rapitapir/sinatra/swagger_ui_generator.rb +166 -0
  138. data/lib/rapitapir/sinatra_rapitapir.rb +40 -0
  139. data/lib/rapitapir/types/array.rb +163 -0
  140. data/lib/rapitapir/types/auto_derivation.rb +265 -0
  141. data/lib/rapitapir/types/base.rb +146 -0
  142. data/lib/rapitapir/types/boolean.rb +46 -0
  143. data/lib/rapitapir/types/date.rb +92 -0
  144. data/lib/rapitapir/types/datetime.rb +98 -0
  145. data/lib/rapitapir/types/email.rb +32 -0
  146. data/lib/rapitapir/types/float.rb +134 -0
  147. data/lib/rapitapir/types/hash.rb +161 -0
  148. data/lib/rapitapir/types/integer.rb +143 -0
  149. data/lib/rapitapir/types/object.rb +156 -0
  150. data/lib/rapitapir/types/optional.rb +65 -0
  151. data/lib/rapitapir/types/string.rb +185 -0
  152. data/lib/rapitapir/types/uuid.rb +32 -0
  153. data/lib/rapitapir/types.rb +155 -0
  154. data/lib/rapitapir/version.rb +5 -0
  155. data/lib/rapitapir.rb +173 -0
  156. data/rapitapir.gemspec +66 -0
  157. metadata +387 -0
@@ -0,0 +1,789 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>User Management API</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ line-height: 1.6;
17
+ color: #333;
18
+ background-color: #f8f9fa;
19
+ }
20
+
21
+ .container {
22
+ display: flex;
23
+ min-height: 100vh;
24
+ }
25
+
26
+ .header {
27
+ position: fixed;
28
+ top: 0;
29
+ left: 0;
30
+ right: 0;
31
+ background: #fff;
32
+ border-bottom: 1px solid #e1e5e9;
33
+ padding: 1rem 2rem;
34
+ z-index: 1000;
35
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
36
+ }
37
+
38
+ .header h1 {
39
+ color: #2c3e50;
40
+ font-size: 1.5rem;
41
+ margin-bottom: 0.5rem;
42
+ }
43
+
44
+ .header .meta {
45
+ color: #6c757d;
46
+ font-size: 0.9rem;
47
+ }
48
+
49
+ .sidebar {
50
+ width: 300px;
51
+ background: #fff;
52
+ border-right: 1px solid #e1e5e9;
53
+ position: fixed;
54
+ top: 80px;
55
+ bottom: 0;
56
+ overflow-y: auto;
57
+ padding: 1rem;
58
+ }
59
+
60
+ .sidebar h3 {
61
+ color: #2c3e50;
62
+ margin-bottom: 1rem;
63
+ font-size: 1.1rem;
64
+ }
65
+
66
+ .sidebar ul {
67
+ list-style: none;
68
+ }
69
+
70
+ .sidebar li {
71
+ margin-bottom: 0.5rem;
72
+ }
73
+
74
+ .sidebar a {
75
+ text-decoration: none;
76
+ color: #495057;
77
+ padding: 0.5rem;
78
+ border-radius: 4px;
79
+ display: block;
80
+ transition: background-color 0.2s;
81
+ }
82
+
83
+ .sidebar a:hover {
84
+ background-color: #f8f9fa;
85
+ }
86
+
87
+ .method-badge {
88
+ display: inline-block;
89
+ padding: 0.2rem 0.5rem;
90
+ border-radius: 3px;
91
+ font-size: 0.7rem;
92
+ font-weight: bold;
93
+ margin-right: 0.5rem;
94
+ min-width: 45px;
95
+ text-align: center;
96
+ }
97
+
98
+ .method-get { background-color: #28a745; color: white; }
99
+ .method-post { background-color: #007bff; color: white; }
100
+ .method-put { background-color: #ffc107; color: black; }
101
+ .method-delete { background-color: #dc3545; color: white; }
102
+ .method-patch { background-color: #17a2b8; color: white; }
103
+
104
+ .main-content {
105
+ flex: 1;
106
+ margin-left: 300px;
107
+ margin-top: 80px;
108
+ padding: 2rem;
109
+ }
110
+
111
+ .endpoint {
112
+ background: #fff;
113
+ border-radius: 8px;
114
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
115
+ margin-bottom: 2rem;
116
+ overflow: hidden;
117
+ }
118
+
119
+ .endpoint-header {
120
+ padding: 1.5rem;
121
+ border-bottom: 1px solid #e1e5e9;
122
+ }
123
+
124
+ .endpoint-title {
125
+ display: flex;
126
+ align-items: center;
127
+ margin-bottom: 0.5rem;
128
+ }
129
+
130
+ .endpoint-path {
131
+ font-family: 'Monaco', 'Menlo', monospace;
132
+ font-size: 1.1rem;
133
+ margin-left: 0.5rem;
134
+ }
135
+
136
+ .endpoint-description {
137
+ color: #6c757d;
138
+ margin-top: 0.5rem;
139
+ }
140
+
141
+ .endpoint-content {
142
+ padding: 1.5rem;
143
+ }
144
+
145
+ .section {
146
+ margin-bottom: 2rem;
147
+ }
148
+
149
+ .section h4 {
150
+ color: #2c3e50;
151
+ margin-bottom: 1rem;
152
+ padding-bottom: 0.5rem;
153
+ border-bottom: 2px solid #e1e5e9;
154
+ }
155
+
156
+ .params-table {
157
+ width: 100%;
158
+ border-collapse: collapse;
159
+ margin-bottom: 1rem;
160
+ }
161
+
162
+ .params-table th,
163
+ .params-table td {
164
+ padding: 0.75rem;
165
+ text-align: left;
166
+ border-bottom: 1px solid #e1e5e9;
167
+ }
168
+
169
+ .params-table th {
170
+ background-color: #f8f9fa;
171
+ font-weight: 600;
172
+ }
173
+
174
+ .param-name {
175
+ font-family: 'Monaco', 'Menlo', monospace;
176
+ background-color: #f8f9fa;
177
+ padding: 0.2rem 0.4rem;
178
+ border-radius: 3px;
179
+ }
180
+
181
+ .code-block {
182
+ background-color: #f8f9fa;
183
+ border: 1px solid #e1e5e9;
184
+ border-radius: 4px;
185
+ padding: 1rem;
186
+ overflow-x: auto;
187
+ font-family: 'Monaco', 'Menlo', monospace;
188
+ font-size: 0.9rem;
189
+ }
190
+
191
+ .try-it-section {
192
+ background-color: #f8f9fa;
193
+ border-radius: 8px;
194
+ padding: 1.5rem;
195
+ margin-top: 2rem;
196
+ }
197
+
198
+ .try-it-form {
199
+ display: grid;
200
+ gap: 1rem;
201
+ }
202
+
203
+ .form-group {
204
+ display: flex;
205
+ flex-direction: column;
206
+ }
207
+
208
+ .form-group label {
209
+ margin-bottom: 0.5rem;
210
+ font-weight: 600;
211
+ }
212
+
213
+ .form-group input,
214
+ .form-group textarea {
215
+ padding: 0.5rem;
216
+ border: 1px solid #ced4da;
217
+ border-radius: 4px;
218
+ font-family: inherit;
219
+ }
220
+
221
+ .btn {
222
+ padding: 0.75rem 1.5rem;
223
+ border: none;
224
+ border-radius: 4px;
225
+ cursor: pointer;
226
+ font-weight: 600;
227
+ transition: background-color 0.2s;
228
+ }
229
+
230
+ .btn-primary {
231
+ background-color: #007bff;
232
+ color: white;
233
+ }
234
+
235
+ .btn-primary:hover {
236
+ background-color: #0056b3;
237
+ }
238
+
239
+ .response-section {
240
+ margin-top: 1rem;
241
+ padding: 1rem;
242
+ background-color: #fff;
243
+ border-radius: 4px;
244
+ border: 1px solid #e1e5e9;
245
+ }
246
+
247
+ .required-badge {
248
+ background-color: #dc3545;
249
+ color: white;
250
+ padding: 0.1rem 0.3rem;
251
+ border-radius: 3px;
252
+ font-size: 0.7rem;
253
+ margin-left: 0.5rem;
254
+ }
255
+
256
+ .optional-badge {
257
+ background-color: #6c757d;
258
+ color: white;
259
+ padding: 0.1rem 0.3rem;
260
+ border-radius: 3px;
261
+ font-size: 0.7rem;
262
+ margin-left: 0.5rem;
263
+ }
264
+ </style>
265
+
266
+ </head>
267
+ <body>
268
+ <div class="container">
269
+ <div class="header">
270
+ <h1>User Management API</h1>
271
+ <div class="meta">
272
+ Version: 2.0.0 | Base URL: <code>https://api.example.com/v2</code>
273
+ </div>
274
+ </div>
275
+
276
+ <div class="sidebar">
277
+ <h3>Endpoints</h3>
278
+ <ul>
279
+ <li>
280
+ <a href="#get-users">
281
+ <span class="method-badge method-get">GET</span>
282
+ Get all users
283
+ </a>
284
+ </li>
285
+ <li>
286
+ <a href="#get-usersid">
287
+ <span class="method-badge method-get">GET</span>
288
+ Get user by ID
289
+ </a>
290
+ </li>
291
+ <li>
292
+ <a href="#post-users">
293
+ <span class="method-badge method-post">POST</span>
294
+ Create new user
295
+ </a>
296
+ </li>
297
+ <li>
298
+ <a href="#put-usersid">
299
+ <span class="method-badge method-put">PUT</span>
300
+ Update user
301
+ </a>
302
+ </li>
303
+ <li>
304
+ <a href="#delete-usersid">
305
+ <span class="method-badge method-delete">DELETE</span>
306
+ Delete user
307
+ </a>
308
+ </li>
309
+ <li>
310
+ <a href="#get-userssearch">
311
+ <span class="method-badge method-get">GET</span>
312
+ Search users
313
+ </a>
314
+ </li>
315
+
316
+ </ul>
317
+ </div>
318
+
319
+ <div class="main-content">
320
+ <div class="endpoint" id="get-users">
321
+ <div class="endpoint-header">
322
+ <div class="endpoint-title">
323
+ <span class="method-badge method-get">GET</span>
324
+ <span class="endpoint-path">/users</span>
325
+ </div>
326
+ <div class="endpoint-summary"><strong>Get all users</strong></div>
327
+ <div class="endpoint-description">Retrieve a paginated list of all users in the system</div>
328
+ </div>
329
+ <div class="endpoint-content">
330
+ <div class="section">
331
+ <h4>Response</h4>
332
+ <p><strong>Content-Type:</strong> <code>application/json</code></p>
333
+ <div class="code-block">[
334
+ {
335
+ &quot;id&quot;: 123,
336
+ &quot;name&quot;: &quot;example string&quot;,
337
+ &quot;email&quot;: &quot;example string&quot;,
338
+ &quot;created_at&quot;: &quot;2025-01-15T10:30:00Z&quot;
339
+ }
340
+ ]</div>
341
+
342
+ </div>
343
+ <div class="try-it-section">
344
+ <h4>Try it out</h4>
345
+ <form class="try-it-form" onsubmit="return tryRequest('get_users', 'GET', '/users')">
346
+
347
+ <button type="submit" class="btn btn-primary">Send Request</button>
348
+ </form>
349
+ <div id="get_users_response" class="response-section" style="display: none;">
350
+ <h5>Response</h5>
351
+ <div class="code-block" id="get_users_response_content"></div>
352
+ </div>
353
+ </div>
354
+
355
+ </div>
356
+ </div>
357
+ <div class="endpoint" id="get-usersid">
358
+ <div class="endpoint-header">
359
+ <div class="endpoint-title">
360
+ <span class="method-badge method-get">GET</span>
361
+ <span class="endpoint-path">/users/:id</span>
362
+ </div>
363
+ <div class="endpoint-summary"><strong>Get user by ID</strong></div>
364
+ <div class="endpoint-description">Retrieve a specific user by their unique identifier</div>
365
+ </div>
366
+ <div class="endpoint-content">
367
+ <div class="section">
368
+ <h4>Path Parameters</h4>
369
+ <table class="params-table">
370
+ <thead>
371
+ <tr>
372
+ <th>Parameter</th>
373
+ <th>Type</th>
374
+ <th>Required</th>
375
+ <th>Description</th>
376
+ </tr>
377
+ </thead>
378
+ <tbody>
379
+ <tr>
380
+ <td><code class="param-name">id</code></td>
381
+ <td>integer</td>
382
+ <td><span class="required-badge">Required</span></td>
383
+ <td>No description</td>
384
+ </tr>
385
+
386
+ </tbody>
387
+ </table>
388
+ </div>
389
+ <div class="section">
390
+ <h4>Response</h4>
391
+ <p><strong>Content-Type:</strong> <code>application/json</code></p>
392
+ <div class="code-block">{
393
+ &quot;id&quot;: 123,
394
+ &quot;name&quot;: &quot;example string&quot;,
395
+ &quot;email&quot;: &quot;example string&quot;,
396
+ &quot;created_at&quot;: &quot;2025-01-15T10:30:00Z&quot;,
397
+ &quot;updated_at&quot;: &quot;2025-01-15T10:30:00Z&quot;
398
+ }</div>
399
+
400
+ </div>
401
+ <div class="try-it-section">
402
+ <h4>Try it out</h4>
403
+ <form class="try-it-form" onsubmit="return tryRequest('get_usersid', 'GET', '/users/:id')">
404
+ <div class="form-group">
405
+ <label for="get_usersid_id">id (path parameter)</label>
406
+ <input type="text" id="get_usersid_id" name="id" placeholder="Enter id" required>
407
+ </div>
408
+
409
+ <button type="submit" class="btn btn-primary">Send Request</button>
410
+ </form>
411
+ <div id="get_usersid_response" class="response-section" style="display: none;">
412
+ <h5>Response</h5>
413
+ <div class="code-block" id="get_usersid_response_content"></div>
414
+ </div>
415
+ </div>
416
+
417
+ </div>
418
+ </div>
419
+ <div class="endpoint" id="post-users">
420
+ <div class="endpoint-header">
421
+ <div class="endpoint-title">
422
+ <span class="method-badge method-post">POST</span>
423
+ <span class="endpoint-path">/users</span>
424
+ </div>
425
+ <div class="endpoint-summary"><strong>Create new user</strong></div>
426
+ <div class="endpoint-description">Create a new user account with the provided information</div>
427
+ </div>
428
+ <div class="endpoint-content">
429
+ <div class="section">
430
+ <h4>Request Body</h4>
431
+ <p><strong>Content-Type:</strong> <code>application/json</code></p>
432
+ <div class="code-block">{
433
+ &quot;name&quot;: &quot;example string&quot;,
434
+ &quot;email&quot;: &quot;example string&quot;,
435
+ &quot;password&quot;: &quot;example string&quot;
436
+ }</div>
437
+ </div>
438
+ <div class="section">
439
+ <h4>Response</h4>
440
+ <p><strong>Content-Type:</strong> <code>application/json</code></p>
441
+ <div class="code-block">{
442
+ &quot;id&quot;: 123,
443
+ &quot;name&quot;: &quot;example string&quot;,
444
+ &quot;email&quot;: &quot;example string&quot;,
445
+ &quot;created_at&quot;: &quot;2025-01-15T10:30:00Z&quot;
446
+ }</div>
447
+
448
+ </div>
449
+ <div class="try-it-section">
450
+ <h4>Try it out</h4>
451
+ <form class="try-it-form" onsubmit="return tryRequest('post_users', 'POST', '/users')">
452
+ <div class="form-group">
453
+ <label for="post_users_body">Request Body (JSON)</label>
454
+ <textarea id="post_users_body" name="body" rows="6" placeholder="Enter JSON body">{
455
+ &quot;name&quot;: &quot;example string&quot;,
456
+ &quot;email&quot;: &quot;example string&quot;,
457
+ &quot;password&quot;: &quot;example string&quot;
458
+ }</textarea>
459
+ </div>
460
+
461
+ <button type="submit" class="btn btn-primary">Send Request</button>
462
+ </form>
463
+ <div id="post_users_response" class="response-section" style="display: none;">
464
+ <h5>Response</h5>
465
+ <div class="code-block" id="post_users_response_content"></div>
466
+ </div>
467
+ </div>
468
+
469
+ </div>
470
+ </div>
471
+ <div class="endpoint" id="put-usersid">
472
+ <div class="endpoint-header">
473
+ <div class="endpoint-title">
474
+ <span class="method-badge method-put">PUT</span>
475
+ <span class="endpoint-path">/users/:id</span>
476
+ </div>
477
+ <div class="endpoint-summary"><strong>Update user</strong></div>
478
+ <div class="endpoint-description">Update an existing user's information</div>
479
+ </div>
480
+ <div class="endpoint-content">
481
+ <div class="section">
482
+ <h4>Path Parameters</h4>
483
+ <table class="params-table">
484
+ <thead>
485
+ <tr>
486
+ <th>Parameter</th>
487
+ <th>Type</th>
488
+ <th>Required</th>
489
+ <th>Description</th>
490
+ </tr>
491
+ </thead>
492
+ <tbody>
493
+ <tr>
494
+ <td><code class="param-name">id</code></td>
495
+ <td>integer</td>
496
+ <td><span class="required-badge">Required</span></td>
497
+ <td>No description</td>
498
+ </tr>
499
+
500
+ </tbody>
501
+ </table>
502
+ </div>
503
+ <div class="section">
504
+ <h4>Request Body</h4>
505
+ <p><strong>Content-Type:</strong> <code>application/json</code></p>
506
+ <div class="code-block">{
507
+ &quot;name&quot;: &quot;example string&quot;,
508
+ &quot;email&quot;: &quot;example string&quot;
509
+ }</div>
510
+ </div>
511
+ <div class="section">
512
+ <h4>Response</h4>
513
+ <p><strong>Content-Type:</strong> <code>application/json</code></p>
514
+ <div class="code-block">{
515
+ &quot;id&quot;: 123,
516
+ &quot;name&quot;: &quot;example string&quot;,
517
+ &quot;email&quot;: &quot;example string&quot;,
518
+ &quot;updated_at&quot;: &quot;2025-01-15T10:30:00Z&quot;
519
+ }</div>
520
+
521
+ </div>
522
+ <div class="try-it-section">
523
+ <h4>Try it out</h4>
524
+ <form class="try-it-form" onsubmit="return tryRequest('put_usersid', 'PUT', '/users/:id')">
525
+ <div class="form-group">
526
+ <label for="put_usersid_id">id (path parameter)</label>
527
+ <input type="text" id="put_usersid_id" name="id" placeholder="Enter id" required>
528
+ </div>
529
+ <div class="form-group">
530
+ <label for="put_usersid_body">Request Body (JSON)</label>
531
+ <textarea id="put_usersid_body" name="body" rows="6" placeholder="Enter JSON body">{
532
+ &quot;name&quot;: &quot;example string&quot;,
533
+ &quot;email&quot;: &quot;example string&quot;
534
+ }</textarea>
535
+ </div>
536
+
537
+ <button type="submit" class="btn btn-primary">Send Request</button>
538
+ </form>
539
+ <div id="put_usersid_response" class="response-section" style="display: none;">
540
+ <h5>Response</h5>
541
+ <div class="code-block" id="put_usersid_response_content"></div>
542
+ </div>
543
+ </div>
544
+
545
+ </div>
546
+ </div>
547
+ <div class="endpoint" id="delete-usersid">
548
+ <div class="endpoint-header">
549
+ <div class="endpoint-title">
550
+ <span class="method-badge method-delete">DELETE</span>
551
+ <span class="endpoint-path">/users/:id</span>
552
+ </div>
553
+ <div class="endpoint-summary"><strong>Delete user</strong></div>
554
+ <div class="endpoint-description">Delete a user account permanently</div>
555
+ </div>
556
+ <div class="endpoint-content">
557
+ <div class="section">
558
+ <h4>Path Parameters</h4>
559
+ <table class="params-table">
560
+ <thead>
561
+ <tr>
562
+ <th>Parameter</th>
563
+ <th>Type</th>
564
+ <th>Required</th>
565
+ <th>Description</th>
566
+ </tr>
567
+ </thead>
568
+ <tbody>
569
+ <tr>
570
+ <td><code class="param-name">id</code></td>
571
+ <td>integer</td>
572
+ <td><span class="required-badge">Required</span></td>
573
+ <td>No description</td>
574
+ </tr>
575
+
576
+ </tbody>
577
+ </table>
578
+ </div>
579
+ <div class="section">
580
+ <h4>Response</h4>
581
+ <p><strong>Content-Type:</strong> <code>application/json</code></p>
582
+ <div class="code-block">{
583
+ &quot;success&quot;: true,
584
+ &quot;message&quot;: &quot;example string&quot;
585
+ }</div>
586
+
587
+ </div>
588
+ <div class="try-it-section">
589
+ <h4>Try it out</h4>
590
+ <form class="try-it-form" onsubmit="return tryRequest('delete_usersid', 'DELETE', '/users/:id')">
591
+ <div class="form-group">
592
+ <label for="delete_usersid_id">id (path parameter)</label>
593
+ <input type="text" id="delete_usersid_id" name="id" placeholder="Enter id" required>
594
+ </div>
595
+
596
+ <button type="submit" class="btn btn-primary">Send Request</button>
597
+ </form>
598
+ <div id="delete_usersid_response" class="response-section" style="display: none;">
599
+ <h5>Response</h5>
600
+ <div class="code-block" id="delete_usersid_response_content"></div>
601
+ </div>
602
+ </div>
603
+
604
+ </div>
605
+ </div>
606
+ <div class="endpoint" id="get-userssearch">
607
+ <div class="endpoint-header">
608
+ <div class="endpoint-title">
609
+ <span class="method-badge method-get">GET</span>
610
+ <span class="endpoint-path">/users/search</span>
611
+ </div>
612
+ <div class="endpoint-summary"><strong>Search users</strong></div>
613
+ <div class="endpoint-description">Search for users by name or email with pagination support</div>
614
+ </div>
615
+ <div class="endpoint-content">
616
+ <div class="section">
617
+ <h4>Query Parameters</h4>
618
+ <table class="params-table">
619
+ <thead>
620
+ <tr>
621
+ <th>Parameter</th>
622
+ <th>Type</th>
623
+ <th>Required</th>
624
+ <th>Description</th>
625
+ </tr>
626
+ </thead>
627
+ <tbody>
628
+ <tr>
629
+ <td><code class="param-name">q</code></td>
630
+ <td>string</td>
631
+ <td><span class="required-badge">Required</span></td>
632
+ <td>No description</td>
633
+ </tr>
634
+ <tr>
635
+ <td><code class="param-name">limit</code></td>
636
+ <td>integer</td>
637
+ <td><span class="optional-badge">Optional</span></td>
638
+ <td>No description</td>
639
+ </tr>
640
+ <tr>
641
+ <td><code class="param-name">offset</code></td>
642
+ <td>integer</td>
643
+ <td><span class="optional-badge">Optional</span></td>
644
+ <td>No description</td>
645
+ </tr>
646
+
647
+ </tbody>
648
+ </table>
649
+ </div>
650
+ <div class="section">
651
+ <h4>Response</h4>
652
+ <p><strong>Content-Type:</strong> <code>application/json</code></p>
653
+ <div class="code-block">{
654
+ &quot;users&quot;: [
655
+ {
656
+ &quot;id&quot;: 123,
657
+ &quot;name&quot;: &quot;example string&quot;,
658
+ &quot;email&quot;: &quot;example string&quot;
659
+ }
660
+ ],
661
+ &quot;total&quot;: 123,
662
+ &quot;limit&quot;: 123,
663
+ &quot;offset&quot;: 123
664
+ }</div>
665
+
666
+ </div>
667
+ <div class="try-it-section">
668
+ <h4>Try it out</h4>
669
+ <form class="try-it-form" onsubmit="return tryRequest('get_userssearch', 'GET', '/users/search')">
670
+ <div class="form-group">
671
+ <label for="get_userssearch_q">q (query parameter)</label>
672
+ <input type="text" id="get_userssearch_q" name="q" placeholder="Enter q" required>
673
+ </div>
674
+ <div class="form-group">
675
+ <label for="get_userssearch_limit">limit (query parameter)</label>
676
+ <input type="text" id="get_userssearch_limit" name="limit" placeholder="Enter limit" >
677
+ </div>
678
+ <div class="form-group">
679
+ <label for="get_userssearch_offset">offset (query parameter)</label>
680
+ <input type="text" id="get_userssearch_offset" name="offset" placeholder="Enter offset" >
681
+ </div>
682
+
683
+ <button type="submit" class="btn btn-primary">Send Request</button>
684
+ </form>
685
+ <div id="get_userssearch_response" class="response-section" style="display: none;">
686
+ <h5>Response</h5>
687
+ <div class="code-block" id="get_userssearch_response_content"></div>
688
+ </div>
689
+ </div>
690
+
691
+ </div>
692
+ </div>
693
+
694
+ </div>
695
+
696
+ </div>
697
+ <script>
698
+ async function tryRequest(endpointId, method, path) {
699
+ event.preventDefault();
700
+
701
+ const form = event.target;
702
+ const formData = new FormData(form);
703
+
704
+ // Build URL with path parameters
705
+ let url = 'https://api.example.com/v2' + path;
706
+ const pathParams = {};
707
+ const queryParams = {};
708
+ let body = null;
709
+
710
+ // Process form data
711
+ for (const [key, value] of formData.entries()) {
712
+ if (key === 'body') {
713
+ if (value.trim()) {
714
+ try {
715
+ body = JSON.parse(value);
716
+ } catch (e) {
717
+ alert('Invalid JSON in request body');
718
+ return false;
719
+ }
720
+ }
721
+ } else if (path.includes(':' + key)) {
722
+ pathParams[key] = value;
723
+ } else if (value.trim()) {
724
+ queryParams[key] = value;
725
+ }
726
+ }
727
+
728
+ // Replace path parameters
729
+ for (const [key, value] of Object.entries(pathParams)) {
730
+ url = url.replace(':' + key, encodeURIComponent(value));
731
+ }
732
+
733
+ // Add query parameters
734
+ const queryString = new URLSearchParams(queryParams).toString();
735
+ if (queryString) {
736
+ url += '?' + queryString;
737
+ }
738
+
739
+ // Prepare request options
740
+ const options = {
741
+ method: method,
742
+ headers: {
743
+ 'Content-Type': 'application/json',
744
+ 'Accept': 'application/json'
745
+ }
746
+ };
747
+
748
+ if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
749
+ options.body = JSON.stringify(body);
750
+ }
751
+
752
+ // Show loading state
753
+ const responseDiv = document.getElementById(endpointId + '_response');
754
+ const responseContent = document.getElementById(endpointId + '_response_content');
755
+ responseDiv.style.display = 'block';
756
+ responseContent.textContent = 'Loading...';
757
+
758
+ try {
759
+ const response = await fetch(url, options);
760
+ const responseText = await response.text();
761
+
762
+ let responseData;
763
+ try {
764
+ responseData = JSON.parse(responseText);
765
+ responseContent.innerHTML = `
766
+ <strong>Status:</strong> ${response.status} ${response.statusText}<br><br>
767
+ <strong>Response:</strong><br>
768
+ ${JSON.stringify(responseData, null, 2)}
769
+ `;
770
+ } catch (e) {
771
+ responseContent.innerHTML = `
772
+ <strong>Status:</strong> ${response.status} ${response.statusText}<br><br>
773
+ <strong>Response:</strong><br>
774
+ ${responseText}
775
+ `;
776
+ }
777
+ } catch (error) {
778
+ responseContent.innerHTML = `
779
+ <strong>Error:</strong><br>
780
+ ${error.message}
781
+ `;
782
+ }
783
+
784
+ return false;
785
+ }
786
+ </script>
787
+
788
+ </body>
789
+ </html>