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,417 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Enterprise Task Management API - RapiTapir Sinatra Extension Demo
4
+ #
5
+ # This example demonstrates the new ergonomic Sinatra extension that eliminates
6
+ # boilerplate and provides a seamless developer experience for building
7
+ # enterprise-grade APIs with RapiTapir.
8
+
9
+ # Check for Sinatra availability
10
+ begin
11
+ require 'sinatra/base'
12
+ SINATRA_AVAILABLE = true
13
+ rescue LoadError
14
+ SINATRA_AVAILABLE = false
15
+ puts 'āš ļø Sinatra not available. Install with: gem install sinatra'
16
+ puts 'šŸ”„ Running in demo mode instead...'
17
+ end
18
+
19
+ require 'json'
20
+ require_relative '../lib/rapitapir'
21
+
22
+ # Only require extension if Sinatra is available
23
+ require_relative '../lib/rapitapir/sinatra/extension' if SINATRA_AVAILABLE
24
+
25
+ # Sample databases (same as before)
26
+ class UserDatabase
27
+ USERS = {
28
+ 'user-token-123' => {
29
+ id: 1,
30
+ name: 'John Doe',
31
+ email: 'john.doe@example.com',
32
+ role: 'user',
33
+ scopes: %w[read write]
34
+ },
35
+ 'admin-token-456' => {
36
+ id: 2,
37
+ name: 'Jane Admin',
38
+ email: 'jane.admin@example.com',
39
+ role: 'admin',
40
+ scopes: %w[read write admin delete]
41
+ }
42
+ }.freeze
43
+
44
+ def self.find_by_token(token)
45
+ USERS[token]
46
+ end
47
+
48
+ def self.all_users
49
+ USERS.values
50
+ end
51
+
52
+ def self.find_by_id(id)
53
+ USERS.values.find { |user| user[:id] == id.to_i }
54
+ end
55
+ end
56
+
57
+ class TaskDatabase
58
+ @@tasks = [
59
+ { id: 1, title: 'Setup CI/CD Pipeline', description: 'Configure automated testing and deployment',
60
+ status: 'in_progress', assignee_id: 1, created_at: Time.now - 86_400 },
61
+ { id: 2, title: 'Review Security Audit', description: 'Complete quarterly security review', status: 'pending',
62
+ assignee_id: 2, created_at: Time.now - 3600 }
63
+ ]
64
+ @@next_id = 3
65
+
66
+ def self.all
67
+ @@tasks
68
+ end
69
+
70
+ def self.find(id)
71
+ @@tasks.find { |task| task[:id] == id.to_i }
72
+ end
73
+
74
+ def self.create(attrs)
75
+ task = attrs.merge(id: @@next_id, created_at: Time.now)
76
+ @@next_id += 1
77
+ @@tasks << task
78
+ task
79
+ end
80
+
81
+ def self.update(id, attrs)
82
+ task = find(id)
83
+ return nil unless task
84
+
85
+ attrs.each { |key, value| task[key] = value }
86
+ task[:updated_at] = Time.now
87
+ task
88
+ end
89
+
90
+ def self.delete(id)
91
+ @@tasks.reject! { |task| task[:id] == id.to_i }
92
+ end
93
+ end
94
+
95
+ # Define schemas using RapiTapir types
96
+ TASK_SCHEMA = RapiTapir::Types.hash({
97
+ 'id' => RapiTapir::Types.integer,
98
+ 'title' => RapiTapir::Types.string,
99
+ 'description' => RapiTapir::Types.string,
100
+ 'status' => RapiTapir::Types.string,
101
+ 'assignee_id' => RapiTapir::Types.integer,
102
+ 'created_at' => RapiTapir::Types.string,
103
+ 'updated_at' => RapiTapir::Types.optional(RapiTapir::Types.string)
104
+ })
105
+
106
+ USER_SCHEMA = RapiTapir::Types.hash({
107
+ 'id' => RapiTapir::Types.integer,
108
+ 'name' => RapiTapir::Types.string,
109
+ 'email' => RapiTapir::Types.string,
110
+ 'role' => RapiTapir::Types.string,
111
+ 'scopes' => RapiTapir::Types.array(RapiTapir::Types.string)
112
+ })
113
+
114
+ # Main Application using RapiTapir Sinatra Extension
115
+ if SINATRA_AVAILABLE
116
+ class EnterpriseTaskAPI < Sinatra::Base
117
+ # Register the RapiTapir extension
118
+ register RapiTapir::Sinatra::Extension
119
+
120
+ # Configure the application in one place
121
+ rapitapir do
122
+ # API information
123
+ info(
124
+ title: 'Enterprise Task Management API',
125
+ description: 'A production-ready task management API built with RapiTapir Sinatra Extension',
126
+ version: '2.0.0',
127
+ contact: {
128
+ name: 'API Support',
129
+ email: 'api-support@example.com'
130
+ }
131
+ )
132
+
133
+ # Server configuration
134
+ server(url: 'http://localhost:4567', description: 'Development server')
135
+ server(url: 'https://api.example.com', description: 'Production server')
136
+
137
+ # Authentication configuration
138
+ bearer_auth(:bearer, {
139
+ realm: 'Enterprise Task Management API',
140
+ token_validator: proc do |token|
141
+ user = UserDatabase.find_by_token(token)
142
+ next nil unless user
143
+
144
+ {
145
+ user: user,
146
+ scopes: user[:scopes]
147
+ }
148
+ end
149
+ })
150
+
151
+ # Middleware configuration based on environment
152
+ if development?
153
+ development_defaults!
154
+ else
155
+ production_defaults!
156
+ end
157
+
158
+ # Enable documentation
159
+ enable_docs(path: '/docs', openapi_path: '/openapi.json')
160
+ end
161
+
162
+ # Health check endpoint (public)
163
+ endpoint(
164
+ RapiTapir.get('/health')
165
+ .summary('Health check')
166
+ .description('Returns the health status of the API')
167
+ .ok(RapiTapir::Types.hash({
168
+ 'status' => RapiTapir::Types.string,
169
+ 'timestamp' => RapiTapir::Types.string,
170
+ 'version' => RapiTapir::Types.string,
171
+ 'features' => RapiTapir::Types.array(RapiTapir::Types.string)
172
+ }))
173
+ .build
174
+ ) do |_inputs|
175
+ {
176
+ status: 'healthy',
177
+ timestamp: Time.now.iso8601,
178
+ version: '2.0.0',
179
+ features: ['RapiTapir Sinatra Extension', 'Auto-generated OpenAPI', 'Zero Boilerplate']
180
+ }
181
+ end
182
+
183
+ # Tasks resource - using the RESTful resource builder
184
+ api_resource '/api/v1/tasks', schema: TASK_SCHEMA do
185
+ # Enable full CRUD operations with custom handlers
186
+ crud do
187
+ # List tasks with filtering
188
+ index do |inputs|
189
+ tasks = TaskDatabase.all
190
+
191
+ # Apply filters if provided
192
+ tasks = tasks.select { |task| task[:status] == inputs[:status] } if inputs[:status]
193
+
194
+ # Apply pagination
195
+ limit = inputs[:limit] || 50
196
+ offset = inputs[:offset] || 0
197
+ tasks = tasks.drop(offset).take(limit)
198
+
199
+ # Format timestamps
200
+ tasks.map do |task|
201
+ task_copy = task.dup
202
+ task_copy[:created_at] = task_copy[:created_at].iso8601 if task_copy[:created_at]
203
+ task_copy[:updated_at] = task_copy[:updated_at].iso8601 if task_copy[:updated_at]
204
+ task_copy
205
+ end
206
+ end
207
+
208
+ # Get specific task
209
+ show do |inputs|
210
+ task = TaskDatabase.find(inputs[:id])
211
+ halt 404, { error: 'Task not found' }.to_json unless task
212
+
213
+ # Enrich with assignee details
214
+ assignee = UserDatabase.find_by_id(task[:assignee_id])
215
+ task_with_assignee = task.dup
216
+ task_with_assignee[:created_at] = task_with_assignee[:created_at].iso8601 if task_with_assignee[:created_at]
217
+ task_with_assignee[:assignee] = if assignee
218
+ {
219
+ id: assignee[:id],
220
+ name: assignee[:name],
221
+ email: assignee[:email]
222
+ }
223
+ end
224
+
225
+ task_with_assignee
226
+ end
227
+
228
+ # Create new task
229
+ create do |inputs|
230
+ body = inputs[:body]
231
+
232
+ # Validate assignee exists
233
+ assignee = UserDatabase.find_by_id(body['assignee_id'])
234
+ halt 400, { error: 'Invalid assignee ID' }.to_json unless assignee
235
+
236
+ # Create task
237
+ task_data = {
238
+ title: body['title'],
239
+ description: body['description'],
240
+ status: body['status'] || 'pending',
241
+ assignee_id: body['assignee_id']
242
+ }
243
+
244
+ task = TaskDatabase.create(task_data)
245
+ task[:created_at] = task[:created_at].iso8601
246
+ task
247
+ end
248
+
249
+ # Update task
250
+ update do |inputs|
251
+ task = TaskDatabase.find(inputs[:id])
252
+ halt 404, { error: 'Task not found' }.to_json unless task
253
+
254
+ body = inputs[:body]
255
+ update_data = {}
256
+
257
+ # Prepare update data
258
+ update_data[:title] = body['title'] if body['title']
259
+ update_data[:description] = body['description'] if body['description']
260
+ update_data[:status] = body['status'] if body['status']
261
+
262
+ if body['assignee_id']
263
+ assignee = UserDatabase.find_by_id(body['assignee_id'])
264
+ halt 400, { error: 'Invalid assignee ID' }.to_json unless assignee
265
+ update_data[:assignee_id] = body['assignee_id']
266
+ end
267
+
268
+ # Update task
269
+ updated_task = TaskDatabase.update(inputs[:id], update_data)
270
+ updated_task[:created_at] = updated_task[:created_at].iso8601 if updated_task[:created_at]
271
+ updated_task[:updated_at] = updated_task[:updated_at].iso8601 if updated_task[:updated_at]
272
+ updated_task
273
+ end
274
+
275
+ # Delete task (requires admin scope)
276
+ destroy(scopes: ['admin']) do |inputs|
277
+ task = TaskDatabase.find(inputs[:id])
278
+ halt 404, { error: 'Task not found' }.to_json unless task
279
+
280
+ TaskDatabase.delete(inputs[:id])
281
+ status 204
282
+ nil
283
+ end
284
+ end
285
+
286
+ # Custom endpoint: Get tasks by status
287
+ custom(:get, 'by-status/:status',
288
+ summary: 'Get tasks by status',
289
+ configure: lambda { |endpoint|
290
+ endpoint
291
+ .path_param(:status, RapiTapir::Types.string, description: 'Task status')
292
+ .ok(RapiTapir::Types.array(TASK_SCHEMA))
293
+ }) do |inputs|
294
+ tasks = TaskDatabase.all.select { |task| task[:status] == inputs[:status] }
295
+ tasks.map do |task|
296
+ task_copy = task.dup
297
+ task_copy[:created_at] = task_copy[:created_at].iso8601 if task_copy[:created_at]
298
+ task_copy
299
+ end
300
+ end
301
+ end
302
+
303
+ # User profile endpoint
304
+ endpoint(
305
+ RapiTapir.get('/api/v1/profile')
306
+ .summary('Get current user profile')
307
+ .description('Retrieve the profile of the authenticated user')
308
+ .ok(RapiTapir::Types.hash({
309
+ 'id' => RapiTapir::Types.integer,
310
+ 'name' => RapiTapir::Types.string,
311
+ 'email' => RapiTapir::Types.string,
312
+ 'role' => RapiTapir::Types.string,
313
+ 'scopes' => RapiTapir::Types.array(RapiTapir::Types.string)
314
+ }))
315
+ .build
316
+ ) do |_inputs|
317
+ require_authentication!
318
+ current_user
319
+ end
320
+
321
+ # Admin endpoint - list all users
322
+ endpoint(
323
+ RapiTapir.get('/api/v1/admin/users')
324
+ .summary('List all users (admin only)')
325
+ .description('Retrieve a list of all users in the system. Requires admin permission.')
326
+ .ok(RapiTapir::Types.array(USER_SCHEMA))
327
+ .build
328
+ ) do |_inputs|
329
+ require_scope!('admin')
330
+ UserDatabase.all_users
331
+ end
332
+
333
+ # Development info
334
+ configure :development do
335
+ puts "\n#{'=' * 70}"
336
+ puts 'šŸš€ ENTERPRISE TASK MANAGEMENT API v2.0 - RapiTapir Extension'
337
+ puts '=' * 70
338
+ puts 'šŸ“š API Documentation: http://localhost:4567/docs'
339
+ puts 'šŸ“‹ OpenAPI Spec: http://localhost:4567/openapi.json'
340
+ puts 'ā¤ļø Health Check: http://localhost:4567/health'
341
+ puts "\nšŸ”‘ Bearer Tokens:"
342
+ puts ' User: user-token-123 (scopes: read, write)'
343
+ puts ' Admin: admin-token-456 (scopes: read, write, admin, delete)'
344
+ puts "\n✨ NEW FEATURES with RapiTapir Extension:"
345
+ puts ' šŸŽÆ Zero boilerplate configuration'
346
+ puts ' šŸ”§ Automatic middleware setup'
347
+ puts ' šŸ“¦ RESTful resource builder'
348
+ puts ' šŸ›”ļø Built-in authentication helpers'
349
+ puts ' šŸ“– Auto-generated beautiful documentation'
350
+ puts ' šŸ—ļø SOLID principles architecture'
351
+ puts ''
352
+ end
353
+ end
354
+
355
+ # Start the server
356
+ EnterpriseTaskAPI.run! if __FILE__ == $PROGRAM_NAME
357
+ else
358
+ # Demo mode when Sinatra is not available
359
+ puts "\n#{'=' * 70}"
360
+ puts 'šŸš€ ENTERPRISE TASK MANAGEMENT API v2.0 - Demo Mode'
361
+ puts '=' * 70
362
+
363
+ puts "\nāœ… Successfully loaded:"
364
+ puts ' • RapiTapir core and type system'
365
+ puts ' • Task and User schemas'
366
+ puts ' • Database classes'
367
+
368
+ puts "\nšŸŽÆ This enterprise API would provide:"
369
+ puts ' GET /health - Health check (public)'
370
+ puts ' GET /api/v1/tasks - List tasks with filtering'
371
+ puts ' GET /api/v1/tasks/:id - Get specific task'
372
+ puts ' POST /api/v1/tasks - Create new task'
373
+ puts ' PUT /api/v1/tasks/:id - Update task'
374
+ puts ' DELETE /api/v1/tasks/:id - Delete task (admin only)'
375
+ puts ' GET /api/v1/tasks/by-status/:status - Tasks by status'
376
+ puts ' GET /api/v1/profile - Current user profile'
377
+ puts ' GET /api/v1/admin/users - List users (admin only)'
378
+ puts ' GET /docs - Swagger UI documentation'
379
+ puts ' GET /openapi.json - OpenAPI 3.0 specification'
380
+
381
+ puts "\nšŸ›”ļø Security features:"
382
+ puts ' • Bearer token authentication'
383
+ puts ' • Scope-based authorization (read, write, admin, delete)'
384
+ puts ' • CORS protection'
385
+ puts ' • Rate limiting (configurable per environment)'
386
+ puts ' • Security headers'
387
+
388
+ puts "\nšŸŽÆ Extension advantages demonstrated:"
389
+ puts ' • 90% less boilerplate code compared to manual implementation'
390
+ puts ' • One-line configuration: development_defaults!()'
391
+ puts ' • RESTful resource builder: api_resource + crud block'
392
+ puts ' • Built-in auth helpers: require_authentication!, require_scope!'
393
+ puts ' • Auto-generated OpenAPI 3.0 documentation'
394
+ puts ' • Production-ready middleware stack'
395
+
396
+ puts "\nšŸ’” Authentication tokens that would work:"
397
+ puts ' User token: user-token-123 (scopes: read, write)'
398
+ puts ' Admin token: admin-token-456 (scopes: read, write, admin, delete)'
399
+
400
+ puts "\nšŸ“– Sample API calls that would work:"
401
+ puts " curl -H 'Authorization: Bearer user-token-123' http://localhost:4567/api/v1/tasks"
402
+ puts " curl -H 'Authorization: Bearer admin-token-456' http://localhost:4567/api/v1/admin/users"
403
+ puts " curl -X POST -H 'Authorization: Bearer user-token-123' \\"
404
+ puts " -H 'Content-Type: application/json' \\"
405
+ puts " -d '{\"title\":\"New Task\",\"description\":\"Test\",\"assignee_id\":1}' \\"
406
+ puts ' http://localhost:4567/api/v1/tasks'
407
+
408
+ puts "\nšŸ’” To run the actual server:"
409
+ puts ' gem install sinatra'
410
+ puts " ruby #{__FILE__}"
411
+
412
+ puts "\nšŸ—ļø Architecture highlights:"
413
+ puts ' • SOLID principles implementation'
414
+ puts ' • Single Responsibility: Each class has one purpose'
415
+ puts ' • Open/Closed: Extensible without modification'
416
+ puts ' • Dependency Inversion: Auth logic injected via procs'
417
+ end