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,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example: Authentication and Security in RapiTapir
4
+ #
5
+ # This example demonstrates how to use RapiTapir's Phase 2.2 Authentication & Security features,
6
+ # including various authentication schemes, authorization middleware, and security features.
7
+
8
+ require_relative '../lib/rapitapir'
9
+ require 'ostruct'
10
+
11
+ # Helper method to create simple JWT (for demonstration only)
12
+ def create_simple_jwt(payload, secret)
13
+ require 'base64'
14
+ require 'json'
15
+ require 'openssl'
16
+
17
+ header = { 'alg' => 'HS256', 'typ' => 'JWT' }
18
+
19
+ encoded_header = Base64.urlsafe_encode64(JSON.generate(header)).tr('=', '')
20
+ encoded_payload = Base64.urlsafe_encode64(JSON.generate(payload)).tr('=', '')
21
+
22
+ signature = Base64.urlsafe_encode64(
23
+ OpenSSL::HMAC.digest('SHA256', secret, "#{encoded_header}.#{encoded_payload}")
24
+ ).tr('=', '')
25
+
26
+ "#{encoded_header}.#{encoded_payload}.#{signature}"
27
+ end
28
+
29
+ # Configure authentication globally
30
+ RapiTapir::Auth.configure do |config|
31
+ config.default_realm = 'My API'
32
+ config.jwt_secret = 'your-secret-key'
33
+ config.oauth2.client_id = 'your-client-id'
34
+ config.oauth2.client_secret = 'your-client-secret'
35
+ config.rate_limiting.requests_per_minute = 100
36
+ end
37
+
38
+ # Create authentication schemes
39
+ bearer_auth = RapiTapir::Auth.bearer_token(:bearer, {
40
+ token_validator: proc do |token|
41
+ # Validate token against your database or service
42
+ case token
43
+ when 'valid-token-123'
44
+ {
45
+ user: { id: 123, name: 'John Doe', email: 'john@example.com' },
46
+ scopes: %w[read write]
47
+ }
48
+ when 'admin-token-456'
49
+ {
50
+ user: { id: 456, name: 'Admin User', email: 'admin@example.com' },
51
+ scopes: %w[read write admin]
52
+ }
53
+ end
54
+ end
55
+ })
56
+
57
+ api_key_auth = RapiTapir::Auth.api_key(:api_key, {
58
+ header_name: 'X-API-Key',
59
+ key_validator: proc do |key|
60
+ # Validate API key
61
+ case key
62
+ when 'api-key-789'
63
+ {
64
+ user: { id: 'api-user', name: 'API Client' },
65
+ scopes: ['read']
66
+ }
67
+ end
68
+ end
69
+ })
70
+
71
+ jwt_auth = RapiTapir::Auth.jwt(:jwt, {
72
+ secret: 'your-jwt-secret',
73
+ algorithm: 'HS256'
74
+ })
75
+
76
+ # Usage examples:
77
+
78
+ # 1. Test authentication manually
79
+ puts '=== Authentication Examples ==='
80
+
81
+ # Create a mock request for Bearer token
82
+ bearer_request = Struct.new(:env, :params, :headers).new(
83
+ { 'HTTP_AUTHORIZATION' => 'Bearer valid-token-123' },
84
+ {},
85
+ { 'authorization' => 'Bearer valid-token-123' }
86
+ )
87
+
88
+ context = bearer_auth.authenticate(bearer_request)
89
+ puts "Bearer auth result: #{context&.authenticated?} - User: #{context&.user}"
90
+
91
+ # Create a mock request for API key
92
+ api_key_request = Struct.new(:env, :params, :headers).new(
93
+ { 'HTTP_X_API_KEY' => 'api-key-789' },
94
+ {},
95
+ { 'x-api-key' => 'api-key-789' }
96
+ )
97
+
98
+ context = api_key_auth.authenticate(api_key_request)
99
+ puts "API key auth result: #{context&.authenticated?} - User: #{context&.user}"
100
+
101
+ # 2. Test JWT authentication
102
+ puts "\n=== JWT Authentication Example ==="
103
+
104
+ # Create a simple JWT token
105
+ jwt_payload = {
106
+ 'sub' => 'user123',
107
+ 'name' => 'JWT User',
108
+ 'scopes' => %w[read write],
109
+ 'exp' => Time.now.to_i + 3600
110
+ }
111
+
112
+ jwt_token = create_simple_jwt(jwt_payload, 'your-jwt-secret')
113
+ puts "Generated JWT: #{jwt_token[0..20]}..."
114
+
115
+ jwt_request = Struct.new(:env, :params, :headers).new(
116
+ { 'HTTP_AUTHORIZATION' => "Bearer #{jwt_token}" },
117
+ {},
118
+ { 'authorization' => "Bearer #{jwt_token}" }
119
+ )
120
+
121
+ context = jwt_auth.authenticate(jwt_request)
122
+ puts "JWT auth result: #{context&.authenticated?} - User: #{context&.user}"
123
+
124
+ # 3. Demonstrate context store
125
+ puts "\n=== Context Store Example ==="
126
+
127
+ test_context = RapiTapir::Auth::Context.new(
128
+ user: { id: 999, name: 'Test User' },
129
+ scopes: %w[read write],
130
+ token: 'test-token'
131
+ )
132
+
133
+ RapiTapir::Auth::ContextStore.with_context(test_context) do
134
+ puts "Current user: #{RapiTapir::Auth.current_user}"
135
+ puts "Authenticated: #{RapiTapir::Auth.authenticated?}"
136
+ puts "Has 'read' scope: #{RapiTapir::Auth.has_scope?('read')}"
137
+ puts "Has 'admin' scope: #{RapiTapir::Auth.has_scope?('admin')}"
138
+ end
139
+
140
+ puts "Context after block: #{RapiTapir::Auth.current_context}"
141
+
142
+ # 4. Test middleware functionality
143
+ puts "\n=== Middleware Examples ==="
144
+
145
+ # Test rate limiting storage
146
+ storage = RapiTapir::Auth::Middleware::RateLimitingMiddleware::MemoryStorage.new
147
+ storage.increment('test_key')
148
+ storage.increment('test_key')
149
+ puts "Rate limit count: #{storage.get('test_key')}"
150
+
151
+ # Test CORS functionality
152
+ cors_middleware = RapiTapir::Auth.cors_middleware({
153
+ allowed_origins: ['https://example.com'],
154
+ allowed_methods: %w[GET POST],
155
+ allow_credentials: true
156
+ })
157
+
158
+ puts "CORS middleware created: #{cors_middleware.class}"
159
+
160
+ # 5. Test authorization
161
+ puts "\n=== Authorization Examples ==="
162
+
163
+ admin_context = RapiTapir::Auth::Context.new(
164
+ user: { id: 123, name: 'Admin' },
165
+ scopes: %w[read write admin]
166
+ )
167
+
168
+ regular_context = RapiTapir::Auth::Context.new(
169
+ user: { id: 456, name: 'User' },
170
+ scopes: ['read']
171
+ )
172
+
173
+ RapiTapir::Auth::ContextStore.with_context(admin_context) do
174
+ puts "Admin user has admin scope: #{RapiTapir::Auth.has_scope?('admin')}"
175
+ end
176
+
177
+ RapiTapir::Auth::ContextStore.with_context(regular_context) do
178
+ puts "Regular user has admin scope: #{RapiTapir::Auth.has_scope?('admin')}"
179
+ end
180
+
181
+ puts "\n=== Phase 2.2 Authentication & Security System Complete ==="
182
+ puts '✅ Bearer Token Authentication'
183
+ puts '✅ API Key Authentication'
184
+ puts '✅ Basic Authentication'
185
+ puts '✅ OAuth2 Authentication'
186
+ puts '✅ JWT Authentication'
187
+ puts '✅ Authorization Middleware'
188
+ puts '✅ Rate Limiting'
189
+ puts '✅ CORS Support'
190
+ puts '✅ Security Headers'
191
+ puts '✅ Context Management'
192
+ puts '✅ Comprehensive Test Suite (91 tests)'
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file demonstrates the improved auto-derivation capabilities that work
4
+ # well with Ruby's dynamic nature and lack of built-in type declarations
5
+
6
+ require_relative '../lib/rapitapir/types'
7
+
8
+ puts "=== RapiTapir Auto-Derivation Examples ==="
9
+ puts
10
+
11
+ # 1. From Hash (most reliable for Ruby)
12
+ puts "1. From Hash (recommended for Ruby)"
13
+ hash_data = {
14
+ name: "John Doe",
15
+ age: 30,
16
+ email: "john@example.com",
17
+ active: true,
18
+ score: 95.5,
19
+ tags: ["developer", "ruby"],
20
+ metadata: { role: "senior", team: "backend" }
21
+ }
22
+
23
+ schema = RapiTapir::Types.from_hash(hash_data)
24
+ puts "Schema from hash:"
25
+ puts schema.field_types.inspect
26
+ puts
27
+
28
+ # 2. From Hash with filtering
29
+ filtered_schema = RapiTapir::Types.from_hash(hash_data, except: [:metadata])
30
+ puts "Schema with 'except' filtering:"
31
+ puts filtered_schema.field_types.inspect
32
+ puts
33
+
34
+ # 3. From explicit types (recommended for classes)
35
+ puts "2. From Class with Explicit Types (recommended)"
36
+ class Person
37
+ attr_accessor :name, :age, :email, :active
38
+ end
39
+
40
+ # Method 1: Via types parameter
41
+ person_schema = RapiTapir::Types.from_object(Person, types: {
42
+ name: :string,
43
+ age: :integer,
44
+ email: :string,
45
+ active: :boolean
46
+ })
47
+ puts "Person schema with explicit types:"
48
+ puts person_schema.field_types.inspect
49
+ puts
50
+
51
+ # 4. From instance (less reliable but convenient)
52
+ puts "3. From Instance (use with well-populated data)"
53
+ person = Person.new
54
+ person.name = "Jane Smith"
55
+ person.age = 28
56
+ person.email = "jane@example.com"
57
+ person.active = true
58
+
59
+ instance_schema = RapiTapir::Types.from_object(person)
60
+ puts "Schema from instance:"
61
+ puts instance_schema.field_types.inspect
62
+ puts
63
+
64
+ # 5. From Struct (good compromise)
65
+ puts "4. From Struct (good Ruby pattern)"
66
+ Point = Struct.new(:x, :y, :z) do
67
+ def distance_from_origin
68
+ Math.sqrt(x**2 + y**2 + z**2)
69
+ end
70
+ end
71
+
72
+ sample_point = Point.new(1.0, 2.0, 3.0)
73
+ point_schema = RapiTapir::Types.from_object(Point, sample: sample_point)
74
+ puts "Point schema from Struct:"
75
+ puts point_schema.field_types.inspect
76
+ puts
77
+
78
+ # 6. DSL approach (most Ruby-like)
79
+ puts "5. DSL Approach (Ruby-idiomatic)"
80
+ class User
81
+ include RapiTapir::Types::AutoDerivation::Annotated
82
+
83
+ attr_accessor :username, :email, :age, :admin
84
+
85
+ rapitapir_schema do
86
+ field :username, :string
87
+ field :email, :string
88
+ field :age, :integer
89
+ field :admin, :boolean
90
+ end
91
+ end
92
+
93
+ user_schema = RapiTapir::Types.from_object(User)
94
+ puts "User schema with DSL:"
95
+ puts user_schema.field_types.inspect
96
+ puts
97
+
98
+ # 7. OpenStruct (convenient for prototyping)
99
+ puts "6. From OpenStruct"
100
+ require 'ostruct'
101
+
102
+ config = OpenStruct.new(
103
+ host: "localhost",
104
+ port: 3000,
105
+ ssl: false,
106
+ timeout: 30.5
107
+ )
108
+
109
+ config_schema = RapiTapir::Types.from_open_struct(config)
110
+ puts "Config schema from OpenStruct:"
111
+ puts config_schema.field_types.inspect
112
+ puts
113
+
114
+ # 8. JSON Schema (for external APIs)
115
+ puts "7. From JSON Schema"
116
+ json_schema = {
117
+ "type" => "object",
118
+ "properties" => {
119
+ "id" => { "type" => "integer" },
120
+ "name" => { "type" => "string", "maxLength" => 100 },
121
+ "email" => { "type" => "string", "format" => "email" },
122
+ "created_at" => { "type" => "string", "format" => "date-time" },
123
+ "tags" => {
124
+ "type" => "array",
125
+ "items" => { "type" => "string" }
126
+ }
127
+ },
128
+ "required" => ["id", "name", "email"]
129
+ }
130
+
131
+ json_derived_schema = RapiTapir::Types.from_json_schema(json_schema)
132
+ puts "Schema from JSON Schema:"
133
+ puts json_derived_schema.field_types.inspect
134
+ puts
135
+
136
+ # 9. Demonstrate the limitations
137
+ puts "8. Limitations and Error Handling"
138
+
139
+ class EmptyClass
140
+ end
141
+
142
+ begin
143
+ # This should fail gracefully
144
+ RapiTapir::Types.from_object(EmptyClass)
145
+ rescue ArgumentError => e
146
+ puts "Expected error for empty class: #{e.message}"
147
+ end
148
+
149
+ begin
150
+ # This should also fail
151
+ RapiTapir::Types.from_object(Point) # Struct without sample
152
+ rescue ArgumentError => e
153
+ puts "Expected error for Struct without sample: #{e.message}"
154
+ end
155
+
156
+ puts
157
+ puts "=== Recommendations ==="
158
+ puts "1. Use from_hash() for parsed JSON or config data"
159
+ puts "2. Use explicit types parameter for Ruby classes"
160
+ puts "3. Use DSL annotations for reusable schemas"
161
+ puts "4. Use Structs for value objects with samples"
162
+ puts "5. Use from_json_schema() for external API integration"
163
+ puts "6. Avoid deriving from empty classes or nil values"
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add the lib directory to the load path
4
+ lib_path = File.expand_path('../lib', __dir__)
5
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
6
+
7
+ require 'rapitapir'
8
+
9
+ # Include DSL to use helper methods
10
+ include RapiTapir::DSL
11
+
12
+ # Define endpoints for CLI usage
13
+ [
14
+ # Get all users
15
+ RapiTapir.get('/users')
16
+ .out(json_body([{ id: :integer, name: :string, email: :string }]))
17
+ .summary('Get all users')
18
+ .description('Retrieve a list of all users'),
19
+
20
+ # Get user by ID
21
+ RapiTapir.get('/users/:id')
22
+ .in(path_param(:id, :integer))
23
+ .out(json_body({ id: :integer, name: :string, email: :string }))
24
+ .summary('Get user by ID')
25
+ .description('Retrieve a specific user by their ID'),
26
+
27
+ # Create new user
28
+ RapiTapir.post('/users')
29
+ .in(body({ name: :string, email: :string }))
30
+ .out(json_body({ id: :integer, name: :string, email: :string }))
31
+ .summary('Create new user')
32
+ .description('Create a new user with the provided information'),
33
+
34
+ # Update user
35
+ RapiTapir.put('/users/:id')
36
+ .in(path_param(:id, :integer))
37
+ .in(body({ name: :string, email: :string }))
38
+ .out(json_body({ id: :integer, name: :string, email: :string }))
39
+ .summary('Update user')
40
+ .description('Update an existing user'),
41
+
42
+ # Delete user
43
+ RapiTapir.delete('/users/:id')
44
+ .in(path_param(:id, :integer))
45
+ .out(json_body({ success: :boolean }))
46
+ .summary('Delete user')
47
+ .description('Delete a user by their ID'),
48
+
49
+ # Search users
50
+ RapiTapir.get('/users/search')
51
+ .in(query(:q, :string))
52
+ .in(query(:limit, :integer, optional: true))
53
+ .out(json_body([{ id: :integer, name: :string, email: :string }]))
54
+ .summary('Search users')
55
+ .description('Search for users by name or email')
56
+ ]
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/rapitapir'
4
+
5
+ # Include DSL to use helper methods
6
+ include RapiTapir::DSL
7
+
8
+ # Define the same User API as before
9
+ user_api = [
10
+ # Get all users
11
+ RapiTapir.get('/users')
12
+ .out(json_body([{ id: :integer, name: :string, email: :string }]))
13
+ .summary('Get all users')
14
+ .description('Retrieve a list of all users'),
15
+
16
+ # Get user by ID
17
+ RapiTapir.get('/users/:id')
18
+ .in(path_param(:id, :integer))
19
+ .out(json_body({ id: :integer, name: :string, email: :string }))
20
+ .summary('Get user by ID')
21
+ .description('Retrieve a specific user by their ID'),
22
+
23
+ # Create new user
24
+ RapiTapir.post('/users')
25
+ .in(body({ name: :string, email: :string }))
26
+ .out(json_body({ id: :integer, name: :string, email: :string }))
27
+ .summary('Create new user')
28
+ .description('Create a new user with the provided information'),
29
+
30
+ # Update user
31
+ RapiTapir.put('/users/:id')
32
+ .in(path_param(:id, :integer))
33
+ .in(body({ name: :string, email: :string }))
34
+ .out(json_body({ id: :integer, name: :string, email: :string }))
35
+ .summary('Update user')
36
+ .description('Update an existing user'),
37
+
38
+ # Delete user
39
+ RapiTapir.delete('/users/:id')
40
+ .in(path_param(:id, :integer))
41
+ .out(json_body({ success: :boolean }))
42
+ .summary('Delete user')
43
+ .description('Delete a user by their ID'),
44
+
45
+ # Search users
46
+ RapiTapir.get('/users/search')
47
+ .in(query(:q, :string))
48
+ .in(query(:limit, :integer, optional: true))
49
+ .out(json_body([{ id: :integer, name: :string, email: :string }]))
50
+ .summary('Search users')
51
+ .description('Search for users by name or email')
52
+ ]
53
+
54
+ # Generate TypeScript client
55
+ puts 'Generating TypeScript client...'
56
+
57
+ generator = RapiTapir::Client::TypescriptGenerator.new(
58
+ endpoints: user_api,
59
+ config: {
60
+ base_url: 'https://api.example.com',
61
+ client_name: 'UserApiClient',
62
+ package_name: '@mycompany/user-api-client',
63
+ version: '1.2.0'
64
+ }
65
+ )
66
+
67
+ # Save to file
68
+ output_file = File.join(__dir__, 'user-api-client.ts')
69
+ generator.save_to_file(output_file)
70
+
71
+ puts "\nTypeScript client generated successfully!"
72
+ puts "File: #{output_file}"
73
+ puts "\nTo use the client in your TypeScript project:"
74
+ puts '1. Copy the generated file to your project'
75
+ puts '2. Install dependencies: npm install'
76
+ puts '3. Import and use the client:'
77
+ puts ''
78
+ puts '```typescript'
79
+ puts "import UserApiClient from './user-api-client';"
80
+ puts ''
81
+ puts 'const client = new UserApiClient({'
82
+ puts " baseUrl: 'https://api.example.com',"
83
+ puts " headers: { 'Authorization': 'Bearer your-token' }"
84
+ puts '});'
85
+ puts ''
86
+ puts '// Get all users'
87
+ puts 'const users = await client.getUsers();'
88
+ puts ''
89
+ puts '// Get user by ID'
90
+ puts 'const user = await client.getUsersById({ id: 123 });'
91
+ puts ''
92
+ puts '// Create new user'
93
+ puts 'const newUser = await client.createUsers({'
94
+ puts " body: { name: 'John Doe', email: 'john@example.com' }"
95
+ puts '});'
96
+ puts ''
97
+ puts '// Search users'
98
+ puts 'const searchResults = await client.getUsersSearch({'
99
+ puts " q: 'john',"
100
+ puts ' limit: 10'
101
+ puts '});'
102
+ puts '```'
@@ -0,0 +1,193 @@
1
+ // Generated by RapiTapir TypeScript Client Generator
2
+ // Package: @mycompany/user-api-client
3
+ // Version: 1.2.0
4
+ // Base URL: https://api.example.com
5
+
6
+ // Response type for all API calls
7
+ export interface ApiResponse<T> {
8
+ data: T;
9
+ status: number;
10
+ headers: Record<string, string>;
11
+ }
12
+
13
+ // Error type for API errors
14
+ export interface ApiError {
15
+ message: string;
16
+ status: number;
17
+ details?: any;
18
+ }
19
+
20
+ // HTTP client configuration
21
+ export interface ClientConfig {
22
+ baseUrl?: string;
23
+ headers?: Record<string, string>;
24
+ timeout?: number;
25
+ }
26
+
27
+
28
+ // Generated types
29
+ export type GetusersResponse = { id: number; name: string; email: string }[];
30
+
31
+ export interface GetusersbyidRequest {
32
+ id: number;
33
+ }
34
+
35
+
36
+ export type GetusersbyidResponse = { id: number; name: string; email: string };
37
+
38
+ export interface CreateuserRequest {
39
+ body: { name: string; email: string };
40
+ }
41
+
42
+
43
+ export type CreateuserResponse = { id: number; name: string; email: string };
44
+
45
+ export interface UpdateuserRequest {
46
+ id: number;
47
+ body: { name: string; email: string };
48
+ }
49
+
50
+
51
+ export type UpdateuserResponse = { id: number; name: string; email: string };
52
+
53
+ export interface DeleteuserRequest {
54
+ id: number;
55
+ }
56
+
57
+
58
+ export type DeleteuserResponse = { success: boolean };
59
+
60
+ export interface GetuserssearchRequest {
61
+ q: string;
62
+ limit?: number;
63
+ }
64
+
65
+
66
+ export type GetuserssearchResponse = { id: number; name: string; email: string }[];
67
+
68
+ export class UserApiClient {
69
+ private baseUrl: string;
70
+ private headers: Record<string, string>;
71
+ private timeout: number;
72
+
73
+ constructor(config: ClientConfig = {}) {
74
+ this.baseUrl = config.baseUrl || 'https://api.example.com';
75
+ this.headers = config.headers || {};
76
+ this.timeout = config.timeout || 10000;
77
+ }
78
+
79
+ private async request<T>(
80
+ method: string,
81
+ path: string,
82
+ options: {
83
+ params?: Record<string, any>;
84
+ body?: any;
85
+ headers?: Record<string, string>;
86
+ } = {}
87
+ ): Promise<ApiResponse<T>> {
88
+ const url = new URL(path, this.baseUrl);
89
+
90
+ // Add query parameters
91
+ if (options.params) {
92
+ Object.entries(options.params).forEach(([key, value]) => {
93
+ if (value !== undefined && value !== null) {
94
+ url.searchParams.append(key, String(value));
95
+ }
96
+ });
97
+ }
98
+
99
+ const requestHeaders = {
100
+ 'Content-Type': 'application/json',
101
+ ...this.headers,
102
+ ...options.headers,
103
+ };
104
+
105
+ const requestInit: RequestInit = {
106
+ method,
107
+ headers: requestHeaders,
108
+ };
109
+
110
+ if (options.body) {
111
+ requestInit.body = JSON.stringify(options.body);
112
+ }
113
+
114
+ try {
115
+ const response = await fetch(url.toString(), requestInit);
116
+
117
+ const responseHeaders: Record<string, string> = {};
118
+ response.headers.forEach((value, key) => {
119
+ responseHeaders[key] = value;
120
+ });
121
+
122
+ let data: T;
123
+ const contentType = response.headers.get('content-type');
124
+ if (contentType && contentType.includes('application/json')) {
125
+ data = await response.json();
126
+ } else {
127
+ data = (await response.text()) as unknown as T;
128
+ }
129
+
130
+ if (!response.ok) {
131
+ const error: ApiError = {
132
+ message: `HTTP ${response.status}: ${response.statusText}`,
133
+ status: response.status,
134
+ details: data,
135
+ };
136
+ throw error;
137
+ }
138
+
139
+ return {
140
+ data,
141
+ status: response.status,
142
+ headers: responseHeaders,
143
+ };
144
+ } catch (error) {
145
+ if (error && typeof error === 'object' && 'status' in error) {
146
+ throw error; // Re-throw ApiError
147
+ }
148
+
149
+ const apiError: ApiError = {
150
+ message: error instanceof Error ? error.message : 'Unknown error',
151
+ status: 0,
152
+ details: error,
153
+ };
154
+ throw apiError;
155
+ }
156
+ }
157
+
158
+ async getUsers(): Promise<ApiResponse<GetusersResponse>> {
159
+ return this.request<GetusersResponse>('GET', '/users');
160
+ }
161
+
162
+ async getUsersById(request: GetusersbyidRequest): Promise<ApiResponse<GetusersbyidResponse>> {
163
+ return this.request<GetusersbyidResponse>('GET', `/users/${request.id}`, {
164
+ });
165
+ }
166
+
167
+ async createUser(request: CreateuserRequest): Promise<ApiResponse<CreateuserResponse>> {
168
+ return this.request<CreateuserResponse>('POST', '/users', {
169
+ body: request.body
170
+ });
171
+ }
172
+
173
+ async updateUser(request: UpdateuserRequest): Promise<ApiResponse<UpdateuserResponse>> {
174
+ return this.request<UpdateuserResponse>('PUT', `/users/${request.id}`, {
175
+ body: request.body
176
+ });
177
+ }
178
+
179
+ async deleteUser(request: DeleteuserRequest): Promise<ApiResponse<DeleteuserResponse>> {
180
+ return this.request<DeleteuserResponse>('DELETE', `/users/${request.id}`, {
181
+ });
182
+ }
183
+
184
+ async getUsersSearch(request: GetuserssearchRequest): Promise<ApiResponse<GetuserssearchResponse>> {
185
+ return this.request<GetuserssearchResponse>('GET', '/users/search', {
186
+ params: { q: request.q, limit: request.limit }
187
+ });
188
+ }
189
+ }
190
+
191
+
192
+ // Default export
193
+ export default UserApiClient;