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,384 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rapitapir'
4
+ require 'rack'
5
+
6
+ # Advanced observability configuration
7
+ RapiTapir.configure do |config|
8
+ # Comprehensive metrics setup
9
+ config.metrics.enable_prometheus(
10
+ namespace: 'ecommerce_api',
11
+ labels: {
12
+ service: 'order_service',
13
+ version: ENV.fetch('APP_VERSION', '1.0.0'),
14
+ environment: ENV.fetch('RAILS_ENV', 'development')
15
+ }
16
+ )
17
+
18
+ # Detailed tracing configuration
19
+ config.tracing.enable_opentelemetry(
20
+ service_name: 'ecommerce-order-api',
21
+ service_version: ENV.fetch('APP_VERSION', '1.0.0')
22
+ )
23
+
24
+ # Structured logging with custom fields
25
+ config.logging.enable_structured(
26
+ level: :info,
27
+ fields: %i[
28
+ timestamp level message request_id
29
+ method path status duration
30
+ user_id tenant_id trace_id span_id
31
+ ]
32
+ )
33
+
34
+ # Comprehensive health checks
35
+ config.health_check.enable(endpoint: '/health')
36
+
37
+ # Database health check
38
+ config.health_check.add_check(:database) do
39
+ # Simulate database connection check
40
+ start_time = Time.now
41
+ # ActiveRecord::Base.connection.execute("SELECT 1")
42
+ duration = Time.now - start_time
43
+
44
+ {
45
+ status: :healthy,
46
+ message: 'Database connection successful',
47
+ response_time_ms: (duration * 1000).round(2)
48
+ }
49
+ rescue StandardError => e
50
+ {
51
+ status: :unhealthy,
52
+ message: "Database connection failed: #{e.message}"
53
+ }
54
+ end
55
+
56
+ # Redis health check
57
+ config.health_check.add_check(:redis) do
58
+ # Simulate Redis connection check
59
+ start_time = Time.now
60
+ # Redis.current.ping
61
+ duration = Time.now - start_time
62
+
63
+ {
64
+ status: :healthy,
65
+ message: 'Redis connection successful',
66
+ response_time_ms: (duration * 1000).round(2)
67
+ }
68
+ rescue StandardError => e
69
+ {
70
+ status: :unhealthy,
71
+ message: "Redis connection failed: #{e.message}"
72
+ }
73
+ end
74
+
75
+ # External API health check
76
+ config.health_check.add_check(:payment_gateway) do
77
+ # Simulate external API health check
78
+ start_time = Time.now
79
+ # HTTP.get("https://api.stripe.com/v1/charges", headers: { "Authorization" => "Bearer sk_test_..." })
80
+ duration = Time.now - start_time
81
+
82
+ if duration > 5.0 # More than 5 seconds is concerning
83
+ {
84
+ status: :warning,
85
+ message: 'Payment gateway responding slowly',
86
+ response_time_ms: (duration * 1000).round(2)
87
+ }
88
+ else
89
+ {
90
+ status: :healthy,
91
+ message: 'Payment gateway connection successful',
92
+ response_time_ms: (duration * 1000).round(2)
93
+ }
94
+ end
95
+ rescue StandardError => e
96
+ {
97
+ status: :unhealthy,
98
+ message: "Payment gateway unreachable: #{e.message}"
99
+ }
100
+ end
101
+ end
102
+
103
+ # Define order schema
104
+ ORDER_SCHEMA = {
105
+ customer_id: :uuid,
106
+ items: [{
107
+ product_id: :uuid,
108
+ quantity: { type: :integer, minimum: 1 },
109
+ price: { type: :float, minimum: 0.01 }
110
+ }],
111
+ shipping_address: {
112
+ street: :string,
113
+ city: :string,
114
+ state: :string,
115
+ zip_code: :string,
116
+ country: :string
117
+ },
118
+ payment_method: {
119
+ type: { type: :string, enum: %w[credit_card paypal bank_transfer] },
120
+ details: :object
121
+ }
122
+ }.freeze
123
+
124
+ # Create order endpoint with comprehensive observability
125
+ RapiTapir.endpoint
126
+ .post
127
+ .in('/orders')
128
+ .header(:authorization, Types.string(pattern: /\ABearer .+\z/), description: 'JWT token')
129
+ .header(:'x-tenant-id', :uuid, description: 'Tenant identifier')
130
+ .json_body(ORDER_SCHEMA)
131
+ .out_json({
132
+ id: :uuid,
133
+ status: { type: :string,
134
+ enum: %w[pending confirmed processing shipped delivered] },
135
+ customer_id: :uuid,
136
+ total_amount: :float,
137
+ items: [{
138
+ product_id: :uuid,
139
+ quantity: :integer,
140
+ unit_price: :float,
141
+ total_price: :float
142
+ }],
143
+ created_at: :datetime,
144
+ updated_at: :datetime
145
+ })
146
+ .with_metrics('order_creation')
147
+ .with_tracing('POST /orders')
148
+ .with_logging(
149
+ level: :info,
150
+ fields: %i[customer_id order_id total_amount item_count tenant_id]
151
+ )
152
+ .description('Create a new order')
153
+ .tag('orders')
154
+ .handle do |request|
155
+ # Extract context from headers
156
+ tenant_id = request.headers[:'x-tenant-id']
157
+ request.headers[:authorization]
158
+
159
+ # Add context to tracing
160
+ RapiTapir::Observability::Tracing.set_attribute('tenant.id', tenant_id)
161
+ RapiTapir::Observability::Tracing.set_attribute('auth.type', 'bearer')
162
+
163
+ order_data = request.body
164
+ customer_id = order_data[:customer_id]
165
+ total_amount = calculate_total_amount(order_data[:items])
166
+ item_count = order_data[:items].length
167
+
168
+ # Add business metrics to tracing
169
+ RapiTapir::Observability::Tracing.set_attribute('order.customer_id', customer_id)
170
+ RapiTapir::Observability::Tracing.set_attribute('order.total_amount', total_amount)
171
+ RapiTapir::Observability::Tracing.set_attribute('order.item_count', item_count)
172
+
173
+ # Structured logging
174
+ RapiTapir::Observability::Logging.info(
175
+ 'Processing order creation',
176
+ customer_id: customer_id,
177
+ total_amount: total_amount,
178
+ item_count: item_count,
179
+ tenant_id: tenant_id
180
+ )
181
+
182
+ begin
183
+ # Simulate order processing with nested spans
184
+ order_id = RapiTapir::Observability::Tracing.start_span('validate_order') do
185
+ # Validation logic
186
+ validate_order(order_data)
187
+ SecureRandom.uuid
188
+ end
189
+
190
+ RapiTapir::Observability::Tracing.start_span('process_payment') do |span|
191
+ span.set_attribute('payment.method', order_data[:payment_method][:type])
192
+ span.set_attribute('payment.amount', total_amount)
193
+
194
+ # Simulate payment processing
195
+ payment_result = process_payment(order_data[:payment_method], total_amount)
196
+ span.set_attribute('payment.transaction_id', payment_result[:transaction_id])
197
+
198
+ # Add payment event
199
+ RapiTapir::Observability::Tracing.add_event(
200
+ 'payment.processed',
201
+ attributes: {
202
+ 'payment.status' => payment_result[:status],
203
+ 'payment.transaction_id' => payment_result[:transaction_id]
204
+ }
205
+ )
206
+ end
207
+
208
+ # Build response
209
+ response = {
210
+ id: order_id,
211
+ status: 'confirmed',
212
+ customer_id: customer_id,
213
+ total_amount: total_amount,
214
+ items: order_data[:items].map.with_index do |item, _index|
215
+ {
216
+ product_id: item[:product_id],
217
+ quantity: item[:quantity],
218
+ unit_price: item[:price],
219
+ total_price: item[:quantity] * item[:price]
220
+ }
221
+ end,
222
+ created_at: Time.now.utc.iso8601,
223
+ updated_at: Time.now.utc.iso8601
224
+ }
225
+
226
+ # Success event
227
+ RapiTapir::Observability::Tracing.add_event(
228
+ 'order.created',
229
+ attributes: {
230
+ 'order.id' => order_id,
231
+ 'order.status' => 'confirmed'
232
+ }
233
+ )
234
+
235
+ # Success log
236
+ RapiTapir::Observability::Logging.info(
237
+ 'Order created successfully',
238
+ customer_id: customer_id,
239
+ order_id: order_id,
240
+ total_amount: total_amount,
241
+ item_count: item_count,
242
+ tenant_id: tenant_id
243
+ )
244
+
245
+ response
246
+ rescue ValidationError => e
247
+ RapiTapir::Observability::Tracing.record_exception(e)
248
+ RapiTapir::Observability::Logging.log_error(
249
+ e,
250
+ customer_id: customer_id,
251
+ operation: 'order_validation',
252
+ tenant_id: tenant_id
253
+ )
254
+ raise
255
+ rescue PaymentError => e
256
+ RapiTapir::Observability::Tracing.record_exception(e)
257
+ RapiTapir::Observability::Logging.log_error(
258
+ e,
259
+ customer_id: customer_id,
260
+ operation: 'payment_processing',
261
+ tenant_id: tenant_id,
262
+ total_amount: total_amount
263
+ )
264
+ raise
265
+ rescue StandardError => e
266
+ RapiTapir::Observability::Tracing.record_exception(e)
267
+ RapiTapir::Observability::Logging.log_error(
268
+ e,
269
+ customer_id: customer_id,
270
+ operation: 'order_creation',
271
+ tenant_id: tenant_id
272
+ )
273
+ raise
274
+ end
275
+ end
276
+
277
+ # Helper methods for the example
278
+ def calculate_total_amount(items)
279
+ items.sum { |item| item[:quantity] * item[:price] }
280
+ end
281
+
282
+ def validate_order(order_data)
283
+ # Simulation of order validation
284
+ raise ValidationError, 'Invalid customer ID' if order_data[:customer_id].nil?
285
+ raise ValidationError, 'No items in order' if order_data[:items].empty?
286
+
287
+ order_data[:items].each do |item|
288
+ raise ValidationError, 'Invalid item quantity' if item[:quantity] <= 0
289
+ raise ValidationError, 'Invalid item price' if item[:price] <= 0
290
+ end
291
+ end
292
+
293
+ def process_payment(payment_method, amount)
294
+ # Simulation of payment processing
295
+ case payment_method[:type]
296
+ when 'credit_card'
297
+ # Simulate potential payment failure
298
+ raise PaymentError, 'Credit card declined' if amount > 10_000
299
+
300
+ {
301
+ status: 'success',
302
+ transaction_id: SecureRandom.hex(16)
303
+ }
304
+ when 'paypal'
305
+ {
306
+ status: 'success',
307
+ transaction_id: "PP_#{SecureRandom.hex(12)}"
308
+ }
309
+ when 'bank_transfer'
310
+ {
311
+ status: 'pending',
312
+ transaction_id: "BT_#{SecureRandom.hex(12)}"
313
+ }
314
+ else
315
+ raise PaymentError, 'Unsupported payment method'
316
+ end
317
+ end
318
+
319
+ # Custom exception classes
320
+ class ValidationError < StandardError; end
321
+ class PaymentError < StandardError; end
322
+
323
+ # Rack application with observability
324
+ class OrderApp
325
+ def call(env)
326
+ # Route to our endpoints
327
+ request = Rack::Request.new(env)
328
+
329
+ case request.path_info
330
+ when '/orders'
331
+ if request.request_method == 'POST'
332
+ create_order_endpoint.call(env)
333
+ else
334
+ [405, { 'Content-Type' => 'text/plain' }, ['Method Not Allowed']]
335
+ end
336
+ else
337
+ [404, { 'Content-Type' => 'text/plain' }, ['Not Found']]
338
+ end
339
+ end
340
+ end
341
+
342
+ # Build Rack app with observability middleware
343
+ Rack::Builder.new do
344
+ # Add observability middleware
345
+ use RapiTapir::Observability::RackMiddleware
346
+
347
+ # Your application
348
+ run OrderApp.new
349
+ end
350
+
351
+ puts 'Advanced observability example configured!'
352
+ puts 'Available endpoints:'
353
+ puts '- POST /orders (create order with full observability)'
354
+ puts '- GET /health (comprehensive health checks)'
355
+ puts '- GET /metrics (detailed Prometheus metrics)'
356
+ puts ''
357
+ puts 'Example request:'
358
+ puts 'curl -X POST http://localhost:9292/orders \\'
359
+ puts " -H 'Content-Type: application/json' \\"
360
+ puts " -H 'Authorization: Bearer your-jwt-token' \\"
361
+ puts " -H 'X-Tenant-ID: 123e4567-e89b-12d3-a456-426614174000' \\"
362
+ puts " -d '{"
363
+ puts ' "customer_id": "123e4567-e89b-12d3-a456-426614174000",'
364
+ puts ' "items": ['
365
+ puts ' {'
366
+ puts ' "product_id": "123e4567-e89b-12d3-a456-426614174001",'
367
+ puts ' "quantity": 2,'
368
+ puts ' "price": 29.99'
369
+ puts ' }'
370
+ puts ' ],'
371
+ puts ' "shipping_address": {'
372
+ puts ' "street": "123 Main St",'
373
+ puts ' "city": "Anytown",'
374
+ puts ' "state": "CA",'
375
+ puts ' "zip_code": "12345",'
376
+ puts ' "country": "US"'
377
+ puts ' },'
378
+ puts ' "payment_method": {'
379
+ puts ' "type": "credit_card",'
380
+ puts ' "details": {}'
381
+ puts ' }'
382
+ puts " }'"
383
+ puts ''
384
+ puts 'To run: rackup -p 9292'
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rapitapir'
4
+
5
+ # Configure observability for the application
6
+ RapiTapir.configure do |config|
7
+ # Enable Prometheus metrics
8
+ config.metrics.enable_prometheus(
9
+ namespace: 'my_api',
10
+ labels: { service: 'user_service', version: '1.0.0' }
11
+ )
12
+
13
+ # Enable OpenTelemetry tracing
14
+ config.tracing.enable_opentelemetry(
15
+ service_name: 'my-api-service',
16
+ service_version: '1.0.0'
17
+ )
18
+
19
+ # Enable structured logging
20
+ config.logging.enable_structured(
21
+ level: :info,
22
+ fields: %i[timestamp level message request_id method path status duration user_id]
23
+ )
24
+
25
+ # Enable health checks
26
+ config.health_check.enable(endpoint: '/health')
27
+
28
+ # Add custom health checks
29
+ config.health_check.add_check(:database) do
30
+ # Simulate database health check
31
+ { status: :healthy, message: 'Database connection OK' }
32
+ end
33
+
34
+ config.health_check.add_check(:redis) do
35
+ # Simulate Redis health check
36
+ { status: :healthy, message: 'Redis connection OK' }
37
+ end
38
+ end
39
+
40
+ # Example endpoint with observability
41
+ RapiTapir.endpoint
42
+ .post
43
+ .in('/users')
44
+ .json_body({
45
+ name: :string,
46
+ email: :email,
47
+ age: { type: :integer, minimum: 18 }
48
+ })
49
+ .out_json({
50
+ id: :uuid,
51
+ name: :string,
52
+ email: :email,
53
+ created_at: :datetime
54
+ })
55
+ .with_metrics('user_creation')
56
+ .with_tracing
57
+ .with_logging(level: :info, fields: %i[user_email user_age])
58
+ .description('Create a new user')
59
+ .tag('users')
60
+ .handle do |request|
61
+ # Extract user data
62
+ user_data = request.body
63
+
64
+ # Add custom attributes to tracing span
65
+ RapiTapir::Observability::Tracing.set_attribute('user.email', user_data[:email])
66
+ RapiTapir::Observability::Tracing.set_attribute('user.age', user_data[:age])
67
+
68
+ # Log user creation attempt
69
+ RapiTapir::Observability::Logging.info(
70
+ 'Creating user',
71
+ user_email: user_data[:email],
72
+ user_age: user_data[:age]
73
+ )
74
+
75
+ begin
76
+ # Simulate user creation with metrics
77
+ user_id = SecureRandom.uuid
78
+ created_at = Time.now.utc
79
+
80
+ # Add event to tracing span
81
+ RapiTapir::Observability::Tracing.add_event(
82
+ 'user.created',
83
+ attributes: { 'user.id' => user_id }
84
+ )
85
+
86
+ # Return created user
87
+ {
88
+ id: user_id,
89
+ name: user_data[:name],
90
+ email: user_data[:email],
91
+ created_at: created_at.iso8601
92
+ }
93
+ rescue StandardError => e
94
+ # Record exception in tracing
95
+ RapiTapir::Observability::Tracing.record_exception(e)
96
+
97
+ # Log error with structured format
98
+ RapiTapir::Observability::Logging.log_error(
99
+ e,
100
+ user_email: user_data[:email],
101
+ operation: 'user_creation'
102
+ )
103
+
104
+ raise
105
+ end
106
+ end
107
+
108
+ # Example endpoint with custom metrics
109
+ RapiTapir.endpoint
110
+ .get
111
+ .in('/users')
112
+ .query(:page, Types.integer(minimum: 1, default: 1))
113
+ .query(:limit, Types.integer(minimum: 1, maximum: 100, default: 20))
114
+ .out_json({
115
+ users: [{
116
+ id: :uuid,
117
+ name: :string,
118
+ email: :email
119
+ }],
120
+ pagination: {
121
+ current_page: :integer,
122
+ total_pages: :integer,
123
+ total_count: :integer
124
+ }
125
+ })
126
+ .with_metrics('user_list')
127
+ .with_tracing('GET /users')
128
+ .handle do |request|
129
+ page = request.query[:page] || 1
130
+ limit = request.query[:limit] || 20
131
+
132
+ # Custom span attributes
133
+ RapiTapir::Observability::Tracing.set_attribute('pagination.page', page)
134
+ RapiTapir::Observability::Tracing.set_attribute('pagination.limit', limit)
135
+
136
+ # Simulate fetching users
137
+ users = (1..limit).map do |i|
138
+ {
139
+ id: SecureRandom.uuid,
140
+ name: "User #{((page - 1) * limit) + i}",
141
+ email: "user#{((page - 1) * limit) + i}@example.com"
142
+ }
143
+ end
144
+
145
+ total_count = 1000 # Simulated total
146
+ total_pages = (total_count / limit.to_f).ceil
147
+
148
+ {
149
+ users: users,
150
+ pagination: {
151
+ current_page: page,
152
+ total_pages: total_pages,
153
+ total_count: total_count
154
+ }
155
+ }
156
+ end
157
+
158
+ # Error handling endpoint example
159
+ RapiTapir.endpoint
160
+ .get
161
+ .in('/error-test')
162
+ .out_json({ error: :string })
163
+ .with_metrics('error_test')
164
+ .with_tracing
165
+ .handle do |request|
166
+ # Simulate different types of errors for testing observability
167
+ error_type = request.query[:type] || 'generic'
168
+
169
+ case error_type
170
+ when 'timeout'
171
+ raise Timeout::Error, 'Operation timed out'
172
+ when 'validation'
173
+ raise ArgumentError, 'Invalid input data'
174
+ when 'not_found'
175
+ raise StandardError, 'Resource not found'
176
+ else
177
+ raise 'Generic error for testing'
178
+ end
179
+ end
180
+
181
+ puts 'Observability example configured!'
182
+ puts 'Available endpoints:'
183
+ puts '- POST /users (create user with full observability)'
184
+ puts '- GET /users (list users with pagination tracking)'
185
+ puts '- GET /error-test (test error tracking)'
186
+ puts '- GET /health (health check endpoint)'
187
+ puts '- GET /metrics (Prometheus metrics endpoint)'
188
+ puts ''
189
+ puts 'To run with a Rack server:'
190
+ puts "require 'rack'"
191
+ puts 'use RapiTapir::Observability::RackMiddleware'
192
+ puts 'run YourApp'
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'uri'
7
+
8
+ puts "🍯 Complete Honeycomb.io Server Test"
9
+ puts "===================================="
10
+
11
+ # Change to project root
12
+ project_root = File.expand_path('../../', __dir__)
13
+ Dir.chdir(project_root)
14
+
15
+ puts "📂 Working directory: #{Dir.pwd}"
16
+
17
+ # Start server in background
18
+ puts "📡 Starting server..."
19
+ server_pid = spawn("bundle exec ruby examples/observability/honeycomb_working_example.rb",
20
+ out: "/tmp/honeycomb_server.log",
21
+ err: "/tmp/honeycomb_server.log")
22
+
23
+ # Give server time to start
24
+ puts "⏳ Waiting for server to start..."
25
+ server_ready = false
26
+ 20.times do |i|
27
+ sleep 1
28
+ begin
29
+ response = Net::HTTP.get_response(URI('http://localhost:4567/health'))
30
+ if response.code == '200'
31
+ server_ready = true
32
+ puts "✅ Server is ready! (attempt #{i + 1})"
33
+ break
34
+ end
35
+ rescue Errno::ECONNREFUSED, Net::OpenTimeout
36
+ print "."
37
+ end
38
+ end
39
+
40
+ unless server_ready
41
+ puts "\n❌ Server failed to start within 20 seconds"
42
+ Process.kill('TERM', server_pid) rescue nil
43
+ Process.wait(server_pid) rescue nil
44
+ exit 1
45
+ end
46
+
47
+ puts "\n🧪 Running tests..."
48
+
49
+ # Test 1: Health check
50
+ puts "\n1️⃣ Testing health endpoint..."
51
+ response = Net::HTTP.get_response(URI('http://localhost:4567/health'))
52
+ puts " Status: #{response.code}"
53
+ puts " Body: #{response.body}"
54
+
55
+ # Test 2: List users (empty)
56
+ puts "\n2️⃣ Testing user list endpoint..."
57
+ response = Net::HTTP.get_response(URI('http://localhost:4567/users'))
58
+ puts " Status: #{response.code}"
59
+ puts " Body: #{response.body}"
60
+
61
+ # Test 3: Create a user
62
+ puts "\n3️⃣ Testing user creation..."
63
+ uri = URI('http://localhost:4567/users')
64
+ http = Net::HTTP.new(uri.host, uri.port)
65
+ request = Net::HTTP::Post.new(uri)
66
+ request['Content-Type'] = 'application/json'
67
+ request.body = {
68
+ name: 'Alice Johnson',
69
+ email: 'alice@example.com',
70
+ age: 28,
71
+ department: 'engineering'
72
+ }.to_json
73
+
74
+ response = http.request(request)
75
+ puts " Status: #{response.code}"
76
+ puts " Body: #{response.body}"
77
+
78
+ if response.code == '201'
79
+ user_data = JSON.parse(response.body)
80
+ user_id = user_data['id']
81
+
82
+ # Test 4: Get the created user
83
+ puts "\n4️⃣ Testing get user by ID..."
84
+ response = Net::HTTP.get_response(URI("http://localhost:4567/users/#{user_id}"))
85
+ puts " Status: #{response.code}"
86
+ puts " Body: #{response.body}"
87
+
88
+ # Test 5: Update the user
89
+ puts "\n5️⃣ Testing user update..."
90
+ uri = URI("http://localhost:4567/users/#{user_id}")
91
+ http = Net::HTTP.new(uri.host, uri.port)
92
+ request = Net::HTTP::Put.new(uri)
93
+ request['Content-Type'] = 'application/json'
94
+ request.body = {
95
+ name: 'Alice Johnson-Smith',
96
+ department: 'product'
97
+ }.to_json
98
+
99
+ response = http.request(request)
100
+ puts " Status: #{response.code}"
101
+ puts " Body: #{response.body}"
102
+ end
103
+
104
+ # Test 6: Department analytics
105
+ puts "\n6️⃣ Testing department analytics..."
106
+ response = Net::HTTP.get_response(URI('http://localhost:4567/analytics/department-stats'))
107
+ puts " Status: #{response.code}"
108
+ puts " Body: #{response.body}"
109
+
110
+ puts "\n✅ All tests completed!"
111
+ puts "\n📊 Check your Honeycomb.io dashboard for traces!"
112
+ puts " Dataset: rapitapir-demo"
113
+ puts " Service: rapitapir-demo"
114
+
115
+ # Cleanup
116
+ puts "\n🧹 Cleaning up..."
117
+ Process.kill('TERM', server_pid) rescue nil
118
+ Process.wait(server_pid) rescue nil
119
+ puts "✅ Server stopped"
120
+
121
+ puts "\n🎉 Test complete! Your Honeycomb integration is working!"