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,44 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ puts "=== RapiTapir Auto-Derivation Examples ==="
5
+
6
+ require_relative '../lib/rapitapir/types'
7
+
8
+ # 1. JSON Schema Example
9
+ puts "\n1. JSON Schema Auto-Derivation"
10
+ puts "-" * 30
11
+
12
+ user_schema = {
13
+ "type" => "object",
14
+ "properties" => {
15
+ "name" => { "type" => "string" },
16
+ "age" => { "type" => "integer" },
17
+ "active" => { "type" => "boolean" }
18
+ }
19
+ }
20
+
21
+ result = RapiTapir::Types.from_json_schema(user_schema)
22
+ puts "āœ… Derived schema from JSON Schema"
23
+ puts "Fields: #{result.field_types.keys.join(', ')}"
24
+
25
+ # 2. OpenStruct Example
26
+ puts "\n2. OpenStruct Auto-Derivation"
27
+ puts "-" * 30
28
+
29
+ require 'ostruct'
30
+ config = OpenStruct.new(
31
+ host: "api.example.com",
32
+ port: 443,
33
+ ssl: true
34
+ )
35
+
36
+ result = RapiTapir::Types.from_open_struct(config)
37
+ puts "āœ… Derived schema from OpenStruct"
38
+ puts "Fields: #{result.field_types.keys.join(', ')}"
39
+
40
+ puts "\nšŸŽ‰ Auto-derivation working for structured data sources!"
41
+ puts "šŸ’” Focus on sources with explicit type information:"
42
+ puts " - JSON Schema (API contracts)"
43
+ puts " - OpenStruct (configuration objects)"
44
+ puts " - Protobuf (when available)"
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/rapitapir'
4
+ include RapiTapir::DSL
5
+
6
+ # Simple API endpoints for demonstration
7
+ RapiTapir.get('/users')
8
+ .out(json_body([{ id: :integer, name: :string, email: :string }]))
9
+ .summary('List all users')
10
+ .description('Retrieve a paginated list of all users in the system')
11
+
12
+ RapiTapir.post('/users')
13
+ .in(body({ name: :string, email: :string, password: :string }))
14
+ .out(status_code(201), json_body({ id: :integer, name: :string, email: :string }))
15
+ .error_out(400, json_body({ error: :string, validation_errors: [:string] }))
16
+ .error_out(422, json_body({ error: :string, message: :string }))
17
+ .summary('Create new user')
18
+ .description('Create a new user account with the provided information')
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra'
4
+ require 'json'
5
+ require_relative '../../lib/rapitapir'
6
+ require_relative '../../lib/rapitapir/server/sinatra_adapter'
7
+
8
+ # Example Sinatra app using RapiTapir
9
+ class UserSinatraApp < Sinatra::Base
10
+ def initialize
11
+ super
12
+ @users = {
13
+ 1 => { id: 1, name: 'John Doe', email: 'john@example.com' },
14
+ 2 => { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
15
+ }
16
+
17
+ setup_rapitapir_endpoints
18
+ end
19
+
20
+ private
21
+
22
+ def setup_rapitapir_endpoints
23
+ adapter = RapiTapir::Server::SinatraAdapter.new(self)
24
+
25
+ # List users endpoint
26
+ list_endpoint = RapiTapir.get('/users')
27
+ .summary('List all users')
28
+ .out(RapiTapir::Core::Output.new(kind: :json, type: { users: Array }))
29
+
30
+ adapter.register_endpoint(list_endpoint) do |_inputs|
31
+ { users: @users.values }
32
+ end
33
+
34
+ # Get user by ID endpoint
35
+ get_endpoint = RapiTapir.get('/users/:id')
36
+ .summary('Get user by ID')
37
+ .in(RapiTapir::Core::Input.new(kind: :path, name: :id, type: :integer))
38
+ .out(RapiTapir::Core::Output.new(kind: :json,
39
+ type: {
40
+ id: :integer, name: :string, email: :string
41
+ }))
42
+
43
+ adapter.register_endpoint(get_endpoint) do |inputs|
44
+ user_id = inputs[:id]
45
+ user = @users[user_id]
46
+
47
+ raise ArgumentError, 'User not found' unless user
48
+
49
+ user
50
+ end
51
+
52
+ # Create user endpoint
53
+ create_endpoint = RapiTapir.post('/users')
54
+ .summary('Create a new user')
55
+ .in(RapiTapir::Core::Input.new(kind: :body, name: :user_data, type: Hash))
56
+ .out(RapiTapir::Core::Output.new(kind: :json,
57
+ type: {
58
+ id: :integer, name: :string, email: :string
59
+ }))
60
+
61
+ adapter.register_endpoint(create_endpoint) do |inputs|
62
+ user_data = inputs[:user_data]
63
+ new_id = (@users.keys.max || 0) + 1
64
+
65
+ new_user = {
66
+ id: new_id,
67
+ name: user_data['name'],
68
+ email: user_data['email']
69
+ }
70
+
71
+ @users[new_id] = new_user
72
+ new_user
73
+ end
74
+
75
+ # Update user endpoint
76
+ update_endpoint = RapiTapir.put('/users/:id')
77
+ .summary('Update an existing user')
78
+ .in(RapiTapir::Core::Input.new(kind: :path, name: :id, type: :integer))
79
+ .in(RapiTapir::Core::Input.new(kind: :body, name: :user_data, type: Hash))
80
+ .out(RapiTapir::Core::Output.new(kind: :json,
81
+ type: {
82
+ id: :integer, name: :string, email: :string
83
+ }))
84
+
85
+ adapter.register_endpoint(update_endpoint) do |inputs|
86
+ user_id = inputs[:id]
87
+ user_data = inputs[:user_data]
88
+
89
+ raise ArgumentError, 'User not found' unless @users[user_id]
90
+
91
+ @users[user_id].merge!(
92
+ name: user_data['name'] || @users[user_id][:name],
93
+ email: user_data['email'] || @users[user_id][:email]
94
+ )
95
+
96
+ @users[user_id]
97
+ end
98
+
99
+ # Delete user endpoint
100
+ delete_endpoint = RapiTapir.delete('/users/:id')
101
+ .summary('Delete a user')
102
+ .in(RapiTapir::Core::Input.new(kind: :path, name: :id, type: :integer))
103
+ .out(RapiTapir::Core::Output.new(kind: :json, type: { message: :string }))
104
+
105
+ adapter.register_endpoint(delete_endpoint) do |inputs|
106
+ user_id = inputs[:id]
107
+
108
+ raise ArgumentError, 'User not found' unless @users[user_id]
109
+
110
+ @users.delete(user_id)
111
+ { message: "User #{user_id} deleted successfully" }
112
+ end
113
+ end
114
+ end
115
+
116
+ # Run the app if this file is executed directly
117
+ if __FILE__ == $PROGRAM_NAME
118
+ puts 'Starting RapiTapir Sinatra User API server on http://localhost:4567'
119
+ puts 'Available endpoints:'
120
+ puts ' GET /users - List all users'
121
+ puts ' GET /users/:id - Get user by ID'
122
+ puts ' POST /users - Create new user'
123
+ puts ' PUT /users/:id - Update user'
124
+ puts ' DELETE /users/:id - Delete user'
125
+
126
+ UserSinatraApp.run!
127
+ end
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Demonstration: T shortcut works immediately without any setup
5
+
6
+ require_relative '../lib/rapitapir'
7
+ require_relative '../lib/rapitapir/sinatra_rapitapir'
8
+
9
+ puts "šŸŽÆ Testing automatic T shortcut availability..."
10
+ puts
11
+
12
+ # Test 1: Top-level usage
13
+ puts "āœ… Top-level usage:"
14
+ BOOK_SCHEMA = T.hash({
15
+ "id" => T.integer,
16
+ "title" => T.string(min_length: 1),
17
+ "published" => T.boolean
18
+ })
19
+ puts " BOOK_SCHEMA = T.hash(...) āœ“"
20
+ puts " Schema type: #{BOOK_SCHEMA.class}"
21
+ puts
22
+
23
+ # Test 2: Inside a class
24
+ puts "āœ… Inside a class:"
25
+ class DemoAPI < SinatraRapiTapir
26
+ USER_SCHEMA = T.hash({
27
+ "name" => T.string,
28
+ "email" => T.email,
29
+ "age" => T.optional(T.integer(min: 0))
30
+ })
31
+
32
+ puts " USER_SCHEMA = T.hash(...) āœ“"
33
+ puts " Schema type: #{USER_SCHEMA.class}"
34
+ end
35
+ puts
36
+
37
+ # Test 3: Complex nested types
38
+ puts "āœ… Complex nested types:"
39
+ COMPLEX_SCHEMA = T.hash({
40
+ "user" => T.hash({
41
+ "profile" => T.optional(T.hash({
42
+ "preferences" => T.array(T.string),
43
+ "settings" => T.hash({
44
+ "theme" => T.string,
45
+ "notifications" => T.boolean
46
+ })
47
+ }))
48
+ }),
49
+ "metadata" => T.hash({
50
+ "version" => T.string,
51
+ "timestamp" => T.datetime
52
+ })
53
+ })
54
+ puts " Complex nested schema created āœ“"
55
+ puts " Schema type: #{COMPLEX_SCHEMA.class}"
56
+ puts
57
+
58
+ puts "šŸŽ‰ All tests passed! T shortcut works everywhere automatically!"
59
+ puts "šŸ“ No manual setup required - just `require 'rapitapir'` and use T.* anywhere!"
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require_relative '../lib/rapitapir'
5
+
6
+ # Example: User Management API
7
+ module UserAPI
8
+ extend RapiTapir::DSL
9
+
10
+ # GET /users - List all users
11
+ LIST_USERS = RapiTapir.get('/users')
12
+ .in(query(:page, :integer, optional: true))
13
+ .in(query(:limit, :integer, optional: true))
14
+ .in(query(:search, :string, optional: true))
15
+ .in(header(:authorization, :string))
16
+ .out(status_code(200))
17
+ .out(json_body({
18
+ users: [{ id: :integer, name: :string, email: :string }],
19
+ total: :integer,
20
+ page: :integer,
21
+ limit: :integer
22
+ }))
23
+ .error_out(401, json_body({ error: :string }))
24
+ .error_out(500, json_body({ error: :string }))
25
+ .description('Retrieve a paginated list of users')
26
+ .summary('List users')
27
+ .tag('users')
28
+
29
+ # GET /users/:id - Get specific user
30
+ GET_USER = RapiTapir.get('/users/:id')
31
+ .in(path_param(:id, :integer))
32
+ .in(header(:authorization, :string))
33
+ .out(status_code(200))
34
+ .out(json_body({ id: :integer, name: :string, email: :string, created_at: :datetime }))
35
+ .error_out(401, json_body({ error: :string }))
36
+ .error_out(404, json_body({ error: :string }))
37
+ .error_out(500, json_body({ error: :string }))
38
+ .description('Retrieve a specific user by ID')
39
+ .summary('Get user')
40
+ .tag('users')
41
+
42
+ # POST /users - Create new user
43
+ CREATE_USER = RapiTapir.post('/users')
44
+ .in(header(:authorization, :string))
45
+ .in(header(:'content-type', :string))
46
+ .in(body({ name: :string, email: :string, password: :string }))
47
+ .out(status_code(201))
48
+ .out(json_body({ id: :integer, name: :string, email: :string, created_at: :datetime }))
49
+ .error_out(400, json_body({ error: :string, details: :string }))
50
+ .error_out(401, json_body({ error: :string }))
51
+ .error_out(422, json_body({ error: :string,
52
+ validation_errors: [{ field: :string, message: :string }] }))
53
+ .error_out(500, json_body({ error: :string }))
54
+ .description('Create a new user account')
55
+ .summary('Create user')
56
+ .tag('users')
57
+
58
+ # PUT /users/:id - Update user
59
+ UPDATE_USER = RapiTapir.put('/users/:id')
60
+ .in(path_param(:id, :integer))
61
+ .in(header(:authorization, :string))
62
+ .in(header(:'content-type', :string))
63
+ .in(body({ name: :string, email: :string }))
64
+ .out(status_code(200))
65
+ .out(json_body({ id: :integer, name: :string, email: :string, updated_at: :datetime }))
66
+ .error_out(400, json_body({ error: :string, details: :string }))
67
+ .error_out(401, json_body({ error: :string }))
68
+ .error_out(404, json_body({ error: :string }))
69
+ .error_out(422, json_body({ error: :string,
70
+ validation_errors: [{ field: :string, message: :string }] }))
71
+ .error_out(500, json_body({ error: :string }))
72
+ .description('Update an existing user')
73
+ .summary('Update user')
74
+ .tag('users')
75
+
76
+ # DELETE /users/:id - Delete user
77
+ DELETE_USER = RapiTapir.delete('/users/:id')
78
+ .in(path_param(:id, :integer))
79
+ .in(header(:authorization, :string))
80
+ .out(status_code(204))
81
+ .error_out(401, json_body({ error: :string }))
82
+ .error_out(404, json_body({ error: :string }))
83
+ .error_out(500, json_body({ error: :string }))
84
+ .description('Delete a user account')
85
+ .summary('Delete user')
86
+ .tag('users')
87
+
88
+ # POST /users/login - User authentication
89
+ LOGIN_USER = RapiTapir.post('/users/login')
90
+ .in(header(:'content-type', :string))
91
+ .in(body({ email: :string, password: :string }))
92
+ .out(status_code(200))
93
+ .out(json_body({
94
+ user: { id: :integer, name: :string, email: :string },
95
+ token: :string,
96
+ expires_at: :datetime
97
+ }))
98
+ .error_out(400, json_body({ error: :string }))
99
+ .error_out(401, json_body({ error: :string }))
100
+ .error_out(500, json_body({ error: :string }))
101
+ .description('Authenticate user and return access token')
102
+ .summary('User login')
103
+ .tag('authentication')
104
+
105
+ # Example usage and validation
106
+ def self.demo
107
+ puts '=== RapiTapir User API Demo ==='
108
+ puts
109
+
110
+ # Example 1: Valid user creation
111
+ puts '1. Valid user creation:'
112
+ input_data = {
113
+ body: { name: 'John Doe', email: 'john@example.com', password: 'secret123' }
114
+ }
115
+ output_data = {
116
+ id: 1,
117
+ name: 'John Doe',
118
+ email: 'john@example.com',
119
+ created_at: DateTime.now
120
+ }
121
+
122
+ begin
123
+ CREATE_USER.validate!(input_data, output_data)
124
+ puts 'āœ“ Validation passed'
125
+ rescue StandardError => e
126
+ puts "āœ— Validation failed: #{e.message}"
127
+ end
128
+ puts
129
+
130
+ # Example 2: Invalid user creation (missing required field)
131
+ puts '2. Invalid user creation (missing password):'
132
+ invalid_input = {
133
+ body: { name: 'John Doe', email: 'john@example.com' }
134
+ }
135
+
136
+ begin
137
+ CREATE_USER.validate!(invalid_input, output_data)
138
+ puts 'āœ“ Validation passed'
139
+ rescue StandardError => e
140
+ puts "āœ— Validation failed: #{e.message}"
141
+ end
142
+ puts
143
+
144
+ # Example 3: Valid user listing
145
+ puts '3. Valid user listing:'
146
+ list_input = {}
147
+ list_output = {
148
+ users: [
149
+ { id: 1, name: 'John Doe', email: 'john@example.com' },
150
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
151
+ ],
152
+ total: 2,
153
+ page: 1,
154
+ limit: 10
155
+ }
156
+
157
+ begin
158
+ LIST_USERS.validate!(list_input, list_output)
159
+ puts 'āœ“ Validation passed'
160
+ rescue StandardError => e
161
+ puts "āœ— Validation failed: #{e.message}"
162
+ end
163
+ puts
164
+
165
+ # Example 4: Endpoint metadata
166
+ puts '4. Endpoint metadata:'
167
+ puts 'GET /users:'
168
+ puts " Description: #{GET_USER.metadata[:description]}"
169
+ puts " Summary: #{GET_USER.metadata[:summary]}"
170
+ puts " Tag: #{GET_USER.metadata[:tag]}"
171
+ puts " Inputs: #{GET_USER.inputs.length}"
172
+ puts " Outputs: #{GET_USER.outputs.length}"
173
+ puts " Error responses: #{GET_USER.errors.length}"
174
+ puts
175
+
176
+ # Example 5: Serialization
177
+ puts '5. Output serialization:'
178
+ json_output = CREATE_USER.outputs.find { |o| o.kind == :json }
179
+ if json_output
180
+ serialized = json_output.serialize(output_data)
181
+ puts "JSON: #{serialized}"
182
+ end
183
+ puts
184
+
185
+ puts '=== Demo Complete ==='
186
+ end
187
+ end
188
+
189
+ # Run the demo if this file is executed directly
190
+ UserAPI.demo if __FILE__ == $PROGRAM_NAME
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RapiTapir Sinatra Extension - WORKING Getting Started Example
4
+ #
5
+ # This is a simplified version that actually works with Sinatra!
6
+
7
+ # Check for Sinatra availability
8
+ begin
9
+ require 'sinatra/base'
10
+ SINATRA_AVAILABLE = true
11
+ rescue LoadError
12
+ SINATRA_AVAILABLE = false
13
+ puts 'āš ļø Sinatra not available. Install with: gem install sinatra'
14
+ puts 'šŸ”„ Running in demo mode instead...'
15
+ end
16
+
17
+ require_relative '../lib/rapitapir'
18
+
19
+ # Only require SinatraAdapter if Sinatra is available
20
+ if SINATRA_AVAILABLE
21
+ require_relative '../lib/rapitapir/server/sinatra_adapter'
22
+ require_relative '../lib/rapitapir/openapi/schema_generator'
23
+ require_relative '../lib/rapitapir/sinatra/swagger_ui_generator'
24
+ end
25
+
26
+ # Simple in-memory data store
27
+ class BookStore
28
+ @@books = [
29
+ { id: 1, title: 'The Ruby Programming Language', author: 'Matz', isbn: '978-0596516178', published: true },
30
+ { id: 2, title: 'Metaprogramming Ruby', author: 'Paolo Perrotta', isbn: '978-1934356470', published: true }
31
+ ]
32
+ @@next_id = 3
33
+
34
+ def self.all
35
+ @@books
36
+ end
37
+
38
+ def self.find(id)
39
+ @@books.find { |book| book[:id] == id.to_i }
40
+ end
41
+
42
+ def self.create(attrs)
43
+ book = attrs.merge(id: @@next_id)
44
+ @@next_id += 1
45
+ @@books << book
46
+ book
47
+ end
48
+
49
+ def self.published
50
+ @@books.select { |book| book[:published] }
51
+ end
52
+ end
53
+
54
+ # Define the book schema
55
+ BOOK_SCHEMA = RapiTapir::Types.hash({
56
+ 'id' => RapiTapir::Types.integer,
57
+ 'title' => RapiTapir::Types.string,
58
+ 'author' => RapiTapir::Types.string,
59
+ 'isbn' => RapiTapir::Types.string,
60
+ 'published' => RapiTapir::Types.boolean
61
+ })
62
+
63
+ # WORKING Bookstore API - simplified but effective!
64
+ if SINATRA_AVAILABLE
65
+ class WorkingBookstoreAPI < Sinatra::Base
66
+ configure do
67
+ # Create the RapiTapir adapter - this is the key!
68
+ set :rapitapir, RapiTapir::Server::SinatraAdapter.new(self)
69
+ puts 'šŸ“ RapiTapir integration enabled'
70
+ end
71
+
72
+ # OpenAPI JSON endpoint - clean and simple
73
+ get '/openapi.json' do
74
+ content_type :json
75
+ endpoints = settings.rapitapir.endpoints.map { |ep_data| ep_data[:endpoint] }
76
+
77
+ generator = RapiTapir::OpenAPI::SchemaGenerator.new(
78
+ endpoints: endpoints,
79
+ info: {
80
+ title: 'WORKING Bookstore API',
81
+ description: 'A simple bookstore API built with RapiTapir and Sinatra - WORKING VERSION!',
82
+ version: '1.0.0'
83
+ },
84
+ servers: [{ url: 'http://localhost:4567', description: 'Development server' }]
85
+ )
86
+
87
+ generator.to_json
88
+ end
89
+
90
+ # Swagger UI documentation endpoint - clean and simple
91
+ get '/docs' do
92
+ content_type :html
93
+ api_info = {
94
+ title: 'WORKING Bookstore API',
95
+ description: 'A simple bookstore API built with RapiTapir and Sinatra - WORKING VERSION!'
96
+ }
97
+
98
+ RapiTapir::Sinatra::SwaggerUIGenerator.new('/openapi.json', api_info).generate
99
+ end
100
+
101
+ # Health check - super simple
102
+ health_endpoint = RapiTapir.get('/health')
103
+ .summary('Health check')
104
+ .ok(RapiTapir::Types.hash({ 'status' => RapiTapir::Types.string }))
105
+ .build
106
+
107
+ settings.rapitapir.register_endpoint(health_endpoint) { { status: 'healthy' } }
108
+
109
+ # List all books
110
+ list_books_endpoint = RapiTapir.get('/books')
111
+ .summary('List all books')
112
+ .ok(RapiTapir::Types.array(BOOK_SCHEMA))
113
+ .build
114
+
115
+ settings.rapitapir.register_endpoint(list_books_endpoint) { BookStore.all }
116
+
117
+ # Published books only - custom endpoint (define BEFORE :id route)
118
+ published_books_endpoint = RapiTapir.get('/books/published')
119
+ .summary('Get published books only')
120
+ .ok(RapiTapir::Types.array(BOOK_SCHEMA))
121
+ .build
122
+
123
+ settings.rapitapir.register_endpoint(published_books_endpoint) { BookStore.published }
124
+
125
+ # Get book by ID (must come AFTER specific routes like /books/published)
126
+ get_book_endpoint = RapiTapir.get('/books/:id')
127
+ .summary('Get book by ID')
128
+ .path_param(:id, RapiTapir::Types.integer, description: 'Book ID')
129
+ .ok(BOOK_SCHEMA)
130
+ .build
131
+
132
+ settings.rapitapir.register_endpoint(get_book_endpoint) do |inputs|
133
+ book = BookStore.find(inputs[:id])
134
+ halt 404, { error: 'Book not found' }.to_json unless book
135
+ book
136
+ end
137
+
138
+ configure :development do
139
+ puts "\nšŸ“š WORKING Bookstore API with RapiTapir"
140
+ puts '🌐 Health: http://localhost:4567/health'
141
+ puts 'šŸ“– Books: http://localhost:4567/books'
142
+ puts 'šŸ“‹ Published: http://localhost:4567/books/published'
143
+ puts 'šŸ“– Book by ID: http://localhost:4567/books/1'
144
+ puts 'šŸ“š Documentation: http://localhost:4567/docs'
145
+ puts 'šŸ“‹ OpenAPI: http://localhost:4567/openapi.json'
146
+ puts "\nāœ… Zero boilerplate, working integration!"
147
+ puts 'šŸ’” Key: Direct use of SinatraAdapter.register_endpoint()'
148
+ puts 'šŸ“– Full OpenAPI 3.0 documentation auto-generated!'
149
+ end
150
+ end
151
+
152
+ WorkingBookstoreAPI.run! if __FILE__ == $PROGRAM_NAME
153
+ else
154
+ # Demo mode when Sinatra is not available
155
+ puts "\nšŸ“š RapiTapir Bookstore API - Demo Mode"
156
+ puts '=' * 50
157
+
158
+ puts "\nāœ… Successfully loaded:"
159
+ puts ' • RapiTapir core'
160
+ puts ' • Type system'
161
+ puts ' • Book schema and store'
162
+
163
+ puts "\nšŸ“Š This working API provides:"
164
+ puts ' GET /health - Health check'
165
+ puts ' GET /books - List all books'
166
+ puts ' GET /books/:id - Get book by ID'
167
+ puts ' GET /books/published - Published books only'
168
+
169
+ puts "\nšŸ”§ Working pattern:"
170
+ puts ' • Create SinatraAdapter: RapiTapir::Server::SinatraAdapter.new(self)'
171
+ puts ' • Register endpoints: settings.rapitapir.register_endpoint(endpoint, &handler)'
172
+ puts " • Build endpoints: RapiTapir.get('/path').summary('...').build"
173
+ puts ' • Simple and effective!'
174
+
175
+ puts "\nšŸ’” To run the working server:"
176
+ puts ' gem install sinatra rack'
177
+ puts " ruby #{__FILE__}"
178
+
179
+ puts "\nšŸ“– Sample usage with curl:"
180
+ puts ' curl http://localhost:4567/health'
181
+ puts ' curl http://localhost:4567/books'
182
+ puts ' curl http://localhost:4567/books/1'
183
+ puts ' curl http://localhost:4567/books/published'
184
+ end