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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +57 -0
- data/CHANGELOG.md +94 -0
- data/CLEANUP_SUMMARY.md +155 -0
- data/CONTRIBUTING.md +280 -0
- data/LICENSE +21 -0
- data/README.md +485 -0
- data/debug_hash.rb +20 -0
- data/docs/EXTENSION_COMPARISON.md +388 -0
- data/docs/SINATRA_EXTENSION.md +467 -0
- data/docs/archive/PHASE_1_2_COMPLETE.md +77 -0
- data/docs/archive/PHASE_1_3_COMPLETE.md +152 -0
- data/docs/archive/PHASE_2_1_OBSERVABILITY_COMPLETED.md +203 -0
- data/docs/archive/PHASE_2_SUMMARY.md +209 -0
- data/docs/archive/REFACTORING_SUMMARY.md +184 -0
- data/docs/archive/phase_1_3_plan.md +136 -0
- data/docs/archive/sinatra_extension_summary.md +188 -0
- data/docs/archive/sinatra_working_solution.md +113 -0
- data/docs/archive/typescript-client-generator-summary.md +259 -0
- data/docs/auto-derivation.md +146 -0
- data/docs/blueprint.md +1091 -0
- data/docs/endpoint-definition.md +211 -0
- data/docs/github_pages_fix.md +52 -0
- data/docs/github_pages_setup.md +49 -0
- data/docs/implementation-status.md +357 -0
- data/docs/observability.md +647 -0
- data/docs/phase3-plan.md +108 -0
- data/docs/sinatra_rapitapir.md +87 -0
- data/docs/type_shortcuts.md +146 -0
- data/examples/README_ENTERPRISE.md +202 -0
- data/examples/authentication_example.rb +192 -0
- data/examples/auto_derivation_ruby_friendly.rb +163 -0
- data/examples/cli/user_api_endpoints.rb +56 -0
- data/examples/client/typescript_client_example.rb +102 -0
- data/examples/client/user-api-client.ts +193 -0
- data/examples/demo_api.rb +41 -0
- data/examples/docs/documentation_example.rb +112 -0
- data/examples/docs/user-api-docs.html +789 -0
- data/examples/docs/user-api-docs.md +403 -0
- data/examples/enhanced_auto_derivation_test.rb +83 -0
- data/examples/enterprise_extension_demo.rb +417 -0
- data/examples/enterprise_rapitapir_api.rb +662 -0
- data/examples/getting_started_extension.rb +218 -0
- data/examples/hello_world.rb +74 -0
- data/examples/oauth2/.env.example +19 -0
- data/examples/oauth2/README.md +205 -0
- data/examples/oauth2/generic_oauth2_api.rb +226 -0
- data/examples/oauth2/get_token.rb +72 -0
- data/examples/oauth2/songs_api_with_auth0.rb +320 -0
- data/examples/oauth2/test_api.sh +16 -0
- data/examples/oauth2/test_songs_api.sh +110 -0
- data/examples/observability/.env.example +35 -0
- data/examples/observability/README.md +230 -0
- data/examples/observability/README_HONEYCOMB.md +332 -0
- data/examples/observability/advanced_setup.rb +384 -0
- data/examples/observability/basic_setup.rb +192 -0
- data/examples/observability/complete_test.rb +121 -0
- data/examples/observability/honeycomb_example.rb +523 -0
- data/examples/observability/honeycomb_rapitapir_clean.rb +488 -0
- data/examples/observability/honeycomb_rapitapir_example.rb +523 -0
- data/examples/observability/honeycomb_working_example.rb +489 -0
- data/examples/observability/quick_test.rb +78 -0
- data/examples/observability/simple_test.rb +14 -0
- data/examples/observability/test_honeycomb_demo.rb +354 -0
- data/examples/observability/test_live_honeycomb.rb +111 -0
- data/examples/observability/test_validation.rb +78 -0
- data/examples/observability/test_working_validation.rb +66 -0
- data/examples/openapi/user_api_schema.rb +132 -0
- data/examples/production_ready_example.rb +105 -0
- data/examples/rails/users_controller.rb +146 -0
- data/examples/readme/basic_sinatra_example.rb +128 -0
- data/examples/server/user_api.rb +179 -0
- data/examples/simple_auto_derivation_demo.rb +44 -0
- data/examples/simple_demo_api.rb +18 -0
- data/examples/sinatra/user_app.rb +127 -0
- data/examples/t_shortcut_demo.rb +59 -0
- data/examples/user_api.rb +190 -0
- data/examples/working_getting_started.rb +184 -0
- data/examples/working_simple_example.rb +195 -0
- data/lib/rapitapir/auth/configuration.rb +129 -0
- data/lib/rapitapir/auth/context.rb +122 -0
- data/lib/rapitapir/auth/errors.rb +104 -0
- data/lib/rapitapir/auth/middleware.rb +324 -0
- data/lib/rapitapir/auth/oauth2.rb +350 -0
- data/lib/rapitapir/auth/schemes.rb +420 -0
- data/lib/rapitapir/auth.rb +113 -0
- data/lib/rapitapir/cli/command.rb +535 -0
- data/lib/rapitapir/cli/server.rb +243 -0
- data/lib/rapitapir/cli/validator.rb +373 -0
- data/lib/rapitapir/client/generator_base.rb +272 -0
- data/lib/rapitapir/client/typescript_generator.rb +350 -0
- data/lib/rapitapir/core/endpoint.rb +158 -0
- data/lib/rapitapir/core/enhanced_endpoint.rb +235 -0
- data/lib/rapitapir/core/input.rb +182 -0
- data/lib/rapitapir/core/output.rb +164 -0
- data/lib/rapitapir/core/request.rb +19 -0
- data/lib/rapitapir/core/response.rb +17 -0
- data/lib/rapitapir/docs/html_generator.rb +780 -0
- data/lib/rapitapir/docs/markdown_generator.rb +464 -0
- data/lib/rapitapir/dsl/endpoint_dsl.rb +116 -0
- data/lib/rapitapir/dsl/enhanced_endpoint_dsl.rb +62 -0
- data/lib/rapitapir/dsl/enhanced_input.rb +73 -0
- data/lib/rapitapir/dsl/enhanced_output.rb +63 -0
- data/lib/rapitapir/dsl/enhanced_structures.rb +393 -0
- data/lib/rapitapir/dsl/fluent_dsl.rb +72 -0
- data/lib/rapitapir/dsl/fluent_endpoint_builder.rb +316 -0
- data/lib/rapitapir/dsl/http_verbs.rb +77 -0
- data/lib/rapitapir/dsl/input_methods.rb +47 -0
- data/lib/rapitapir/dsl/observability_methods.rb +81 -0
- data/lib/rapitapir/dsl/output_methods.rb +43 -0
- data/lib/rapitapir/dsl/type_resolution.rb +43 -0
- data/lib/rapitapir/observability/configuration.rb +108 -0
- data/lib/rapitapir/observability/health_check.rb +236 -0
- data/lib/rapitapir/observability/logging.rb +270 -0
- data/lib/rapitapir/observability/metrics.rb +203 -0
- data/lib/rapitapir/observability/middleware.rb +243 -0
- data/lib/rapitapir/observability/tracing.rb +143 -0
- data/lib/rapitapir/observability.rb +28 -0
- data/lib/rapitapir/openapi/schema_generator.rb +403 -0
- data/lib/rapitapir/schema.rb +136 -0
- data/lib/rapitapir/server/enhanced_rack_adapter.rb +379 -0
- data/lib/rapitapir/server/middleware.rb +120 -0
- data/lib/rapitapir/server/path_matcher.rb +45 -0
- data/lib/rapitapir/server/rack_adapter.rb +215 -0
- data/lib/rapitapir/server/rails_adapter.rb +17 -0
- data/lib/rapitapir/server/rails_adapter_class.rb +53 -0
- data/lib/rapitapir/server/rails_controller.rb +72 -0
- data/lib/rapitapir/server/rails_input_processor.rb +73 -0
- data/lib/rapitapir/server/rails_response_handler.rb +29 -0
- data/lib/rapitapir/server/sinatra_adapter.rb +200 -0
- data/lib/rapitapir/server/sinatra_integration.rb +93 -0
- data/lib/rapitapir/sinatra/configuration.rb +91 -0
- data/lib/rapitapir/sinatra/extension.rb +214 -0
- data/lib/rapitapir/sinatra/oauth2_helpers.rb +236 -0
- data/lib/rapitapir/sinatra/resource_builder.rb +152 -0
- data/lib/rapitapir/sinatra/swagger_ui_generator.rb +166 -0
- data/lib/rapitapir/sinatra_rapitapir.rb +40 -0
- data/lib/rapitapir/types/array.rb +163 -0
- data/lib/rapitapir/types/auto_derivation.rb +265 -0
- data/lib/rapitapir/types/base.rb +146 -0
- data/lib/rapitapir/types/boolean.rb +46 -0
- data/lib/rapitapir/types/date.rb +92 -0
- data/lib/rapitapir/types/datetime.rb +98 -0
- data/lib/rapitapir/types/email.rb +32 -0
- data/lib/rapitapir/types/float.rb +134 -0
- data/lib/rapitapir/types/hash.rb +161 -0
- data/lib/rapitapir/types/integer.rb +143 -0
- data/lib/rapitapir/types/object.rb +156 -0
- data/lib/rapitapir/types/optional.rb +65 -0
- data/lib/rapitapir/types/string.rb +185 -0
- data/lib/rapitapir/types/uuid.rb +32 -0
- data/lib/rapitapir/types.rb +155 -0
- data/lib/rapitapir/version.rb +5 -0
- data/lib/rapitapir.rb +173 -0
- data/rapitapir.gemspec +66 -0
- 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
|