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,316 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types'
4
+ require_relative '../schema'
5
+ require_relative '../core/enhanced_endpoint'
6
+ require_relative 'enhanced_structures'
7
+
8
+ module RapiTapir
9
+ module DSL
10
+ # Fluent builder for creating endpoints with a chainable DSL
11
+ class FluentEndpointBuilder
12
+ attr_reader :method, :path, :inputs, :outputs, :errors, :metadata, :security_schemes
13
+
14
+ def initialize(method, path)
15
+ @method = method.to_sym
16
+ @path = path.to_s
17
+ @inputs = []
18
+ @outputs = []
19
+ @errors = []
20
+ @metadata = {}
21
+ @security_schemes = []
22
+ @scopes = []
23
+ end
24
+
25
+ # Create a copy of this builder with new data
26
+ def copy_with(**changes)
27
+ new_builder = self.class.new(@method, @path)
28
+ copy_instance_variables_to_builder(new_builder, changes)
29
+ new_builder
30
+ end
31
+
32
+ private
33
+
34
+ def copy_instance_variables_to_builder(new_builder, changes)
35
+ copy_inputs_to_builder(new_builder, changes)
36
+ copy_outputs_and_errors_to_builder(new_builder, changes)
37
+ copy_metadata_and_security_to_builder(new_builder, changes)
38
+ end
39
+
40
+ def copy_inputs_to_builder(new_builder, changes)
41
+ new_builder.instance_variable_set(:@inputs, changes[:inputs] || @inputs.dup)
42
+ end
43
+
44
+ def copy_outputs_and_errors_to_builder(new_builder, changes)
45
+ new_builder.instance_variable_set(:@outputs, changes[:outputs] || @outputs.dup)
46
+ new_builder.instance_variable_set(:@errors, changes[:errors] || @errors.dup)
47
+ end
48
+
49
+ def copy_metadata_and_security_to_builder(new_builder, changes)
50
+ new_builder.instance_variable_set(:@metadata, changes[:metadata] || @metadata.dup)
51
+ new_builder.instance_variable_set(:@security_schemes, changes[:security_schemes] || @security_schemes.dup)
52
+ new_builder.instance_variable_set(:@scopes, changes[:scopes] || @scopes.dup)
53
+ end
54
+
55
+ public
56
+
57
+ # Documentation methods
58
+ def summary(text)
59
+ copy_with(metadata: @metadata.merge(summary: text))
60
+ end
61
+
62
+ def description(text)
63
+ copy_with(metadata: @metadata.merge(description: text))
64
+ end
65
+
66
+ def tags(*tag_list)
67
+ copy_with(metadata: @metadata.merge(tags: tag_list.flatten))
68
+ end
69
+
70
+ # Input specification methods
71
+ def query(name, type_def, **options)
72
+ type = resolve_type(type_def)
73
+ input = create_input(:query, name, type, **options)
74
+ copy_with(inputs: @inputs + [input])
75
+ end
76
+
77
+ def path_param(name, type_def, **options)
78
+ type = resolve_type(type_def)
79
+ input = create_input(:path, name, type, **options)
80
+ copy_with(inputs: @inputs + [input])
81
+ end
82
+
83
+ def header(name, type_def, **options)
84
+ type = resolve_type(type_def)
85
+ input = create_input(:header, name, type, **options)
86
+ copy_with(inputs: @inputs + [input])
87
+ end
88
+
89
+ def json_body(type_def, **options)
90
+ type = resolve_type(type_def)
91
+ input = create_input(:body, :body, type, format: :json, **options)
92
+ copy_with(inputs: @inputs + [input])
93
+ end
94
+
95
+ def form_body(type_def, **options)
96
+ type = resolve_type(type_def)
97
+ input = create_input(:body, :body, type, format: :form, **options)
98
+ copy_with(inputs: @inputs + [input])
99
+ end
100
+
101
+ def body(type_def, content_type: 'application/json', **options)
102
+ type = resolve_type(type_def)
103
+ input = create_input(:body, :body, type, content_type: content_type, **options)
104
+ copy_with(inputs: @inputs + [input])
105
+ end
106
+
107
+ # Output specification methods
108
+ def responds_with(status_code, **options)
109
+ output = create_output(status_code, **options)
110
+ copy_with(outputs: @outputs + [output])
111
+ end
112
+
113
+ def json_response(status_code, type_def, **options)
114
+ type = resolve_type(type_def)
115
+ output = create_output(status_code, type: type, content_type: 'application/json', **options)
116
+ copy_with(outputs: @outputs + [output])
117
+ end
118
+
119
+ def text_response(status_code, type_def = Types.string, **options)
120
+ type = resolve_type(type_def)
121
+ output = create_output(status_code, type: type, content_type: 'text/plain', **options)
122
+ copy_with(outputs: @outputs + [output])
123
+ end
124
+
125
+ def status_response(status_code, **options)
126
+ output = create_output(status_code, type: nil, **options)
127
+ copy_with(outputs: @outputs + [output])
128
+ end
129
+
130
+ # Convenience methods for common status codes
131
+ def ok(type_def = nil, **options)
132
+ if type_def
133
+ json_response(200, type_def, **options)
134
+ else
135
+ status_response(200, **options)
136
+ end
137
+ end
138
+
139
+ def created(type_def = nil, **options)
140
+ if type_def
141
+ json_response(201, type_def, **options)
142
+ else
143
+ status_response(201, **options)
144
+ end
145
+ end
146
+
147
+ def accepted(**options)
148
+ status_response(202, **options)
149
+ end
150
+
151
+ def no_content(**options)
152
+ status_response(204, **options)
153
+ end
154
+
155
+ # Error response methods
156
+ def error_response(status_code, type_def = nil, **options)
157
+ type = type_def ? resolve_type(type_def) : nil
158
+ error = create_error(status_code, type, **options)
159
+ copy_with(errors: @errors + [error])
160
+ end
161
+
162
+ def bad_request(type_def = nil, **options)
163
+ error_response(400, type_def, **options)
164
+ end
165
+
166
+ def unauthorized(type_def = nil, **options)
167
+ error_response(401, type_def, **options)
168
+ end
169
+
170
+ def forbidden(type_def = nil, **options)
171
+ error_response(403, type_def, **options)
172
+ end
173
+
174
+ def not_found(type_def = nil, **options)
175
+ error_response(404, type_def, **options)
176
+ end
177
+
178
+ def unprocessable_entity(type_def = nil, **options)
179
+ error_response(422, type_def, **options)
180
+ end
181
+
182
+ def internal_server_error(type_def = nil, **options)
183
+ error_response(500, type_def, **options)
184
+ end
185
+
186
+ # Authentication methods
187
+ def bearer_auth(description = 'Bearer token authentication', **options)
188
+ security = create_security(:bearer, description, **options)
189
+ copy_with(security_schemes: @security_schemes + [security])
190
+ end
191
+
192
+ def api_key_auth(name, location = :header, description = 'API key authentication', **options)
193
+ security = create_security(:api_key, description, name: name, location: location, **options)
194
+ copy_with(security_schemes: @security_schemes + [security])
195
+ end
196
+
197
+ def basic_auth(description = 'Basic authentication', **options)
198
+ security = create_security(:basic, description, **options)
199
+ copy_with(security_schemes: @security_schemes + [security])
200
+ end
201
+
202
+ def oauth2_auth(scopes = [], description = 'OAuth2 authentication', **options)
203
+ security = create_security(:oauth2, description, scopes: scopes, **options)
204
+ copy_with(security_schemes: @security_schemes + [security])
205
+ end
206
+
207
+ def requires_scope(*scope_list)
208
+ copy_with(scopes: @scopes + scope_list.flatten)
209
+ end
210
+
211
+ def optional_auth
212
+ copy_with(metadata: @metadata.merge(optional_auth: true))
213
+ end
214
+
215
+ # Build the final endpoint
216
+ def build
217
+ endpoint = Core::EnhancedEndpoint.new(
218
+ method: @method,
219
+ path: @path,
220
+ inputs: @inputs,
221
+ outputs: @outputs,
222
+ errors: @errors,
223
+ metadata: @metadata.merge(
224
+ security_schemes: @security_schemes,
225
+ scopes: @scopes
226
+ )
227
+ )
228
+
229
+ # Register the endpoint with RapiTapir
230
+ RapiTapir.register_endpoint(endpoint)
231
+ endpoint
232
+ end
233
+
234
+ private
235
+
236
+ def resolve_type(type_def)
237
+ case type_def
238
+ when Types::Base
239
+ type_def
240
+ when Symbol
241
+ resolve_symbol_type(type_def)
242
+ when Class
243
+ resolve_class_type(type_def)
244
+ when Schema, ->(obj) { obj.respond_to?(:validate) } # rubocop:disable Lint/DuplicateBranch
245
+ # Handle Schema objects and any object that can validate
246
+ type_def
247
+ else
248
+ raise ArgumentError, "Invalid type definition: #{type_def}"
249
+ end
250
+ end
251
+
252
+ def resolve_symbol_type(type_symbol)
253
+ symbol_type_mapping = {
254
+ string: Types.string,
255
+ integer: Types.integer,
256
+ float: Types.float,
257
+ boolean: Types.boolean,
258
+ date: Types.date,
259
+ datetime: Types.datetime,
260
+ uuid: Types.uuid,
261
+ email: Types.email
262
+ }
263
+
264
+ symbol_type_mapping[type_symbol] ||
265
+ raise(ArgumentError, "Unknown type symbol: #{type_symbol}")
266
+ end
267
+
268
+ def resolve_class_type(type_class)
269
+ raise ArgumentError, 'Type class must inherit from Types::Base' unless type_class < Types::Base
270
+
271
+ type_class.new
272
+ end
273
+
274
+ def create_input(kind, name, type, **options)
275
+ DSL::EnhancedInput.new(
276
+ kind: kind,
277
+ name: name,
278
+ type: type,
279
+ required: options.fetch(:required, true),
280
+ description: options[:description],
281
+ example: options[:example],
282
+ format: options[:format],
283
+ content_type: options[:content_type]
284
+ )
285
+ end
286
+
287
+ def create_output(status_code, **options)
288
+ DSL::EnhancedOutput.new(
289
+ status_code: status_code,
290
+ type: options[:type],
291
+ content_type: options[:content_type],
292
+ description: options[:description],
293
+ example: options[:example],
294
+ headers: options[:headers] || {}
295
+ )
296
+ end
297
+
298
+ def create_error(status_code, type, **options)
299
+ DSL::EnhancedError.new(
300
+ status_code: status_code,
301
+ type: type,
302
+ description: options[:description],
303
+ example: options[:example]
304
+ )
305
+ end
306
+
307
+ def create_security(type, description, **options)
308
+ DSL::EnhancedSecurity.new(
309
+ type: type,
310
+ description: description,
311
+ **options
312
+ )
313
+ end
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RapiTapir Enhanced DSL Module
4
+ # Provides concise HTTP verb methods for more readable endpoint definitions
5
+ #
6
+ # @example Usage
7
+ # include RapiTapir::DSL::HTTPVerbs
8
+ #
9
+ # endpoint = GET('/users')
10
+ # .ok(RapiTapir::Types.array(UserSchema))
11
+ # .summary('Get all users')
12
+ # .build
13
+ #
14
+ # endpoint = POST('/users')
15
+ # .body(UserSchema)
16
+ # .created(UserSchema)
17
+ # .summary('Create a user')
18
+ # .build
19
+
20
+ module RapiTapir
21
+ module DSL
22
+ # HTTP Verbs mixin module
23
+ # Provides concise HTTP verb methods that can be included in any class
24
+ module HTTPVerbs
25
+ # HTTP GET endpoint
26
+ # @param path [String] The endpoint path
27
+ # @return [DSL::FluentEndpointBuilder] Builder for method chaining
28
+ # rubocop:disable Naming/MethodName
29
+ def GET(path)
30
+ RapiTapir.get(path)
31
+ end
32
+
33
+ # HTTP POST endpoint
34
+ # @param path [String] The endpoint path
35
+ # @return [DSL::FluentEndpointBuilder] Builder for method chaining
36
+ def POST(path)
37
+ RapiTapir.post(path)
38
+ end
39
+
40
+ # HTTP PUT endpoint
41
+ # @param path [String] The endpoint path
42
+ # @return [DSL::FluentEndpointBuilder] Builder for method chaining
43
+ def PUT(path)
44
+ RapiTapir.put(path)
45
+ end
46
+
47
+ # HTTP PATCH endpoint
48
+ # @param path [String] The endpoint path
49
+ # @return [DSL::FluentEndpointBuilder] Builder for method chaining
50
+ def PATCH(path)
51
+ RapiTapir.patch(path)
52
+ end
53
+
54
+ # HTTP DELETE endpoint
55
+ # @param path [String] The endpoint path
56
+ # @return [DSL::FluentEndpointBuilder] Builder for method chaining
57
+ def DELETE(path)
58
+ RapiTapir.delete(path)
59
+ end
60
+
61
+ # HTTP HEAD endpoint
62
+ # @param path [String] The endpoint path
63
+ # @return [DSL::FluentEndpointBuilder] Builder for method chaining
64
+ def HEAD(path)
65
+ RapiTapir.head(path)
66
+ end
67
+
68
+ # HTTP OPTIONS endpoint
69
+ # @param path [String] The endpoint path
70
+ # @return [DSL::FluentEndpointBuilder] Builder for method chaining
71
+ def OPTIONS(path)
72
+ RapiTapir.options(path)
73
+ end
74
+ # rubocop:enable Naming/MethodName
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RapiTapir
4
+ module DSL
5
+ # Input DSL methods for enhanced endpoint DSL
6
+ module InputMethods
7
+ # Input DSL methods
8
+ def query(name, type_def, **options)
9
+ type = resolve_type(type_def)
10
+ create_input(:query, name, type, **options)
11
+ end
12
+
13
+ def path_param(name, type_def, **options)
14
+ type = resolve_type(type_def)
15
+ create_input(:path, name, type, **options)
16
+ end
17
+
18
+ def header(name, type_def, **options)
19
+ type = resolve_type(type_def)
20
+ create_input(:header, name, type, **options)
21
+ end
22
+
23
+ def body(type_def, **options)
24
+ type = resolve_type(type_def)
25
+ create_input(:body, :body, type, **options)
26
+ end
27
+
28
+ def json_body(type_def, **options)
29
+ type = resolve_type(type_def)
30
+ create_input(:body, :body, type, format: :json, **options)
31
+ end
32
+
33
+ def form_body(type_def, **options)
34
+ type = resolve_type(type_def)
35
+ create_input(:body, :body, type, format: :form, **options)
36
+ end
37
+
38
+ private
39
+
40
+ def create_input(kind, name, type, **options)
41
+ @inputs ||= []
42
+ @inputs << EnhancedInput.new(kind: kind, name: name, type: type, options: options)
43
+ self
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RapiTapir
4
+ module DSL
5
+ # Observability methods for enhanced endpoint DSL
6
+ module ObservabilityMethods
7
+ # Observability methods
8
+ def with_metrics(enabled: true, name: nil, labels: {})
9
+ @metrics_enabled = enabled
10
+ @metric_name = name || generate_metric_name
11
+ @metric_labels = labels
12
+ self
13
+ end
14
+
15
+ def with_tracing(enabled: true, span_name: nil, attributes: {})
16
+ @tracing_enabled = enabled
17
+ @trace_span_name = span_name || generate_span_name
18
+ @trace_attributes = attributes
19
+ self
20
+ end
21
+
22
+ def with_logging(enabled: true, **config)
23
+ @logging_enabled = enabled
24
+ @log_config = config
25
+ self
26
+ end
27
+
28
+ def metrics_enabled?
29
+ @metrics_enabled
30
+ end
31
+
32
+ def tracing_enabled?
33
+ @tracing_enabled
34
+ end
35
+
36
+ def logging_enabled?
37
+ @logging_enabled
38
+ end
39
+
40
+ def metric_labels
41
+ @metric_labels
42
+ end
43
+
44
+ def trace_attributes
45
+ @trace_attributes
46
+ end
47
+
48
+ def metric_name
49
+ @metric_name
50
+ end
51
+
52
+ def trace_span_name
53
+ @trace_span_name
54
+ end
55
+
56
+ def log_config
57
+ @log_config
58
+ end
59
+
60
+ private
61
+
62
+ def generate_metric_name
63
+ # Generate a metric name based on HTTP method and path
64
+ method = @method&.downcase || 'unknown'
65
+ path = if @path
66
+ @path.gsub(%r{[/:]}, '_').gsub(/_{2,}/, '_').strip('_')
67
+ else
68
+ 'unknown'
69
+ end
70
+ "#{method}_#{path}"
71
+ end
72
+
73
+ def generate_span_name
74
+ # Generate a span name for tracing
75
+ method = @method&.upcase || 'UNKNOWN'
76
+ path = @path || '/unknown'
77
+ "HTTP #{method} #{path}"
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RapiTapir
4
+ module DSL
5
+ # Output DSL methods for enhanced endpoint DSL
6
+ module OutputMethods
7
+ # Output DSL methods
8
+ def out_json(type_def, **options)
9
+ type = resolve_type(type_def)
10
+ create_output(:json, type, **options)
11
+ end
12
+
13
+ def out_text(type_def = Types.string, **options)
14
+ type = resolve_type(type_def)
15
+ create_output(:text, type, **options)
16
+ end
17
+
18
+ def out_xml(type_def, **options)
19
+ type = resolve_type(type_def)
20
+ create_output(:xml, type, **options)
21
+ end
22
+
23
+ def status(code, **options)
24
+ create_output(:status, code, **options)
25
+ end
26
+
27
+ def error_out(status_code, type_def, **options)
28
+ type = resolve_type(type_def)
29
+ @error_outputs ||= []
30
+ @error_outputs << EnhancedOutput.new(kind: :json, type: type, options: options.merge(status: status_code))
31
+ self
32
+ end
33
+
34
+ private
35
+
36
+ def create_output(kind, type, **options)
37
+ @outputs ||= []
38
+ @outputs << EnhancedOutput.new(kind: kind, type: type, options: options)
39
+ self
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../types'
4
+ require_relative '../schema'
5
+
6
+ module RapiTapir
7
+ module DSL
8
+ # Type resolution methods for enhanced endpoint DSL
9
+ module TypeResolution
10
+ PRIMITIVE_TYPE_MAP = {
11
+ string: -> { Types.string },
12
+ integer: -> { Types.integer },
13
+ float: -> { Types.float },
14
+ boolean: -> { Types.boolean },
15
+ date: -> { Types.date },
16
+ datetime: -> { Types.datetime },
17
+ uuid: -> { Types.uuid },
18
+ email: -> { Types.email }
19
+ }.freeze
20
+
21
+ private
22
+
23
+ def resolve_type(type_def)
24
+ case type_def
25
+ when Symbol
26
+ create_primitive_type(type_def)
27
+ when Hash, Array
28
+ Schema.from_definition(type_def)
29
+ else
30
+ # For classes and already resolved types, return as-is
31
+ type_def
32
+ end
33
+ end
34
+
35
+ def create_primitive_type(symbol)
36
+ type_creator = PRIMITIVE_TYPE_MAP[symbol]
37
+ raise ArgumentError, "Unknown primitive type: #{symbol}" unless type_creator
38
+
39
+ type_creator.call
40
+ end
41
+ end
42
+ end
43
+ end