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
data/docs/using-mcp.md ADDED
@@ -0,0 +1,93 @@
1
+ # Using RapiTapir APIs as LLM Tools (MCP)
2
+
3
+ RapiTapir supports the Model Context Protocol (MCP) to make your API endpoints discoverable and consumable by LLMs and AI agents.
4
+
5
+ ### How to Mark Endpoints for MCP Export
6
+
7
+ Use the `.mcp_export` method in your endpoint DSL chain:
8
+
9
+ ```ruby
10
+ endpoint(
11
+ GET('/hello')
12
+ .query(:name, T.string, description: 'Name to greet')
13
+ .ok(T.hash({ "message" => T.string }))
14
+ .enable_mcp # Mark for MCP export
15
+ .build
16
+ ) do |inputs|
17
+ { message: "Hello, #{inputs[:name]}!" }
18
+ end
19
+ ```
20
+
21
+ ### CLI Usage
22
+
23
+ Generate MCP context using the command line:
24
+
25
+ ```bash
26
+ # Generate MCP context for all marked endpoints
27
+ rapitapir generate mcp --endpoints api.rb --output mcp-context.json
28
+
29
+ # Alternative export command
30
+ rapitapir export mcp --endpoints api.rb --output mcp-context.json
31
+ ```
32
+
33
+ ### Ruby API Usage
34
+
35
+ Use the MCP exporter directly in Ruby code:
36
+
37
+ ```ruby
38
+ require 'rapitapir/ai/mcp'
39
+
40
+ # Load your endpoints
41
+ endpoints = [your_endpoint_list]
42
+
43
+ # Create exporter and generate context
44
+ exporter = RapiTapir::AI::MCP::Exporter.new(endpoints)
45
+ mcp_context = exporter.as_mcp_context
46
+
47
+ # Save to file
48
+ File.write('mcp-context.json', JSON.pretty_generate(mcp_context))
49
+ ```
50
+
51
+ ### Example Output
52
+
53
+ The generated MCP context includes:
54
+
55
+ ```json
56
+ {
57
+ "name": "RapiTapir API Context",
58
+ "version": "1.0.0",
59
+ "endpoints": [
60
+ {
61
+ "name": "get__users__id_",
62
+ "method": "GET",
63
+ "path": "/users/{id}",
64
+ "summary": "Retrieve a user by ID",
65
+ "description": "Fetches a single user record by their unique identifier",
66
+ "input_schema": {
67
+ "id": {
68
+ "type": "integer",
69
+ "kind": "path",
70
+ "required": true
71
+ }
72
+ },
73
+ "output_schema": {
74
+ "json": {
75
+ "type": {
76
+ "id": "integer",
77
+ "name": "string",
78
+ "email": "string"
79
+ }
80
+ }
81
+ },
82
+ "examples": []
83
+ }
84
+ ]
85
+ }
86
+ ```
87
+
88
+ ### Use Cases
89
+ - LLM/agent tool discovery
90
+ - Automated API documentation for AI
91
+ - Integration with agent frameworks (LangChain, OpenAI, etc.)
92
+
93
+ See the AI integration plan for more details.
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ # Add the project root to the load path for development
6
+ project_root = File.expand_path('../../', __dir__)
7
+ $LOAD_PATH.unshift(File.join(project_root, 'lib')) unless $LOAD_PATH.include?(File.join(project_root, 'lib'))
8
+
9
+ require 'rapitapir'
10
+ require 'rapitapir/ai/rag'
11
+ require 'json'
12
+
13
+ # Example: Knowledge Base API with RAG Support
14
+ module KnowledgeBaseAPI
15
+ extend RapiTapir::DSL
16
+
17
+ # Type alias for convenience
18
+ T = RapiTapir::Types
19
+
20
+ # Define schemas
21
+ ASK_SCHEMA = T.hash({
22
+ 'question' => T.string,
23
+ 'user_id' => T.string
24
+ }).freeze
25
+
26
+ ANSWER_SCHEMA = T.hash({
27
+ 'answer' => T.string,
28
+ 'sources' => T.array(T.string),
29
+ 'timestamp' => T.string
30
+ }).freeze
31
+
32
+ CHAT_INPUT_SCHEMA = T.hash({
33
+ 'message' => T.string
34
+ }).freeze
35
+
36
+ CHAT_OUTPUT_SCHEMA = T.hash({
37
+ 'response' => T.string
38
+ }).freeze
39
+
40
+ # Ask a question using RAG
41
+ ASK_QUESTION = RapiTapir.post('/ask')
42
+ .json_body(ASK_SCHEMA)
43
+ .ok(ANSWER_SCHEMA)
44
+ .rag_inference(
45
+ llm: :openai,
46
+ retrieval: :memory,
47
+ context_fields: [:user_id],
48
+ config: {
49
+ llm: { api_key: 'mock' },
50
+ retrieval: {
51
+ documents: [
52
+ { content: 'RapiTapir is a Ruby API framework that provides type-safe endpoints.' },
53
+ { content: 'RAG combines document retrieval with LLM text generation.' },
54
+ { content: 'This API demonstrates AI-powered question answering.' }
55
+ ]
56
+ }
57
+ }
58
+ )
59
+ .summary('Ask a question about the knowledge base')
60
+ .mcp_export
61
+
62
+ # Simple chat
63
+ CHAT = RapiTapir.post('/chat')
64
+ .json_body(CHAT_INPUT_SCHEMA)
65
+ .ok(CHAT_OUTPUT_SCHEMA)
66
+ .rag_inference(
67
+ llm: :openai,
68
+ retrieval: :memory,
69
+ config: {
70
+ llm: { api_key: 'mock' },
71
+ retrieval: {
72
+ documents: [
73
+ { content: 'RapiTapir supports MCP for AI agent integration.' }
74
+ ]
75
+ }
76
+ }
77
+ )
78
+ .summary('Chat with AI')
79
+ .mcp_export
80
+ end
81
+
82
+ # Endpoints for CLI
83
+ knowledge_base_api = [KnowledgeBaseAPI::ASK_QUESTION, KnowledgeBaseAPI::CHAT]
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/rapitapir'
4
+ require_relative '../../lib/rapitapir/endpoint_registry'
5
+
6
+ # Example: User Management API with MCP Export
7
+ # This demonstrates how to mark endpoints for MCP export for AI/LLM consumption
8
+
9
+ class UserManagementAPI
10
+ include RapiTapir::DSL
11
+
12
+ # User schema for consistent typing
13
+ USER_SCHEMA = {
14
+ id: :integer,
15
+ name: :string,
16
+ email: :string,
17
+ created_at: :datetime
18
+ }.freeze
19
+
20
+ # Endpoint 1: Get user by ID - marked for MCP export
21
+ GET_USER = RapiTapir.get('/users/{id}')
22
+ .in(path_param(:id, :integer))
23
+ .out(json_body(USER_SCHEMA))
24
+ .summary('Retrieve a user by ID')
25
+ .description('Fetches a single user record by their unique identifier')
26
+ .mcp_export # Mark for MCP export
27
+
28
+ # Endpoint 2: Create new user - marked for MCP export
29
+ CREATE_USER = RapiTapir.post('/users')
30
+ .in(json_body({
31
+ name: :string,
32
+ email: :string
33
+ }))
34
+ .out(status_code(201))
35
+ .out(json_body(USER_SCHEMA))
36
+ .summary('Create a new user')
37
+ .description('Creates a new user account with name and email')
38
+ .mcp_export # Mark for MCP export
39
+
40
+ # Endpoint 3: Update user - NOT marked for MCP export
41
+ UPDATE_USER = RapiTapir.put('/users/{id}')
42
+ .in(path_param(:id, :integer))
43
+ .in(json_body({
44
+ name: :string,
45
+ email: :string
46
+ }))
47
+ .out(json_body(USER_SCHEMA))
48
+ .summary('Update an existing user')
49
+ .description('Updates user information')
50
+ # No .mcp_export - this endpoint won't be included in MCP context
51
+
52
+ # Endpoint 4: List users with pagination - marked for MCP export
53
+ LIST_USERS = RapiTapir.get('/users')
54
+ .in(query(:page, :integer, required: false))
55
+ .in(query(:limit, :integer, required: false))
56
+ .out(json_body({
57
+ users: [:array, USER_SCHEMA],
58
+ total: :integer,
59
+ page: :integer,
60
+ limit: :integer
61
+ }))
62
+ .summary('List all users')
63
+ .description('Retrieves a paginated list of all users')
64
+ .mcp_export # Mark for MCP export
65
+
66
+ # Store endpoints for export
67
+ ALL_ENDPOINTS = [GET_USER, CREATE_USER, UPDATE_USER, LIST_USERS].freeze
68
+
69
+ # Register endpoints in global registry
70
+ RapiTapir::EndpointRegistry.register_all(ALL_ENDPOINTS)
71
+ end
72
+
73
+ # Usage demonstration
74
+ if __FILE__ == $PROGRAM_NAME
75
+ puts "User Management API Example"
76
+ puts "==========================="
77
+
78
+ # Show which endpoints are marked for MCP export
79
+ mcp_endpoints = UserManagementAPI::ALL_ENDPOINTS.select(&:mcp_export?)
80
+ puts "\nEndpoints marked for MCP export:"
81
+ mcp_endpoints.each do |ep|
82
+ puts "- #{ep.method.upcase} #{ep.path} (#{ep.metadata[:summary]})"
83
+ end
84
+
85
+ # Generate MCP context
86
+ require_relative '../../lib/rapitapir/ai/mcp'
87
+ exporter = RapiTapir::AI::MCP::Exporter.new(UserManagementAPI::ALL_ENDPOINTS)
88
+ mcp_context = exporter.as_mcp_context
89
+
90
+ puts "\nGenerated MCP Context:"
91
+ puts JSON.pretty_generate(mcp_context)
92
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ # Add the project root to the load path for development
6
+ project_root = File.expand_path('../../', __dir__)
7
+ $LOAD_PATH.unshift(File.join(project_root, 'lib')) unless $LOAD_PATH.include?(File.join(project_root, 'lib'))
8
+
9
+ require 'rapitapir'
10
+ require 'rapitapir/ai/llm_instruction'
11
+ require 'json'
12
+
13
+ # Type alias for convenience (avoid warning if already defined)
14
+ T = RapiTapir::Types unless defined?(T)
15
+
16
+ module UserValidationAPI
17
+ # User creation validation schema
18
+ USER_INPUT_SCHEMA = T.hash({
19
+ 'name' => T.string(min_length: 2, max_length: 50),
20
+ 'email' => T.email,
21
+ 'age' => T.integer(minimum: 18, maximum: 120),
22
+ 'preferences' => T.optional(T.hash({
23
+ 'newsletter' => T.boolean,
24
+ 'notifications' => T.boolean
25
+ }))
26
+ }).freeze
27
+
28
+ USER_OUTPUT_SCHEMA = T.hash({
29
+ 'id' => T.integer,
30
+ 'name' => T.string,
31
+ 'email' => T.string,
32
+ 'status' => T.string,
33
+ 'created_at' => T.datetime
34
+ }).freeze
35
+
36
+ VALIDATION_ERROR_SCHEMA = T.hash({
37
+ 'error' => T.string,
38
+ 'field_errors' => T.array(T.hash({
39
+ 'field' => T.string,
40
+ 'message' => T.string,
41
+ 'code' => T.string
42
+ }))
43
+ }).freeze
44
+
45
+ # Data transformation schemas
46
+ LEGACY_USER_INPUT = T.hash({
47
+ 'full_name' => T.string,
48
+ 'email_address' => T.string,
49
+ 'birth_year' => T.integer,
50
+ 'settings' => T.optional(T.string) # JSON string in legacy format
51
+ }).freeze
52
+
53
+ # Analytics schema
54
+ USER_ANALYTICS_SCHEMA = T.hash({
55
+ 'user_count' => T.integer,
56
+ 'active_users' => T.integer,
57
+ 'signup_rate' => T.float,
58
+ 'demographics' => T.hash({
59
+ 'avg_age' => T.float,
60
+ 'age_distribution' => T.array(T.hash({
61
+ 'range' => T.string,
62
+ 'count' => T.integer
63
+ }))
64
+ })
65
+ }).freeze
66
+
67
+ # Validation endpoint with LLM instruction for input validation
68
+ CREATE_USER = RapiTapir.post('/users')
69
+ .json_body(USER_INPUT_SCHEMA)
70
+ .ok(USER_OUTPUT_SCHEMA)
71
+ .error_out(400, VALIDATION_ERROR_SCHEMA)
72
+ .llm_instruction(
73
+ purpose: :validation,
74
+ fields: [:body] # Focus on request body validation
75
+ )
76
+ .summary('Create a new user with validation')
77
+ .description('Creates a new user account with comprehensive input validation and business rule checking')
78
+ .mcp_export
79
+
80
+ # Transformation endpoint for legacy data migration
81
+ MIGRATE_LEGACY_USER = RapiTapir.post('/users/migrate')
82
+ .json_body(LEGACY_USER_INPUT)
83
+ .ok(USER_OUTPUT_SCHEMA)
84
+ .llm_instruction(
85
+ purpose: :transformation,
86
+ fields: :all
87
+ )
88
+ .summary('Migrate legacy user data to new format')
89
+ .description('Transforms legacy user data format to current schema with proper field mapping')
90
+ .mcp_export
91
+
92
+ # Analysis endpoint for user analytics
93
+ USER_ANALYTICS = RapiTapir.get('/users/analytics')
94
+ .query(:start_date, :date)
95
+ .query(:end_date, :date)
96
+ .query(:segment, T.optional(T.string))
97
+ .ok(USER_ANALYTICS_SCHEMA)
98
+ .llm_instruction(
99
+ purpose: :analysis,
100
+ fields: [:json] # Focus on analyzing the response data
101
+ )
102
+ .summary('Get user analytics and insights')
103
+ .description('Provides comprehensive user analytics including demographics and engagement metrics')
104
+ .mcp_export
105
+
106
+ # Documentation endpoint for API reference
107
+ USER_SEARCH = RapiTapir.get('/users/search')
108
+ .query(:q, T.string(min_length: 1))
109
+ .query(:limit, T.optional(T.integer(minimum: 1, maximum: 100)))
110
+ .query(:offset, T.optional(T.integer(minimum: 0)))
111
+ .ok(T.hash({
112
+ 'users' => T.array(USER_OUTPUT_SCHEMA),
113
+ 'total_count' => T.integer,
114
+ 'has_more' => T.boolean
115
+ }))
116
+ .llm_instruction(
117
+ purpose: :documentation,
118
+ fields: :all
119
+ )
120
+ .summary('Search for users')
121
+ .description('Full-text search across user profiles with pagination support')
122
+ .mcp_export
123
+
124
+ # Testing endpoint for comprehensive test generation
125
+ UPDATE_USER = RapiTapir.put('/users/:id')
126
+ .path_param(:id, :integer)
127
+ .json_body(T.hash({
128
+ 'name' => T.optional(T.string(min_length: 2, max_length: 50)),
129
+ 'email' => T.optional(T.email),
130
+ 'preferences' => T.optional(T.hash({
131
+ 'newsletter' => T.boolean,
132
+ 'notifications' => T.boolean
133
+ }))
134
+ }))
135
+ .ok(USER_OUTPUT_SCHEMA)
136
+ .error_out(404, T.hash({'error' => T.string}))
137
+ .error_out(400, VALIDATION_ERROR_SCHEMA)
138
+ .llm_instruction(
139
+ purpose: :testing,
140
+ fields: :all
141
+ )
142
+ .summary('Update user information')
143
+ .description('Updates user profile with partial data and validation')
144
+ .mcp_export
145
+
146
+ # Completion endpoint for smart field suggestions
147
+ USER_PROFILE_COMPLETION = RapiTapir.post('/users/complete')
148
+ .json_body(T.hash({
149
+ 'partial_profile' => T.hash({
150
+ 'name' => T.optional(T.string),
151
+ 'email' => T.optional(T.string),
152
+ 'location' => T.optional(T.string),
153
+ 'interests' => T.optional(T.array(T.string))
154
+ })
155
+ }))
156
+ .ok(T.hash({
157
+ 'suggestions' => T.hash({
158
+ 'name' => T.optional(T.array(T.string)),
159
+ 'email' => T.optional(T.string),
160
+ 'location' => T.optional(T.array(T.string)),
161
+ 'interests' => T.optional(T.array(T.string))
162
+ }),
163
+ 'confidence_scores' => T.hash({
164
+ 'name' => T.optional(T.float),
165
+ 'email' => T.optional(T.float),
166
+ 'location' => T.optional(T.float),
167
+ 'interests' => T.optional(T.float)
168
+ })
169
+ }))
170
+ .llm_instruction(
171
+ purpose: :completion,
172
+ fields: [:body]
173
+ )
174
+ .summary('Get smart profile completion suggestions')
175
+ .description('Provides intelligent suggestions for completing user profile data')
176
+ .mcp_export
177
+ end
178
+
179
+ # Endpoints for CLI
180
+ user_validation_api = [
181
+ UserValidationAPI::CREATE_USER,
182
+ UserValidationAPI::MIGRATE_LEGACY_USER,
183
+ UserValidationAPI::USER_ANALYTICS,
184
+ UserValidationAPI::USER_SEARCH,
185
+ UserValidationAPI::UPDATE_USER,
186
+ UserValidationAPI::USER_PROFILE_COMPLETION
187
+ ]
@@ -0,0 +1,165 @@
1
+ # Rails 8 + RapiTapir Quick Start Guide
2
+
3
+ ## 🚀 How to Fix the `uninitialized constant RapiTapir::Server::Rails::ControllerBase` Error
4
+
5
+ The error you're encountering is due to the **loading order**. RapiTapir's Rails integration requires Rails to be loaded first.
6
+
7
+ ### ✅ **Solution: Correct Loading Order**
8
+
9
+ In your Rails application, ensure this loading order:
10
+
11
+ #### 1. In your `Gemfile`:
12
+ ```ruby
13
+ source 'https://rubygems.org'
14
+ ruby '3.2.0'
15
+
16
+ gem 'rails', '~> 8.0.0' # Rails 8
17
+ gem 'rapitapir', '~> 1.0' # RapiTapir after Rails
18
+
19
+ # Other gems...
20
+ gem 'sqlite3'
21
+ gem 'puma'
22
+ ```
23
+
24
+ #### 2. In your application (or initializer):
25
+ ```ruby
26
+ # This is already handled by Rails bundler, but if you need manual loading:
27
+
28
+ # 1. Rails loads first (via bundle/require in application.rb)
29
+ require 'rails/all'
30
+
31
+ # 2. RapiTapir loads after Rails
32
+ require 'rapitapir' # Usually handled by bundler
33
+
34
+ # 3. Now you can use RapiTapir classes
35
+ class ApplicationController < RapiTapir::Server::Rails::ControllerBase
36
+ # This works!
37
+ end
38
+ ```
39
+
40
+ ### 🧪 **Test with Our Runnable Example**
41
+
42
+ The easiest way to verify everything works:
43
+
44
+ ```bash
45
+ cd examples/rails
46
+ ruby traditional_app_runnable.rb
47
+ ```
48
+
49
+ This should start successfully and show:
50
+ ```
51
+ 🚀 Starting Traditional Rails App with RapiTapir on http://localhost:3000
52
+ 📚 API Documentation: http://localhost:3000/docs
53
+ 📋 OpenAPI Spec: http://localhost:3000/openapi.json
54
+ 🏥 Health Check: http://localhost:3000/health
55
+ ```
56
+
57
+ ### 🔧 **For New Rails 8 Applications**
58
+
59
+ #### 1. Create Rails App
60
+ ```bash
61
+ rails new my_api --api
62
+ cd my_api
63
+ ```
64
+
65
+ #### 2. Add RapiTapir to Gemfile
66
+ ```ruby
67
+ # Gemfile
68
+ gem 'rapitapir', '~> 1.0'
69
+ ```
70
+
71
+ #### 3. Install
72
+ ```bash
73
+ bundle install
74
+ ```
75
+
76
+ #### 4. Update ApplicationController
77
+ ```ruby
78
+ # app/controllers/application_controller.rb
79
+ class ApplicationController < RapiTapir::Server::Rails::ControllerBase
80
+ rapitapir do
81
+ development_defaults! if Rails.env.development?
82
+
83
+ # Global error responses
84
+ error_out(json_body(error: T.string), 404)
85
+ error_out(json_body(error: T.string, errors: T.array(T.string).optional), 422)
86
+
87
+ # Health endpoint
88
+ GET('/health')
89
+ .out(json_body(status: T.string, timestamp: T.string))
90
+ .summary("Health check")
91
+ end
92
+
93
+ def health_check
94
+ { status: 'ok', timestamp: Time.current.iso8601 }
95
+ end
96
+ end
97
+ ```
98
+
99
+ #### 5. Create API Controllers
100
+ ```ruby
101
+ # app/controllers/api/v1/users_controller.rb
102
+ class Api::V1::UsersController < ApplicationController
103
+ rapitapir do
104
+ user_type = T.hash(id: T.integer, name: T.string, email: T.string)
105
+
106
+ GET('/api/v1/users')
107
+ .out(json_body(users: T.array(user_type)))
108
+ .summary("List users")
109
+ .tag("Users")
110
+ end
111
+
112
+ def list_users
113
+ { users: [{ id: 1, name: "Test User", email: "test@example.com" }] }
114
+ end
115
+ end
116
+ ```
117
+
118
+ #### 6. Update Routes
119
+ ```ruby
120
+ # config/routes.rb
121
+ Rails.application.routes.draw do
122
+ rapitapir_routes_for ApplicationController
123
+
124
+ namespace :api do
125
+ namespace :v1 do
126
+ rapitapir_routes_for 'Api::V1::UsersController'
127
+ end
128
+ end
129
+ end
130
+ ```
131
+
132
+ #### 7. Start Server
133
+ ```bash
134
+ rails server
135
+ ```
136
+
137
+ Visit:
138
+ - 🏥 Health: http://localhost:3000/health
139
+ - 📚 Docs: http://localhost:3000/docs
140
+ - 👥 Users: http://localhost:3000/api/v1/users
141
+
142
+ ### ⚠️ **Common Issues & Solutions**
143
+
144
+ #### Issue: `uninitialized constant RapiTapir::Server::Rails::ControllerBase`
145
+ **Solution**: Make sure Rails is loaded before RapiTapir. In most Rails apps, this happens automatically via bundler.
146
+
147
+ #### Issue: `NoMethodError: undefined method 'rapitapir_routes_for'`
148
+ **Solution**: Ensure RapiTapir's Rails integration is loaded. Add to `config/application.rb`:
149
+ ```ruby
150
+ require 'rapitapir'
151
+ ```
152
+
153
+ #### Issue: Documentation not showing up
154
+ **Solution**: Make sure you have `development_defaults!` in your rapitapir block.
155
+
156
+ ### 🎉 **Benefits of Rails 8 + RapiTapir**
157
+
158
+ - ✅ **Modern Rails**: Latest Rails 8 features and performance
159
+ - ✅ **Type Safety**: Automatic input validation and output schemas
160
+ - ✅ **Auto Documentation**: Swagger UI and OpenAPI 3.0 specs
161
+ - ✅ **Clean Syntax**: Sinatra-like DSL in Rails controllers
162
+ - ✅ **Zero Config**: Works out of the box with `development_defaults!`
163
+ - ✅ **Production Ready**: Error handling, health checks, monitoring
164
+
165
+ The combination of Rails 8 and RapiTapir gives you the best of both worlds: Rails' maturity and ecosystem with RapiTapir's elegant API design! 🚀
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Instructions for using RapiTapir with Rails
4
+ #
5
+ # To fix the "uninitialized constant RapiTapir::Server::Rails::ControllerBase" error:
6
+ #
7
+ # 1. In your Rails application, make sure to require 'rapitapir' AFTER Rails is loaded
8
+ # 2. In your Gemfile, add:
9
+ # gem 'rapitapir'
10
+ # 3. In your ApplicationController or in an initializer, add:
11
+ # require 'rapitapir' (only if needed - bundler usually handles this)
12
+ #
13
+ # The error occurs because RapiTapir's Rails integration requires Rails and ActiveSupport
14
+ # to be loaded first.
15
+
16
+ # For the traditional_app_runnable.rb example, the require order should be:
17
+
18
+ require 'bundler/inline'
19
+
20
+ gemfile do
21
+ source 'https://rubygems.org'
22
+ gem 'rails', '~> 8.0'
23
+ gem 'sqlite3'
24
+ gem 'puma'
25
+ end
26
+
27
+ # Load Rails first
28
+ require 'rails/all'
29
+
30
+ # THEN load RapiTapir (this is the key!)
31
+ require_relative '../../../lib/rapitapir'
32
+
33
+ # Now you can use RapiTapir::Server::Rails::ControllerBase
34
+
35
+ puts "✅ This order works correctly!"