rapitapir 0.1.1 → 2.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.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -7
  3. data/.rubocop_todo.yml +83 -0
  4. data/README.md +1319 -235
  5. data/RUBY_WEEKLY_LAUNCH_POST.md +219 -0
  6. data/docs/RAILS_INTEGRATION_IMPLEMENTATION.md +209 -0
  7. data/docs/SINATRA_EXTENSION.md +399 -348
  8. data/docs/STRICT_VALIDATION.md +229 -0
  9. data/docs/VALIDATION_IMPROVEMENTS.md +218 -0
  10. data/docs/ai-integration-plan.md +112 -0
  11. data/docs/auto-derivation.md +505 -92
  12. data/docs/endpoint-definition.md +536 -129
  13. data/docs/n8n-integration.md +212 -0
  14. data/docs/observability.md +810 -500
  15. data/docs/using-mcp.md +93 -0
  16. data/examples/ai/knowledge_base_rag.rb +83 -0
  17. data/examples/ai/user_management_mcp.rb +92 -0
  18. data/examples/ai/user_validation_llm.rb +187 -0
  19. data/examples/rails/RAILS_8_GUIDE.md +165 -0
  20. data/examples/rails/RAILS_LOADING_FIX.rb +35 -0
  21. data/examples/rails/README.md +497 -0
  22. data/examples/rails/comprehensive_test.rb +91 -0
  23. data/examples/rails/config/routes.rb +48 -0
  24. data/examples/rails/debug_controller.rb +63 -0
  25. data/examples/rails/detailed_test.rb +46 -0
  26. data/examples/rails/enhanced_users_controller.rb +278 -0
  27. data/examples/rails/final_server_test.rb +50 -0
  28. data/examples/rails/hello_world_app.rb +116 -0
  29. data/examples/rails/hello_world_controller.rb +186 -0
  30. data/examples/rails/hello_world_routes.rb +28 -0
  31. data/examples/rails/rails8_minimal_demo.rb +132 -0
  32. data/examples/rails/rails8_simple_demo.rb +140 -0
  33. data/examples/rails/rails8_working_demo.rb +255 -0
  34. data/examples/rails/real_world_blog_api.rb +510 -0
  35. data/examples/rails/server_test.rb +46 -0
  36. data/examples/rails/test_direct_processing.rb +41 -0
  37. data/examples/rails/test_hello_world.rb +80 -0
  38. data/examples/rails/test_rails_integration.rb +54 -0
  39. data/examples/rails/traditional_app/Gemfile +37 -0
  40. data/examples/rails/traditional_app/README.md +265 -0
  41. data/examples/rails/traditional_app/app/controllers/api/v1/posts_controller.rb +254 -0
  42. data/examples/rails/traditional_app/app/controllers/api/v1/users_controller.rb +220 -0
  43. data/examples/rails/traditional_app/app/controllers/application_controller.rb +86 -0
  44. data/examples/rails/traditional_app/app/controllers/application_controller_simplified.rb +87 -0
  45. data/examples/rails/traditional_app/app/controllers/documentation_controller.rb +149 -0
  46. data/examples/rails/traditional_app/app/controllers/health_controller.rb +42 -0
  47. data/examples/rails/traditional_app/config/routes.rb +25 -0
  48. data/examples/rails/traditional_app/config/routes_best_practice.rb +25 -0
  49. data/examples/rails/traditional_app/config/routes_simplified.rb +36 -0
  50. data/examples/rails/traditional_app_runnable.rb +406 -0
  51. data/examples/rails/users_controller.rb +4 -1
  52. data/examples/serverless/Gemfile +43 -0
  53. data/examples/serverless/QUICKSTART.md +331 -0
  54. data/examples/serverless/README.md +520 -0
  55. data/examples/serverless/aws_lambda_example.rb +307 -0
  56. data/examples/serverless/aws_sam_template.yaml +215 -0
  57. data/examples/serverless/azure_functions_example.rb +407 -0
  58. data/examples/serverless/deploy.rb +204 -0
  59. data/examples/serverless/gcp_cloud_functions_example.rb +367 -0
  60. data/examples/serverless/gcp_function.yaml +23 -0
  61. data/examples/serverless/host.json +24 -0
  62. data/examples/serverless/package.json +32 -0
  63. data/examples/serverless/spec/aws_lambda_spec.rb +196 -0
  64. data/examples/serverless/spec/spec_helper.rb +89 -0
  65. data/examples/serverless/vercel.json +31 -0
  66. data/examples/serverless/vercel_example.rb +404 -0
  67. data/examples/strict_validation_examples.rb +104 -0
  68. data/examples/validation_error_examples.rb +173 -0
  69. data/lib/rapitapir/ai/llm_instruction.rb +456 -0
  70. data/lib/rapitapir/ai/mcp.rb +134 -0
  71. data/lib/rapitapir/ai/rag.rb +287 -0
  72. data/lib/rapitapir/ai/rag_middleware.rb +147 -0
  73. data/lib/rapitapir/auth/oauth2.rb +43 -57
  74. data/lib/rapitapir/cli/command.rb +362 -2
  75. data/lib/rapitapir/cli/mcp_export.rb +18 -0
  76. data/lib/rapitapir/cli/validator.rb +2 -6
  77. data/lib/rapitapir/core/endpoint.rb +59 -6
  78. data/lib/rapitapir/core/enhanced_endpoint.rb +2 -6
  79. data/lib/rapitapir/dsl/fluent_endpoint_builder.rb +53 -0
  80. data/lib/rapitapir/endpoint_registry.rb +47 -0
  81. data/lib/rapitapir/observability/health_check.rb +4 -4
  82. data/lib/rapitapir/observability/logging.rb +10 -10
  83. data/lib/rapitapir/schema.rb +2 -2
  84. data/lib/rapitapir/server/rack_adapter.rb +1 -3
  85. data/lib/rapitapir/server/rails/configuration.rb +77 -0
  86. data/lib/rapitapir/server/rails/controller_base.rb +185 -0
  87. data/lib/rapitapir/server/rails/documentation_helpers.rb +76 -0
  88. data/lib/rapitapir/server/rails/resource_builder.rb +181 -0
  89. data/lib/rapitapir/server/rails/routes.rb +114 -0
  90. data/lib/rapitapir/server/rails_adapter.rb +10 -3
  91. data/lib/rapitapir/server/rails_adapter_class.rb +1 -3
  92. data/lib/rapitapir/server/rails_controller.rb +1 -3
  93. data/lib/rapitapir/server/rails_integration.rb +67 -0
  94. data/lib/rapitapir/server/rails_response_handler.rb +16 -3
  95. data/lib/rapitapir/server/sinatra_adapter.rb +29 -5
  96. data/lib/rapitapir/server/sinatra_integration.rb +4 -4
  97. data/lib/rapitapir/sinatra/extension.rb +2 -2
  98. data/lib/rapitapir/sinatra/oauth2_helpers.rb +34 -40
  99. data/lib/rapitapir/types/array.rb +4 -0
  100. data/lib/rapitapir/types/auto_derivation.rb +4 -18
  101. data/lib/rapitapir/types/datetime.rb +1 -3
  102. data/lib/rapitapir/types/float.rb +2 -6
  103. data/lib/rapitapir/types/hash.rb +40 -2
  104. data/lib/rapitapir/types/integer.rb +4 -12
  105. data/lib/rapitapir/types/object.rb +6 -2
  106. data/lib/rapitapir/types.rb +6 -2
  107. data/lib/rapitapir/version.rb +1 -1
  108. data/lib/rapitapir.rb +5 -3
  109. data/rapitapir.gemspec +7 -5
  110. metadata +116 -16
@@ -14,6 +14,11 @@ module RapiTapir
14
14
  # inputs: [path_param(:id, :integer)],
15
15
  # outputs: [json_output(user_schema)]
16
16
  # )
17
+ # method: :get,
18
+ # path: '/users/{id}',
19
+ # inputs: [path_param(:id, :integer)],
20
+ # outputs: [json_output(user_schema)]
21
+ # )
17
22
  class Endpoint
18
23
  HTTP_METHODS = %i[get post put patch delete options head].freeze
19
24
 
@@ -77,6 +82,58 @@ module RapiTapir
77
82
  with_metadata(deprecated: flag_value)
78
83
  end
79
84
 
85
+ # Mark this endpoint for MCP export
86
+ def mcp_export
87
+ with_metadata(mcp_export: true)
88
+ end
89
+
90
+ # Returns true if this endpoint is marked for MCP export
91
+ def mcp_export?
92
+ metadata[:mcp_export] == true
93
+ end
94
+
95
+ # Configure RAG inference for this endpoint
96
+ def rag_inference(llm:, retrieval:, context_fields: [], config: {})
97
+ rag_config = {
98
+ llm: llm,
99
+ retrieval: retrieval,
100
+ context_fields: context_fields,
101
+ config: config
102
+ }
103
+ with_metadata(rag_inference: rag_config)
104
+ end
105
+
106
+ # Returns true if this endpoint has RAG inference configured
107
+ def rag_inference?
108
+ metadata.key?(:rag_inference)
109
+ end
110
+
111
+ # Get RAG configuration for this endpoint
112
+ def rag_config
113
+ metadata[:rag_inference]
114
+ end
115
+
116
+ # Configure LLM instruction generation for this endpoint
117
+ def llm_instruction(purpose:, fields: :all, **options)
118
+ llm_config = {
119
+ purpose: purpose,
120
+ fields: fields,
121
+ options: options
122
+ }
123
+
124
+ with_metadata(llm_instruction: llm_config)
125
+ end
126
+
127
+ # Check if this endpoint has LLM instruction generation configured
128
+ def llm_instruction?
129
+ metadata.key?(:llm_instruction)
130
+ end
131
+
132
+ # Get LLM instruction configuration for this endpoint
133
+ def llm_instruction_config
134
+ metadata[:llm_instruction]
135
+ end
136
+
80
137
  # Validate input/output types for a given input/output hash
81
138
  # Raises validation errors if invalid, returns true if valid
82
139
  def validate!(input_hash = {}, output_hash = {}) # rubocop:disable Naming/PredicateMethod
@@ -141,14 +198,10 @@ module RapiTapir
141
198
  def validate_outputs!(output_hash)
142
199
  outputs.each do |output|
143
200
  if output.type.is_a?(Hash)
144
- unless output.valid_type?(output_hash)
145
- raise TypeError, "Invalid output hash: expected #{output.type}, got #{output_hash}"
146
- end
201
+ raise TypeError, "Invalid output hash: expected #{output.type}, got #{output_hash}" unless output.valid_type?(output_hash)
147
202
  else
148
203
  output_hash.each do |k, v|
149
- unless output.valid_type?(v)
150
- raise TypeError, "Invalid type for output '#{k}': expected #{output.type}, got #{v.class}"
151
- end
204
+ raise TypeError, "Invalid type for output '#{k}': expected #{output.type}, got #{v.class}" unless output.valid_type?(v)
152
205
  end
153
206
  end
154
207
  end
@@ -210,17 +210,13 @@ module RapiTapir
210
210
 
211
211
  def validate_security_schemes!
212
212
  security_schemes.each do |scheme|
213
- unless %i[bearer api_key basic].include?(scheme.options[:auth_type])
214
- raise ArgumentError, "Unknown authentication type: #{scheme.options[:auth_type]}"
215
- end
213
+ raise ArgumentError, "Unknown authentication type: #{scheme.options[:auth_type]}" unless %i[bearer api_key basic].include?(scheme.options[:auth_type])
216
214
  end
217
215
  end
218
216
 
219
217
  def validate_type_consistency!
220
218
  inputs.each do |input|
221
- unless input.type.respond_to?(:validate)
222
- raise ArgumentError, "Input '#{input.name}' type does not support validation"
223
- end
219
+ raise ArgumentError, "Input '#{input.name}' type does not support validation" unless input.type.respond_to?(:validate)
224
220
  end
225
221
 
226
222
  outputs.each do |output|
@@ -159,6 +159,9 @@ module RapiTapir
159
159
  copy_with(errors: @errors + [error])
160
160
  end
161
161
 
162
+ # Alias for compatibility with Core::Endpoint
163
+ alias error_out error_response
164
+
162
165
  def bad_request(type_def = nil, **options)
163
166
  error_response(400, type_def, **options)
164
167
  end
@@ -311,6 +314,56 @@ module RapiTapir
311
314
  **options
312
315
  )
313
316
  end
317
+
318
+ public
319
+
320
+ # AI/MCP Extensions
321
+ def mcp_export
322
+ copy_with(metadata: @metadata.merge(mcp_export: true))
323
+ end
324
+
325
+ def mcp_export?
326
+ @metadata[:mcp_export] == true
327
+ end
328
+
329
+ def rag_inference(llm:, retrieval:, context_fields: [], config: {})
330
+ copy_with(metadata: @metadata.merge(
331
+ rag_inference: true,
332
+ rag_config: {
333
+ llm: llm,
334
+ retrieval: retrieval,
335
+ context_fields: context_fields,
336
+ config: config
337
+ }
338
+ ))
339
+ end
340
+
341
+ def rag_inference?
342
+ @metadata[:rag_inference] == true
343
+ end
344
+
345
+ def rag_config
346
+ @metadata[:rag_config]
347
+ end
348
+
349
+ # LLM Instruction Generation
350
+ def llm_instruction(purpose:, fields: :all, **options)
351
+ copy_with(metadata: @metadata.merge(
352
+ llm_instruction: {
353
+ purpose: purpose,
354
+ fields: fields,
355
+ options: options
356
+ }
357
+ ))
358
+ end
359
+
360
+ def llm_instruction?
361
+ @metadata.key?(:llm_instruction)
362
+ end
363
+
364
+ def llm_instruction_config
365
+ @metadata[:llm_instruction]
366
+ end
314
367
  end
315
368
  end
316
369
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RapiTapir
4
+ # Global endpoint registry for collecting and managing endpoints
5
+ # Provides a central place to register and discover endpoints for CLI operations
6
+ class EndpointRegistry
7
+ @endpoints = []
8
+ @mutex = Mutex.new
9
+
10
+ class << self
11
+ # Register an endpoint in the global registry
12
+ def register(endpoint)
13
+ @mutex.synchronize do
14
+ @endpoints << endpoint unless @endpoints.include?(endpoint)
15
+ end
16
+ end
17
+
18
+ # Get all registered endpoints
19
+ def all
20
+ @mutex.synchronize { @endpoints.dup }
21
+ end
22
+
23
+ # Get only endpoints marked for MCP export
24
+ def mcp_endpoints
25
+ @mutex.synchronize { @endpoints.select(&:mcp_export?) }
26
+ end
27
+
28
+ # Clear all registered endpoints (useful for testing)
29
+ def clear!
30
+ @mutex.synchronize { @endpoints.clear }
31
+ end
32
+
33
+ # Register multiple endpoints at once
34
+ def register_all(endpoints)
35
+ endpoints.each { |endpoint| register(endpoint) }
36
+ end
37
+
38
+ # Find endpoints by method and path pattern
39
+ def find_by(method: nil, path: nil)
40
+ results = all
41
+ results = results.select { |ep| ep.method == method } if method
42
+ results = results.select { |ep| ep.path&.include?(path) } if path
43
+ results
44
+ end
45
+ end
46
+ end
47
+ end
@@ -73,8 +73,8 @@ module RapiTapir
73
73
  register_default_checks
74
74
  end
75
75
 
76
- def register(name, description = nil, &block)
77
- @checks << Check.new(name, description, &block)
76
+ def register(name, description = nil, &)
77
+ @checks << Check.new(name, description, &)
78
78
  end
79
79
 
80
80
  def run_all
@@ -210,9 +210,9 @@ module RapiTapir
210
210
  @endpoint_path = endpoint
211
211
  end
212
212
 
213
- def register(name, description = nil, &block)
213
+ def register(name, description = nil, &)
214
214
  @registry ||= Registry.new
215
- @registry.register(name, description, &block)
215
+ @registry.register(name, description, &)
216
216
  end
217
217
 
218
218
  def endpoint
@@ -22,24 +22,24 @@ module RapiTapir
22
22
  @logger.formatter = @formatter
23
23
  end
24
24
 
25
- def debug(message = nil, **fields, &block)
26
- log(:debug, message, **fields, &block)
25
+ def debug(message = nil, **fields, &)
26
+ log(:debug, message, **fields, &)
27
27
  end
28
28
 
29
- def info(message = nil, **fields, &block)
30
- log(:info, message, **fields, &block)
29
+ def info(message = nil, **fields, &)
30
+ log(:info, message, **fields, &)
31
31
  end
32
32
 
33
- def warn(message = nil, **fields, &block)
34
- log(:warn, message, **fields, &block)
33
+ def warn(message = nil, **fields, &)
34
+ log(:warn, message, **fields, &)
35
35
  end
36
36
 
37
- def error(message = nil, **fields, &block)
38
- log(:error, message, **fields, &block)
37
+ def error(message = nil, **fields, &)
38
+ log(:error, message, **fields, &)
39
39
  end
40
40
 
41
- def fatal(message = nil, **fields, &block)
42
- log(:fatal, message, **fields, &block)
41
+ def fatal(message = nil, **fields, &)
42
+ log(:fatal, message, **fields, &)
43
43
  end
44
44
 
45
45
  def log_request(**options)
@@ -24,9 +24,9 @@ module RapiTapir
24
24
  end
25
25
 
26
26
  # Define a schema using a block
27
- def self.define(&block)
27
+ def self.define(&)
28
28
  builder = SchemaBuilder.new
29
- builder.instance_eval(&block)
29
+ builder.instance_eval(&)
30
30
  builder.build
31
31
  end
32
32
 
@@ -19,9 +19,7 @@ module RapiTapir
19
19
 
20
20
  # Register an endpoint with the adapter
21
21
  def register_endpoint(endpoint, handler)
22
- unless endpoint.is_a?(RapiTapir::Core::Endpoint)
23
- raise ArgumentError, 'Endpoint must be a RapiTapir::Core::Endpoint'
24
- end
22
+ raise ArgumentError, 'Endpoint must be a RapiTapir::Core::Endpoint' unless endpoint.is_a?(RapiTapir::Core::Endpoint)
25
23
  raise ArgumentError, 'Handler must respond to call' unless handler.respond_to?(:call)
26
24
 
27
25
  endpoint.validate!
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RapiTapir
4
+ module Server
5
+ module Rails
6
+ # Simple configuration class for Rails integration
7
+ # Provides basic configuration options needed for Rails controllers
8
+ class Configuration
9
+ attr_accessor :docs_path, :openapi_path, :health_check_enabled, :health_check_path
10
+ attr_reader :api_info, :servers, :public_paths, :auth_schemes
11
+
12
+ def initialize
13
+ @api_info = {
14
+ title: 'API Documentation',
15
+ description: 'Generated with RapiTapir',
16
+ version: '1.0.0'
17
+ }
18
+ @servers = []
19
+ @public_paths = []
20
+ @auth_schemes = {}
21
+ @docs_path = '/docs'
22
+ @openapi_path = '/openapi.json'
23
+ @health_check_enabled = false
24
+ @health_check_path = '/health'
25
+ end
26
+
27
+ # API Information configuration
28
+ def info(title: nil, description: nil, version: nil, **options)
29
+ @api_info[:title] = title if title
30
+ @api_info[:description] = description if description
31
+ @api_info[:version] = version if version
32
+ @api_info.merge!(options)
33
+ end
34
+
35
+ # Server configuration
36
+ def server(url:, description: nil)
37
+ @servers << { url: url, description: description }.compact
38
+ end
39
+
40
+ # Add paths that don't require authentication
41
+ def add_public_paths(*paths)
42
+ @public_paths.concat(paths.flatten.map(&:to_s))
43
+ end
44
+
45
+ # Authentication scheme management
46
+ def add_auth_scheme(name, scheme)
47
+ @auth_schemes[name] = scheme
48
+ end
49
+
50
+ def get_auth_scheme(name)
51
+ @auth_schemes[name]
52
+ end
53
+
54
+ # Enable development defaults (docs endpoints, etc.)
55
+ def development_defaults!
56
+ # This would set up automatic documentation endpoints
57
+ # For now, just enable health checks
58
+ @health_check_enabled = true
59
+
60
+ # Enable docs
61
+ enable_docs
62
+ end
63
+
64
+ # Enable documentation endpoints
65
+ def enable_docs(path: '/docs', openapi_path: '/openapi.json')
66
+ @docs_path = path
67
+ @openapi_path = openapi_path
68
+ end
69
+
70
+ # Check if docs are enabled
71
+ def docs_enabled?
72
+ !@docs_path.nil?
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../rails_controller'
4
+ require_relative '../../dsl/http_verbs'
5
+ require_relative '../../types'
6
+ require_relative 'resource_builder'
7
+ require_relative 'configuration'
8
+
9
+ module RapiTapir
10
+ module Server
11
+ module Rails
12
+ # Enhanced Rails controller base class providing Sinatra-like experience
13
+ #
14
+ # This class bridges the gap between Sinatra's elegant DSL and Rails conventions,
15
+ # offering the same clean syntax that Sinatra developers enjoy.
16
+ #
17
+ # @example Basic usage
18
+ # class UsersController < RapiTapir::Server::Rails::ControllerBase
19
+ # rapitapir do
20
+ # info(title: 'Users API', version: '1.0.0')
21
+ # development_defaults!
22
+ # end
23
+ #
24
+ # USER_SCHEMA = T.hash({
25
+ # "id" => T.integer,
26
+ # "name" => T.string,
27
+ # "email" => T.email
28
+ # })
29
+ #
30
+ # endpoint(
31
+ # GET('/users')
32
+ # .summary('List users')
33
+ # .ok(T.array(USER_SCHEMA))
34
+ # .build
35
+ # ) { User.all.map(&:attributes) }
36
+ # end
37
+ class ControllerBase < ActionController::Base
38
+ include RapiTapir::Server::Rails::Controller
39
+ extend RapiTapir::DSL::HTTPVerbs
40
+
41
+ # Make T shortcut available in Rails controllers
42
+ T = RapiTapir::Types
43
+
44
+ class << self
45
+ # Configure RapiTapir for this controller
46
+ # Similar to Sinatra's rapitapir block
47
+ #
48
+ # @param block [Proc] Configuration block
49
+ # @example
50
+ # rapitapir do
51
+ # info(title: 'My API', version: '1.0.0')
52
+ # development_defaults!
53
+ # end
54
+ def rapitapir(&)
55
+ @rapitapir_config = Configuration.new
56
+ @rapitapir_config.instance_eval(&) if block_given?
57
+ setup_default_features
58
+ end
59
+
60
+ # Define an endpoint with automatic action generation
61
+ # This method creates both the endpoint definition and the corresponding Rails action
62
+ #
63
+ # @param endpoint_definition [RapiTapir::Core::Endpoint] The endpoint definition
64
+ # @param block [Proc] The endpoint handler
65
+ # @example
66
+ # endpoint(
67
+ # GET('/users/:id')
68
+ # .summary('Get user')
69
+ # .path_param(:id, T.integer)
70
+ # .ok(USER_SCHEMA)
71
+ # .build
72
+ # ) do |inputs|
73
+ # User.find(inputs[:id]).attributes
74
+ # end
75
+ def endpoint(endpoint_definition, &)
76
+ action_name = derive_action_name(endpoint_definition.path, endpoint_definition.method)
77
+
78
+ # Register endpoint with the Rails controller mixin
79
+ rapitapir_endpoint(action_name, endpoint_definition, &)
80
+
81
+ # Auto-generate Rails controller action that passes the correct action name
82
+ define_method(action_name) do
83
+ process_rapitapir_endpoint(action_name)
84
+ end
85
+ end
86
+
87
+ # Create RESTful resource endpoints with CRUD operations
88
+ # Mirrors Sinatra's api_resource functionality for Rails
89
+ #
90
+ # @param path [String] Base path for the resource
91
+ # @param schema [Object] Schema for the resource
92
+ # @param block [Proc] Resource configuration block
93
+ # @example
94
+ # api_resource '/users', schema: USER_SCHEMA do
95
+ # crud do
96
+ # index { User.all.map(&:attributes) }
97
+ # show { |inputs| User.find(inputs[:id]).attributes }
98
+ # create { |inputs| User.create!(inputs[:body]).attributes }
99
+ # end
100
+ # end
101
+ def api_resource(path, schema:, &block)
102
+ resource_builder = ResourceBuilder.new(self, path, schema)
103
+ resource_builder.instance_eval(&block)
104
+
105
+ # Generate all CRUD endpoints and actions automatically
106
+ resource_builder.endpoints.each do |endpoint_def, handler|
107
+ endpoint(endpoint_def, &handler)
108
+ end
109
+ end
110
+
111
+ # Enhanced HTTP verb methods that return FluentEndpointBuilder
112
+ # These override the mixin methods to ensure proper scoping
113
+ #
114
+ # @param path [String] The endpoint path
115
+ # @return [RapiTapir::DSL::FluentEndpointBuilder] Builder for method chaining
116
+ def GET(path)
117
+ RapiTapir.get(path)
118
+ end
119
+
120
+ def POST(path)
121
+ RapiTapir.post(path)
122
+ end
123
+
124
+ def PUT(path)
125
+ RapiTapir.put(path)
126
+ end
127
+
128
+ def PATCH(path)
129
+ RapiTapir.patch(path)
130
+ end
131
+
132
+ def DELETE(path)
133
+ RapiTapir.delete(path)
134
+ end
135
+
136
+ def HEAD(path)
137
+ RapiTapir.head(path)
138
+ end
139
+
140
+ def OPTIONS(path)
141
+ RapiTapir.options(path)
142
+ end
143
+
144
+ private
145
+
146
+ # Set up default features similar to Sinatra extension
147
+ def setup_default_features
148
+ # Future: Auto-setup CORS, docs, health checks like Sinatra
149
+ # For now, we'll focus on the core functionality
150
+ end
151
+
152
+ # Derive Rails action name from HTTP method and path
153
+ # Follows Rails conventions for RESTful routes
154
+ #
155
+ # @param path [String] The endpoint path
156
+ # @param method [Symbol] The HTTP method
157
+ # @return [Symbol] The Rails action name
158
+ def derive_action_name(path, method)
159
+ # For custom endpoints, always derive from path to avoid conflicts
160
+ path_segments = path.split('/').reject(&:empty?)
161
+
162
+ return method.to_s.downcase.to_sym if path_segments.empty?
163
+
164
+ # Get the main path segment (not parameters)
165
+ main_segment = nil
166
+ path_segments.each do |segment|
167
+ unless segment.start_with?(':') || segment.start_with?('{')
168
+ main_segment = segment
169
+ break
170
+ end
171
+ end
172
+
173
+ if main_segment
174
+ # Create action name from method + segment to ensure uniqueness
175
+ :"#{method.to_s.downcase}_#{main_segment}"
176
+ else
177
+ # Fallback to method name
178
+ method.to_s.downcase.to_sym
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../openapi/schema_generator'
4
+ require_relative '../../sinatra/swagger_ui_generator'
5
+
6
+ module RapiTapir
7
+ module Server
8
+ module Rails
9
+ # Documentation helpers for Rails controllers
10
+ module DocumentationHelpers
11
+ # Generate OpenAPI specification from Rails controller endpoints
12
+ def generate_openapi_spec_for_controller(controller_class, config = {})
13
+ endpoints = []
14
+
15
+ endpoints = controller_class.rapitapir_endpoints.values.map { |ep_config| ep_config[:endpoint] } if controller_class.respond_to?(:rapitapir_endpoints)
16
+
17
+ info = {
18
+ title: 'API Documentation',
19
+ version: '1.0.0',
20
+ description: 'Generated with RapiTapir for Rails'
21
+ }.merge(config[:info] || {})
22
+
23
+ servers = config[:servers] || [
24
+ {
25
+ url: 'http://localhost:9292',
26
+ description: 'Development server (Rails)'
27
+ }
28
+ ]
29
+
30
+ generator = RapiTapir::OpenAPI::SchemaGenerator.new(
31
+ endpoints: endpoints,
32
+ info: info,
33
+ servers: servers
34
+ )
35
+
36
+ generator.generate
37
+ end
38
+
39
+ # Generate Swagger UI HTML for Rails
40
+ def generate_swagger_ui_html(openapi_path, api_info)
41
+ RapiTapir::Sinatra::SwaggerUIGenerator.new(openapi_path, api_info).generate
42
+ end
43
+
44
+ # Create documentation routes for a Rails controller
45
+ def add_documentation_routes(controller_class, config = {})
46
+ docs_path = config[:docs_path] || '/docs'
47
+ openapi_path = config[:openapi_path] || '/openapi.json'
48
+
49
+ # OpenAPI JSON endpoint
50
+ get openapi_path, to: proc { |_env|
51
+ spec = RapiTapir::Server::Rails::DocumentationHelpers.generate_openapi_spec_for_controller(controller_class, config)
52
+ [
53
+ 200,
54
+ { 'Content-Type' => 'application/json' },
55
+ [JSON.pretty_generate(spec)]
56
+ ]
57
+ }
58
+
59
+ # Swagger UI endpoint
60
+ get docs_path, to: proc { |_env|
61
+ api_info = config[:info] || { title: 'API Documentation' }
62
+ html = RapiTapir::Server::Rails::DocumentationHelpers.generate_swagger_ui_html(openapi_path, api_info)
63
+ [
64
+ 200,
65
+ { 'Content-Type' => 'text/html' },
66
+ [html]
67
+ ]
68
+ }
69
+ end
70
+
71
+ # Module methods for static access
72
+ module_function :generate_openapi_spec_for_controller, :generate_swagger_ui_html
73
+ end
74
+ end
75
+ end
76
+ end