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,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/rapitapir'
4
+ require_relative '../../lib/rapitapir/openapi/schema_generator'
5
+
6
+ # Example script to demonstrate OpenAPI schema generation
7
+ class OpenAPIExample
8
+ def initialize
9
+ @endpoints = setup_user_endpoints
10
+ end
11
+
12
+ def generate_schema
13
+ generator = RapiTapir::OpenAPI::SchemaGenerator.new(
14
+ endpoints: @endpoints,
15
+ info: {
16
+ title: 'User Management API',
17
+ version: '1.0.0',
18
+ description: 'A simple user management API built with RapiTapir'
19
+ },
20
+ servers: [
21
+ {
22
+ url: 'http://localhost:4567',
23
+ description: 'Development server (Sinatra)'
24
+ },
25
+ {
26
+ url: 'http://localhost:9292',
27
+ description: 'Development server (Rack)'
28
+ }
29
+ ]
30
+ )
31
+
32
+ generator.generate
33
+ end
34
+
35
+ def save_schema(format: :json, pretty: true)
36
+ schema = generate_schema
37
+
38
+ case format
39
+ when :json
40
+ content = pretty ? JSON.pretty_generate(schema) : JSON.generate(schema)
41
+ filename = 'user_api_schema.json'
42
+ when :yaml
43
+ require 'yaml'
44
+ content = YAML.dump(schema)
45
+ filename = 'user_api_schema.yaml'
46
+ else
47
+ raise ArgumentError, "Unsupported format: #{format}"
48
+ end
49
+
50
+ File.write(filename, content)
51
+ puts "OpenAPI schema saved to #{filename}"
52
+ puts 'Schema preview:'
53
+ puts content[0..500] + (content.length > 500 ? '...' : '')
54
+ end
55
+
56
+ private
57
+
58
+ def setup_user_endpoints
59
+ endpoints = []
60
+
61
+ # List users endpoint
62
+ list_endpoint = RapiTapir.get('/users')
63
+ .summary('List all users')
64
+ .description('Returns a list of all users in the system')
65
+ .out(RapiTapir::Core::Output.new(kind: :json,
66
+ type: { users: [{
67
+ id: :integer, name: :string, email: :string
68
+ }] }))
69
+ endpoints << list_endpoint
70
+
71
+ # Get user by ID endpoint
72
+ get_endpoint = RapiTapir.get('/users/:id')
73
+ .summary('Get user by ID')
74
+ .description('Returns a single user by their ID')
75
+ .in(RapiTapir::Core::Input.new(kind: :path, name: :id, type: :integer))
76
+ .out(RapiTapir::Core::Output.new(kind: :json,
77
+ type: {
78
+ id: :integer, name: :string, email: :string
79
+ }))
80
+ endpoints << get_endpoint
81
+
82
+ # Create user endpoint
83
+ create_endpoint = RapiTapir.post('/users')
84
+ .summary('Create a new user')
85
+ .description('Creates a new user with the provided data')
86
+ .in(RapiTapir::Core::Input.new(kind: :body, name: :user_data,
87
+ type: { name: :string, email: :string }))
88
+ .out(RapiTapir::Core::Output.new(kind: :json,
89
+ type: {
90
+ id: :integer, name: :string, email: :string
91
+ }))
92
+ endpoints << create_endpoint
93
+
94
+ # Update user endpoint
95
+ update_endpoint = RapiTapir.put('/users/:id')
96
+ .summary('Update an existing user')
97
+ .description('Updates an existing user with the provided data')
98
+ .in(RapiTapir::Core::Input.new(kind: :path, name: :id, type: :integer))
99
+ .in(RapiTapir::Core::Input.new(kind: :body, name: :user_data,
100
+ type: { name: :string, email: :string }))
101
+ .out(RapiTapir::Core::Output.new(kind: :json,
102
+ type: {
103
+ id: :integer, name: :string, email: :string
104
+ }))
105
+ endpoints << update_endpoint
106
+
107
+ # Delete user endpoint
108
+ delete_endpoint = RapiTapir.delete('/users/:id')
109
+ .summary('Delete a user')
110
+ .description('Deletes a user by their ID')
111
+ .in(RapiTapir::Core::Input.new(kind: :path, name: :id, type: :integer))
112
+ .out(RapiTapir::Core::Output.new(kind: :json, type: { message: :string }))
113
+ endpoints << delete_endpoint
114
+
115
+ endpoints
116
+ end
117
+ end
118
+
119
+ # Run the example if this file is executed directly
120
+ if __FILE__ == $PROGRAM_NAME
121
+ example = OpenAPIExample.new
122
+
123
+ puts 'Generating OpenAPI schema for User Management API...'
124
+
125
+ # Generate and save JSON schema
126
+ example.save_schema(format: :json, pretty: true)
127
+
128
+ puts "\n#{'=' * 50}"
129
+
130
+ # Generate and save YAML schema
131
+ example.save_schema(format: :yaml)
132
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/rapitapir'
4
+
5
+ puts '🌟 RapiTapir Production-Ready API Example'
6
+ puts '=' * 45
7
+
8
+ # Define schemas
9
+ User = RapiTapir::Schema.define do
10
+ field :id, :uuid
11
+ field :name, :string
12
+ field :email, :email
13
+ field :created_at, :datetime
14
+ end
15
+
16
+ CreateUser = RapiTapir::Schema.define do
17
+ field :name, :string
18
+ field :email, :email
19
+ end
20
+
21
+ Error = RapiTapir::Schema.define do
22
+ field :error, :string
23
+ field :message, :string
24
+ field :code, :integer
25
+ end
26
+
27
+ # Define a complete REST API using the fluent DSL
28
+ RapiTapir.namespace('/api/v1/users') do
29
+ bearer_auth('API Token Required')
30
+ tags('users', 'rest-api')
31
+
32
+ error_responses do
33
+ unauthorized(401, Error, description: 'Invalid or missing token')
34
+ forbidden(403, Error, description: 'Insufficient permissions')
35
+ server_error(500, Error, description: 'Internal server error')
36
+ end
37
+
38
+ # GET /api/v1/users - List users with pagination
39
+ get('/')
40
+ .summary('List users')
41
+ .description('Retrieve paginated list of users')
42
+ .query(:page, :integer, min: 1, default: 1, description: 'Page number')
43
+ .query(:per_page, :integer, min: 1, max: 100, default: 20, description: 'Items per page')
44
+ .query(:search, :string, required: false, description: 'Search users by name')
45
+ .ok(RapiTapir::Types.array(User), description: 'List of users')
46
+ .bad_request(Error, description: 'Invalid query parameters')
47
+
48
+ # GET /api/v1/users/{id} - Get specific user
49
+ get('/{id}')
50
+ .summary('Get user by ID')
51
+ .description('Retrieve a specific user by their unique identifier')
52
+ .path_param(:id, :uuid, description: 'User unique identifier')
53
+ .ok(User, description: 'User details')
54
+ .not_found(Error, description: 'User not found')
55
+
56
+ # POST /api/v1/users - Create new user
57
+ post('/')
58
+ .summary('Create user')
59
+ .description('Create a new user account')
60
+ .json_body(CreateUser, description: 'User data')
61
+ .requires_scope('users:write')
62
+ .created(User, description: 'User created successfully')
63
+ .bad_request(Error, description: 'Invalid user data')
64
+ .unprocessable_entity(Error, description: 'Validation failed')
65
+
66
+ # PUT /api/v1/users/{id} - Update user
67
+ put('/{id}')
68
+ .summary('Update user')
69
+ .description("Update an existing user's information")
70
+ .path_param(:id, :uuid, description: 'User unique identifier')
71
+ .json_body(CreateUser, description: 'Updated user data')
72
+ .requires_scope('users:write')
73
+ .ok(User, description: 'User updated successfully')
74
+ .not_found(Error, description: 'User not found')
75
+ .unprocessable_entity(Error, description: 'Validation failed')
76
+
77
+ # DELETE /api/v1/users/{id} - Delete user
78
+ delete('/{id}')
79
+ .summary('Delete user')
80
+ .description('Permanently delete a user account')
81
+ .path_param(:id, :uuid, description: 'User unique identifier')
82
+ .requires_scope('users:delete')
83
+ .no_content(description: 'User deleted successfully')
84
+ .not_found(Error, description: 'User not found')
85
+ end
86
+
87
+ puts '✅ Defined complete REST API with 5 endpoints'
88
+ puts ' - GET /api/v1/users (list with pagination)'
89
+ puts ' - GET /api/v1/users/{id} (get by ID)'
90
+ puts ' - POST /api/v1/users (create)'
91
+ puts ' - PUT /api/v1/users/{id} (update)'
92
+ puts ' - DELETE /api/v1/users/{id} (delete)'
93
+
94
+ puts "\n✅ Features implemented:"
95
+ puts ' - Bearer token authentication'
96
+ puts ' - Scope-based permissions (read/write/delete)'
97
+ puts ' - Full CRUD operations'
98
+ puts ' - Input validation with type constraints'
99
+ puts ' - Comprehensive error responses'
100
+ puts ' - OpenAPI 3.0.3 specification ready'
101
+ puts ' - Production-ready architecture'
102
+
103
+ puts "\n🚀 RapiTapir Phase 1 Complete!"
104
+ puts ' From documentation tool → Production API framework'
105
+ puts ' Ready for real-world applications! 🎉'
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example Rails controller using RapiTapir
4
+ #
5
+ # To use this in a Rails app:
6
+ # 1. Add this file to app/controllers/
7
+ # 2. Include RapiTapir::Server::Rails::Controller
8
+ # 3. Define endpoints using rapitapir_endpoint
9
+ # 4. Call process_rapitapir_endpoint in your action methods
10
+
11
+ require_relative '../../lib/rapitapir'
12
+ require_relative '../../lib/rapitapir/server/rails_adapter'
13
+
14
+ class UsersController < ApplicationController
15
+ include RapiTapir::Server::Rails::Controller
16
+
17
+ before_action :setup_users_data
18
+
19
+ # Define RapiTapir endpoints
20
+ rapitapir_endpoint :index, RapiTapir.get('/users')
21
+ .summary('List all users')
22
+ .description('Returns a list of all users in the system')
23
+ .out(RapiTapir::Core::Output.new(
24
+ kind: :json, type: { users: Array }
25
+ )) do |_inputs|
26
+ { users: @users.values }
27
+ end
28
+
29
+ rapitapir_endpoint :show, RapiTapir.get('/users/:id')
30
+ .summary('Get user by ID')
31
+ .description('Returns a single user by their ID')
32
+ .in(RapiTapir::Core::Input.new(kind: :path, name: :id, type: :integer))
33
+ .out(RapiTapir::Core::Output.new(
34
+ kind: :json,
35
+ type: { id: :integer, name: :string, email: :string }
36
+ )) do |inputs|
37
+ user_id = inputs[:id]
38
+ user = @users[user_id]
39
+
40
+ raise ArgumentError, 'User not found' unless user
41
+
42
+ user
43
+ end
44
+
45
+ rapitapir_endpoint :create, RapiTapir.post('/users')
46
+ .summary('Create a new user')
47
+ .description('Creates a new user with the provided data')
48
+ .in(RapiTapir::Core::Input.new(kind: :body, name: :user_data, type: Hash))
49
+ .out(RapiTapir::Core::Output.new(
50
+ kind: :json,
51
+ type: { id: :integer, name: :string, email: :string }
52
+ )) do |inputs|
53
+ user_data = inputs[:user_data]
54
+ new_id = (@users.keys.max || 0) + 1
55
+
56
+ new_user = {
57
+ id: new_id,
58
+ name: user_data['name'],
59
+ email: user_data['email']
60
+ }
61
+
62
+ @users[new_id] = new_user
63
+ new_user
64
+ end
65
+
66
+ rapitapir_endpoint :update, RapiTapir.put('/users/:id')
67
+ .summary('Update an existing user')
68
+ .description('Updates an existing user with the provided data')
69
+ .in(RapiTapir::Core::Input.new(kind: :path, name: :id, type: :integer))
70
+ .in(RapiTapir::Core::Input.new(kind: :body, name: :user_data, type: Hash))
71
+ .out(RapiTapir::Core::Output.new(
72
+ kind: :json,
73
+ type: { id: :integer, name: :string, email: :string }
74
+ )) do |inputs|
75
+ user_id = inputs[:id]
76
+ user_data = inputs[:user_data]
77
+
78
+ raise ArgumentError, 'User not found' unless @users[user_id]
79
+
80
+ @users[user_id].merge!(
81
+ name: user_data['name'] || @users[user_id][:name],
82
+ email: user_data['email'] || @users[user_id][:email]
83
+ )
84
+
85
+ @users[user_id]
86
+ end
87
+
88
+ rapitapir_endpoint :destroy, RapiTapir.delete('/users/:id')
89
+ .summary('Delete a user')
90
+ .description('Deletes a user by their ID')
91
+ .in(RapiTapir::Core::Input.new(kind: :path, name: :id, type: :integer))
92
+ .out(RapiTapir::Core::Output.new(
93
+ kind: :json, type: { message: :string }
94
+ )) do |inputs|
95
+ user_id = inputs[:id]
96
+
97
+ raise ArgumentError, 'User not found' unless @users[user_id]
98
+
99
+ @users.delete(user_id)
100
+ { message: "User #{user_id} deleted successfully" }
101
+ end
102
+
103
+ # Controller actions
104
+ def index
105
+ process_rapitapir_endpoint
106
+ end
107
+
108
+ def show
109
+ process_rapitapir_endpoint
110
+ end
111
+
112
+ def create
113
+ process_rapitapir_endpoint
114
+ end
115
+
116
+ def update
117
+ process_rapitapir_endpoint
118
+ end
119
+
120
+ def destroy
121
+ process_rapitapir_endpoint
122
+ end
123
+
124
+ private
125
+
126
+ def setup_users_data
127
+ @users = {
128
+ 1 => { id: 1, name: 'John Doe', email: 'john@example.com' },
129
+ 2 => { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
130
+ }
131
+ end
132
+ end
133
+
134
+ # Example config/routes.rb content:
135
+ #
136
+ # Rails.application.routes.draw do
137
+ # resources :users, only: [:index, :show, :create, :update, :destroy]
138
+ # end
139
+ #
140
+ # Or using the RailsAdapter for automatic route generation:
141
+ #
142
+ # adapter = RapiTapir::Server::Rails::RailsAdapter.new
143
+ # adapter.register_endpoint(list_endpoint, UsersController, :index)
144
+ # adapter.register_endpoint(show_endpoint, UsersController, :show)
145
+ # # ... etc
146
+ # adapter.generate_routes(Rails.application.routes)
@@ -0,0 +1,128 @@
1
+ #require 'rapitapir' # Only one require needed!
2
+ require 'sinatra/base'
3
+ require_relative '../../lib/rapitapir'
4
+
5
+ # Simple Book model for demonstration
6
+ class Book
7
+ attr_reader :id, :title, :author, :published, :isbn, :pages
8
+
9
+ @@books = [
10
+ { id: 1, title: "The Ruby Programming Language", author: "Matz", published: true, isbn: "978-0596516178", pages: 448 },
11
+ { id: 2, title: "Effective Ruby", author: "Peter Jones", published: true, isbn: "978-0134045993", pages: 256 },
12
+ { id: 3, title: "Ruby Under a Microscope", author: "Pat Shaughnessy", published: true, isbn: "978-1593275273", pages: 360 }
13
+ ]
14
+
15
+ def self.all
16
+ @@books
17
+ end
18
+
19
+ def self.find(id)
20
+ @@books.find { |book| book[:id] == id.to_i }
21
+ end
22
+
23
+ def self.create(attributes)
24
+ new_id = (@@books.map { |b| b[:id] }.max || 0) + 1
25
+ new_book = attributes.merge(id: new_id)
26
+ @@books << new_book
27
+ new_book
28
+ end
29
+
30
+ def self.search(query)
31
+ results = @@books.select do |book|
32
+ book[:title].downcase.include?(query.downcase) ||
33
+ book[:author].downcase.include?(query.downcase)
34
+ end
35
+ BookCollection.new(results)
36
+ end
37
+
38
+ def self.where(conditions)
39
+ results = @@books.select do |book|
40
+ conditions.all? { |key, value| book[key] == value }
41
+ end
42
+ BookCollection.new(results)
43
+ end
44
+ end
45
+
46
+ # Simple collection class to handle chaining
47
+ class BookCollection
48
+ def initialize(books)
49
+ @books = books
50
+ end
51
+
52
+ def limit(count)
53
+ BookCollection.new(@books.first(count))
54
+ end
55
+
56
+ def map(&block)
57
+ @books.map(&block)
58
+ end
59
+
60
+ def to_a
61
+ @books
62
+ end
63
+ end
64
+
65
+ class BookAPI < SinatraRapiTapir
66
+ # Configure API information
67
+ rapitapir do
68
+ info(
69
+ title: 'Book API',
70
+ description: 'A simple book management API',
71
+ version: '1.0.0'
72
+ )
73
+ development_defaults! # Auto CORS, docs, health checks
74
+ end
75
+
76
+ # Define your data schema with T shortcut (globally available!)
77
+ BOOK_SCHEMA = T.hash({
78
+ "id" => T.integer,
79
+ "title" => T.string(min_length: 1, max_length: 255),
80
+ "author" => T.string(min_length: 1),
81
+ "published" => T.boolean,
82
+ "isbn" => T.optional(T.string),
83
+ "pages" => T.optional(T.integer(minimum: 1))
84
+ })
85
+
86
+ # Define endpoints with the elegant resource DSL and enhanced HTTP verbs
87
+ api_resource '/books', schema: BOOK_SCHEMA do
88
+ crud do
89
+ index { Book.all }
90
+
91
+ show do |inputs|
92
+ book = Book.find(inputs[:id])
93
+ halt(404, { error: 'Book not found' }.to_json) unless book
94
+ book
95
+ end
96
+
97
+ create do |inputs|
98
+ Book.create(inputs[:body])
99
+ end
100
+ end
101
+
102
+ # Custom endpoint using enhanced DSL
103
+ custom :get, 'featured' do
104
+ Book.where(featured: true).to_a
105
+ end
106
+ end
107
+
108
+ # Alternative endpoint definition using enhanced HTTP verb DSL
109
+ endpoint(
110
+ GET('/api/books/search')
111
+ .query(:q, T.string(min_length: 1), description: 'Search query')
112
+ .query(:limit, T.optional(T.integer(minimum: 1, maximum: 100)), description: 'Results limit')
113
+ .summary('Search books')
114
+ .description('Search books by title or author')
115
+ .tags('Search')
116
+ .ok(T.array(BOOK_SCHEMA))
117
+ .bad_request(T.hash({ "error" => T.string }), description: 'Invalid search parameters')
118
+ .build
119
+ ) do |inputs|
120
+ query = inputs[:q]
121
+ limit = inputs[:limit] || 20
122
+
123
+ books = Book.search(query).limit(limit)
124
+ books.to_a
125
+ end
126
+
127
+ run! if __FILE__ == $0
128
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/rapitapir'
4
+
5
+ # Example demonstrating RapiTapir server functionality
6
+ class UserAPI
7
+ def initialize
8
+ @adapter = RapiTapir::Server::RackAdapter.new
9
+ @users = {
10
+ 1 => { id: 1, name: 'John Doe', email: 'john@example.com' },
11
+ 2 => { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
12
+ }
13
+
14
+ setup_middleware
15
+ setup_endpoints
16
+ end
17
+
18
+ def app
19
+ @adapter
20
+ end
21
+
22
+ private
23
+
24
+ def setup_middleware
25
+ # Add CORS middleware
26
+ @adapter.use(RapiTapir::Server::Middleware::CORS, {
27
+ allow_origin: '*',
28
+ allow_methods: %w[GET POST PUT DELETE OPTIONS],
29
+ allow_headers: %w[Content-Type Authorization]
30
+ })
31
+
32
+ # Add logging middleware
33
+ @adapter.use(RapiTapir::Server::Middleware::Logger)
34
+ end
35
+
36
+ def setup_endpoints
37
+ setup_list_users
38
+ setup_get_user
39
+ setup_create_user
40
+ setup_update_user
41
+ setup_delete_user
42
+ end
43
+
44
+ def setup_list_users
45
+ endpoint = RapiTapir.get('/users')
46
+ .summary('List all users')
47
+ .description('Returns a list of all users in the system')
48
+ .out(RapiTapir::Core::Output.new(kind: :json,
49
+ type: { users: [{
50
+ id: :integer, name: :string, email: :string
51
+ }] }))
52
+
53
+ @adapter.register_endpoint(endpoint, method(:list_users))
54
+ end
55
+
56
+ def setup_get_user
57
+ endpoint = RapiTapir.get('/users/:id')
58
+ .summary('Get user by ID')
59
+ .description('Returns a single user by their ID')
60
+ .in(RapiTapir::Core::Input.new(kind: :path, name: :id, type: :integer))
61
+ .out(RapiTapir::Core::Output.new(kind: :json,
62
+ type: {
63
+ id: :integer, name: :string, email: :string
64
+ }))
65
+
66
+ @adapter.register_endpoint(endpoint, method(:get_user))
67
+ end
68
+
69
+ def setup_create_user
70
+ endpoint = RapiTapir.post('/users')
71
+ .summary('Create a new user')
72
+ .description('Creates a new user with the provided data')
73
+ .in(RapiTapir::Core::Input.new(kind: :body, name: :user_data, type: Hash))
74
+ .out(RapiTapir::Core::Output.new(kind: :json,
75
+ type: {
76
+ id: :integer, name: :string, email: :string
77
+ }))
78
+
79
+ @adapter.register_endpoint(endpoint, method(:create_user))
80
+ end
81
+
82
+ def setup_update_user
83
+ endpoint = RapiTapir.put('/users/:id')
84
+ .summary('Update an existing user')
85
+ .description('Updates an existing user with the provided data')
86
+ .in(RapiTapir::Core::Input.new(kind: :path, name: :id, type: :integer))
87
+ .in(RapiTapir::Core::Input.new(kind: :body, name: :user_data, type: Hash))
88
+ .out(RapiTapir::Core::Output.new(kind: :json,
89
+ type: {
90
+ id: :integer, name: :string, email: :string
91
+ }))
92
+
93
+ @adapter.register_endpoint(endpoint, method(:update_user))
94
+ end
95
+
96
+ def setup_delete_user
97
+ endpoint = RapiTapir.delete('/users/:id')
98
+ .summary('Delete a user')
99
+ .description('Deletes a user by their ID')
100
+ .in(RapiTapir::Core::Input.new(kind: :path, name: :id, type: :integer))
101
+ .out(RapiTapir::Core::Output.new(kind: :json, type: { message: :string }))
102
+
103
+ @adapter.register_endpoint(endpoint, method(:delete_user))
104
+ end
105
+
106
+ # Handler methods
107
+ def list_users(_inputs)
108
+ { users: @users.values }
109
+ end
110
+
111
+ def get_user(inputs)
112
+ user_id = inputs[:id]
113
+ user = @users[user_id]
114
+
115
+ raise ArgumentError, 'User not found' unless user
116
+
117
+ user
118
+ end
119
+
120
+ def create_user(inputs)
121
+ user_data = inputs[:user_data]
122
+ new_id = (@users.keys.max || 0) + 1
123
+
124
+ new_user = {
125
+ id: new_id,
126
+ name: user_data['name'],
127
+ email: user_data['email']
128
+ }
129
+
130
+ @users[new_id] = new_user
131
+ new_user
132
+ end
133
+
134
+ def update_user(inputs)
135
+ user_id = inputs[:id]
136
+ user_data = inputs[:user_data]
137
+
138
+ raise ArgumentError, 'User not found' unless @users[user_id]
139
+
140
+ @users[user_id].merge!(
141
+ name: user_data['name'] || @users[user_id][:name],
142
+ email: user_data['email'] || @users[user_id][:email]
143
+ )
144
+
145
+ @users[user_id]
146
+ end
147
+
148
+ def delete_user(inputs)
149
+ user_id = inputs[:id]
150
+
151
+ raise ArgumentError, 'User not found' unless @users[user_id]
152
+
153
+ @users.delete(user_id)
154
+ { message: "User #{user_id} deleted successfully" }
155
+ end
156
+ end
157
+
158
+ # Example usage:
159
+ if __FILE__ == $PROGRAM_NAME
160
+ require 'rack'
161
+
162
+ user_api = UserAPI.new
163
+
164
+ puts 'Starting RapiTapir User API server on http://localhost:9292'
165
+ puts 'Available endpoints:'
166
+ puts ' GET /users - List all users'
167
+ puts ' GET /users/:id - Get user by ID'
168
+ puts ' POST /users - Create new user'
169
+ puts ' PUT /users/:id - Update user'
170
+ puts ' DELETE /users/:id - Delete user'
171
+ puts ''
172
+ puts 'Example requests:'
173
+ puts ' curl http://localhost:9292/users'
174
+ puts ' curl http://localhost:9292/users/1'
175
+ puts " curl -X POST http://localhost:9292/users -H 'Content-Type: application/json' " \
176
+ "-d '{\"name\":\"Bob\",\"email\":\"bob@example.com\"}'"
177
+
178
+ Rack::Handler::WEBrick.run(user_api.app, Port: 9292)
179
+ end