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,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RapiTapir
|
4
|
+
module DSL
|
5
|
+
# Enhanced Output class that uses the new type system
|
6
|
+
class EnhancedOutput
|
7
|
+
attr_reader :kind, :type, :options
|
8
|
+
|
9
|
+
def initialize(kind:, type:, options: {})
|
10
|
+
@kind = kind
|
11
|
+
@type = type
|
12
|
+
@options = options.freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate(value)
|
16
|
+
return { valid: true, errors: [] } if kind == :status
|
17
|
+
|
18
|
+
type.validate(value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def serialize(value)
|
22
|
+
case kind
|
23
|
+
when :json
|
24
|
+
JSON.generate(value)
|
25
|
+
when :status
|
26
|
+
# Status codes don't need serialization
|
27
|
+
value
|
28
|
+
else # :text and other formats
|
29
|
+
value.to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_openapi_response
|
34
|
+
if kind == :status
|
35
|
+
{
|
36
|
+
description: options[:description] || "Response with status #{type}",
|
37
|
+
content: {}
|
38
|
+
}
|
39
|
+
else
|
40
|
+
schema = type.respond_to?(:to_json_schema) ? type.to_json_schema : { type: 'string' }
|
41
|
+
content_type = kind == :json ? 'application/json' : 'text/plain'
|
42
|
+
|
43
|
+
{
|
44
|
+
description: options[:description] || 'Successful response',
|
45
|
+
content: {
|
46
|
+
content_type => {
|
47
|
+
schema: schema
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_h
|
55
|
+
{
|
56
|
+
kind: kind,
|
57
|
+
type: type.respond_to?(:to_s) ? type.to_s : type.class.name,
|
58
|
+
options: options
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,393 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RapiTapir
|
4
|
+
module DSL
|
5
|
+
# Enhanced input specification with full type system integration
|
6
|
+
class EnhancedInput
|
7
|
+
attr_reader :kind, :name, :type, :required, :description, :example, :format, :content_type
|
8
|
+
|
9
|
+
def initialize(kind:, name:, type:, required: true, **options)
|
10
|
+
@kind = kind.to_sym
|
11
|
+
@name = name.to_sym
|
12
|
+
@type = type
|
13
|
+
@required = required
|
14
|
+
@description = options[:description]
|
15
|
+
@example = options[:example]
|
16
|
+
@format = options[:format]
|
17
|
+
@content_type = options[:content_type]
|
18
|
+
end
|
19
|
+
|
20
|
+
def required?
|
21
|
+
# Check if the input is required based on:
|
22
|
+
# 1. Explicit required parameter
|
23
|
+
# 2. Whether the type is optional
|
24
|
+
return false if @required == false # Explicitly set to false
|
25
|
+
return false if @type.respond_to?(:optional?) && @type.optional?
|
26
|
+
|
27
|
+
@required.nil? || @required # Default to true if not specified
|
28
|
+
end
|
29
|
+
|
30
|
+
def optional?
|
31
|
+
!@required
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_openapi_spec
|
35
|
+
spec = {
|
36
|
+
name: @name.to_s,
|
37
|
+
in: openapi_location,
|
38
|
+
required: @required,
|
39
|
+
schema: @type.to_json_schema
|
40
|
+
}
|
41
|
+
|
42
|
+
spec[:description] = @description if @description
|
43
|
+
spec[:example] = @example if @example
|
44
|
+
|
45
|
+
spec
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate(value)
|
49
|
+
@type.validate(value)
|
50
|
+
end
|
51
|
+
|
52
|
+
def coerce(value)
|
53
|
+
@type.coerce(value)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def openapi_location
|
59
|
+
case @kind
|
60
|
+
when :query
|
61
|
+
'query'
|
62
|
+
when :path
|
63
|
+
'path'
|
64
|
+
when :header
|
65
|
+
'header'
|
66
|
+
when :body
|
67
|
+
'requestBody'
|
68
|
+
else
|
69
|
+
@kind.to_s
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Enhanced output specification with status codes and content types
|
75
|
+
class EnhancedOutput
|
76
|
+
attr_reader :status_code, :type, :content_type, :description, :example, :headers
|
77
|
+
|
78
|
+
def initialize(status_code:, type: nil, content_type: 'application/json', **options)
|
79
|
+
@status_code = status_code.to_i
|
80
|
+
@type = type
|
81
|
+
@content_type = content_type
|
82
|
+
@description = options[:description]
|
83
|
+
@example = options[:example]
|
84
|
+
@headers = options[:headers] || {}
|
85
|
+
end
|
86
|
+
|
87
|
+
# Legacy compatibility method for validators
|
88
|
+
def kind
|
89
|
+
return :status if @type.nil?
|
90
|
+
|
91
|
+
case @content_type
|
92
|
+
when 'application/xml', 'text/xml'
|
93
|
+
:xml
|
94
|
+
else # Default to json for application/json and unknown content types
|
95
|
+
:json
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_openapi_spec
|
100
|
+
spec = {
|
101
|
+
description: @description || http_status_description
|
102
|
+
}
|
103
|
+
|
104
|
+
add_content_to_spec(spec) if @type
|
105
|
+
add_headers_to_spec(spec) if @headers.any?
|
106
|
+
|
107
|
+
spec
|
108
|
+
end
|
109
|
+
|
110
|
+
def add_content_to_spec(spec)
|
111
|
+
spec[:content] = {
|
112
|
+
@content_type => {
|
113
|
+
schema: @type.to_json_schema
|
114
|
+
}
|
115
|
+
}
|
116
|
+
|
117
|
+
spec[:content][@content_type][:example] = @example if @example
|
118
|
+
end
|
119
|
+
|
120
|
+
def add_headers_to_spec(spec)
|
121
|
+
spec[:headers] = @headers.transform_values do |header_spec|
|
122
|
+
transform_header_spec(header_spec)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def transform_header_spec(header_spec)
|
127
|
+
case header_spec
|
128
|
+
when Hash
|
129
|
+
header_spec
|
130
|
+
when String
|
131
|
+
{ description: header_spec }
|
132
|
+
else
|
133
|
+
{ description: header_spec.to_s }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def validate(value)
|
138
|
+
return { valid: true, errors: [] } unless @type
|
139
|
+
|
140
|
+
@type.validate(value)
|
141
|
+
end
|
142
|
+
|
143
|
+
def serialize(value)
|
144
|
+
return nil unless @type
|
145
|
+
|
146
|
+
case @content_type
|
147
|
+
when 'application/json'
|
148
|
+
JSON.generate(value)
|
149
|
+
when 'text/plain'
|
150
|
+
value.to_s
|
151
|
+
else
|
152
|
+
value
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def http_status_description
|
159
|
+
case @status_code
|
160
|
+
when 200, 201, 202, 204
|
161
|
+
success_status_description
|
162
|
+
when 400, 401, 403, 404, 422
|
163
|
+
client_error_status_description
|
164
|
+
when 500
|
165
|
+
'Internal Server Error'
|
166
|
+
else
|
167
|
+
"HTTP #{@status_code}"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def success_status_description
|
172
|
+
case @status_code
|
173
|
+
when 200 then 'OK'
|
174
|
+
when 201 then 'Created'
|
175
|
+
when 202 then 'Accepted'
|
176
|
+
when 204 then 'No Content'
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def client_error_status_description
|
181
|
+
case @status_code
|
182
|
+
when 400 then 'Bad Request'
|
183
|
+
when 401 then 'Unauthorized'
|
184
|
+
when 403 then 'Forbidden'
|
185
|
+
when 404 then 'Not Found'
|
186
|
+
when 422 then 'Unprocessable Entity'
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Enhanced error specification for detailed error responses
|
192
|
+
class EnhancedError
|
193
|
+
attr_reader :status_code, :type, :description, :example
|
194
|
+
|
195
|
+
def initialize(status_code:, type: nil, description: nil, example: nil)
|
196
|
+
@status_code = status_code.to_i
|
197
|
+
@type = type
|
198
|
+
@description = description
|
199
|
+
@example = example
|
200
|
+
end
|
201
|
+
|
202
|
+
def to_openapi_spec
|
203
|
+
spec = {
|
204
|
+
description: @description || http_error_description
|
205
|
+
}
|
206
|
+
|
207
|
+
if @type
|
208
|
+
spec[:content] = {
|
209
|
+
'application/json' => {
|
210
|
+
schema: @type.to_json_schema
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
spec[:content]['application/json'][:example] = @example if @example
|
215
|
+
end
|
216
|
+
|
217
|
+
spec
|
218
|
+
end
|
219
|
+
|
220
|
+
def matches?(error)
|
221
|
+
error.respond_to?(:status_code) && error.status_code == @status_code
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
def http_error_description
|
227
|
+
case @status_code
|
228
|
+
when 400
|
229
|
+
'Bad Request - Invalid input parameters'
|
230
|
+
when 401
|
231
|
+
'Unauthorized - Authentication required'
|
232
|
+
when 403
|
233
|
+
'Forbidden - Insufficient permissions'
|
234
|
+
when 404
|
235
|
+
'Not Found - Resource not found'
|
236
|
+
when 422
|
237
|
+
'Unprocessable Entity - Validation failed'
|
238
|
+
when 500
|
239
|
+
'Internal Server Error - Server encountered an error'
|
240
|
+
else
|
241
|
+
"HTTP Error #{@status_code}"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Enhanced security specification for authentication schemes
|
247
|
+
class EnhancedSecurity
|
248
|
+
attr_reader :type, :description, :name, :location, :scopes, :flows
|
249
|
+
|
250
|
+
def initialize(type:, description:, **config)
|
251
|
+
@type = type.to_sym
|
252
|
+
@description = description
|
253
|
+
@name = config[:name]
|
254
|
+
@location = config[:location]&.to_sym
|
255
|
+
@scopes = Array(config[:scopes] || [])
|
256
|
+
@flows = config[:flows]
|
257
|
+
@options = config.except(:name, :location, :scopes, :flows)
|
258
|
+
end
|
259
|
+
|
260
|
+
def to_openapi_spec
|
261
|
+
spec = {
|
262
|
+
type: openapi_type,
|
263
|
+
description: @description
|
264
|
+
}
|
265
|
+
|
266
|
+
add_auth_specific_fields(spec)
|
267
|
+
spec
|
268
|
+
end
|
269
|
+
|
270
|
+
def add_auth_specific_fields(spec)
|
271
|
+
case @type
|
272
|
+
when :bearer
|
273
|
+
add_bearer_fields(spec)
|
274
|
+
when :api_key
|
275
|
+
add_api_key_fields(spec)
|
276
|
+
when :basic
|
277
|
+
add_basic_fields(spec)
|
278
|
+
when :oauth2
|
279
|
+
add_oauth2_fields(spec)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def add_bearer_fields(spec)
|
284
|
+
spec[:scheme] = 'bearer'
|
285
|
+
spec[:bearerFormat] = @options[:bearer_format] if @options[:bearer_format]
|
286
|
+
end
|
287
|
+
|
288
|
+
def add_api_key_fields(spec)
|
289
|
+
spec[:name] = @name || 'X-API-Key'
|
290
|
+
spec[:in] = (@location || :header).to_s
|
291
|
+
end
|
292
|
+
|
293
|
+
def add_basic_fields(spec)
|
294
|
+
spec[:scheme] = 'basic'
|
295
|
+
end
|
296
|
+
|
297
|
+
def add_oauth2_fields(spec)
|
298
|
+
spec[:flows] = @flows || default_oauth2_flows
|
299
|
+
end
|
300
|
+
|
301
|
+
def validate_request(request)
|
302
|
+
case @type
|
303
|
+
when :bearer
|
304
|
+
validate_bearer_token(request)
|
305
|
+
when :api_key
|
306
|
+
validate_api_key(request)
|
307
|
+
when :basic
|
308
|
+
validate_basic_auth(request)
|
309
|
+
when :oauth2
|
310
|
+
validate_oauth2_token(request)
|
311
|
+
else
|
312
|
+
{ valid: false, error: "Unsupported auth type: #{@type}" }
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
private
|
317
|
+
|
318
|
+
def openapi_type
|
319
|
+
case @type
|
320
|
+
when :bearer, :basic
|
321
|
+
'http'
|
322
|
+
when :api_key
|
323
|
+
'apiKey'
|
324
|
+
when :oauth2
|
325
|
+
'oauth2'
|
326
|
+
else
|
327
|
+
@type.to_s
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def default_oauth2_flows
|
332
|
+
{
|
333
|
+
implicit: {
|
334
|
+
authorizationUrl: @options[:authorization_url] || 'https://example.com/oauth/authorize',
|
335
|
+
scopes: @scopes.each_with_object({}) { |scope, hash| hash[scope] = scope.to_s.humanize }
|
336
|
+
}
|
337
|
+
}
|
338
|
+
end
|
339
|
+
|
340
|
+
def validate_bearer_token(request)
|
341
|
+
auth_header = request.env['HTTP_AUTHORIZATION']
|
342
|
+
return { valid: false, error: 'Missing Authorization header' } unless auth_header
|
343
|
+
|
344
|
+
return { valid: false, error: 'Invalid Authorization header format' } unless auth_header.start_with?('Bearer ')
|
345
|
+
|
346
|
+
token = auth_header[7..] # Remove 'Bearer ' prefix
|
347
|
+
return { valid: false, error: 'Empty token' } if token.empty?
|
348
|
+
|
349
|
+
{ valid: true, token: token }
|
350
|
+
end
|
351
|
+
|
352
|
+
def validate_api_key(request)
|
353
|
+
key_name = @name || 'X-API-Key'
|
354
|
+
|
355
|
+
key_value = case @location
|
356
|
+
when :query
|
357
|
+
request.params[key_name]
|
358
|
+
else # Default to header for :header and unknown locations
|
359
|
+
request.env["HTTP_#{key_name.upcase.gsub('-', '_')}"]
|
360
|
+
end
|
361
|
+
|
362
|
+
return { valid: false, error: "Missing API key: #{key_name}" } unless key_value
|
363
|
+
return { valid: false, error: 'Empty API key' } if key_value.empty?
|
364
|
+
|
365
|
+
{ valid: true, api_key: key_value }
|
366
|
+
end
|
367
|
+
|
368
|
+
def validate_basic_auth(request)
|
369
|
+
auth_header = request.env['HTTP_AUTHORIZATION']
|
370
|
+
return { valid: false, error: 'Missing Authorization header' } unless auth_header
|
371
|
+
|
372
|
+
return { valid: false, error: 'Invalid Authorization header format' } unless auth_header.start_with?('Basic ')
|
373
|
+
|
374
|
+
encoded_credentials = auth_header[6..] # Remove 'Basic ' prefix
|
375
|
+
return { valid: false, error: 'Empty credentials' } if encoded_credentials.empty?
|
376
|
+
|
377
|
+
begin
|
378
|
+
decoded_credentials = Base64.decode64(encoded_credentials)
|
379
|
+
username, password = decoded_credentials.split(':', 2)
|
380
|
+
{ valid: true, username: username, password: password }
|
381
|
+
rescue StandardError => e
|
382
|
+
{ valid: false, error: "Invalid credentials encoding: #{e.message}" }
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def validate_oauth2_token(request)
|
387
|
+
# OAuth2 validation would typically involve token introspection
|
388
|
+
# For now, we'll do basic Bearer token validation
|
389
|
+
validate_bearer_token(request)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../core/endpoint'
|
4
|
+
require_relative 'fluent_endpoint_builder'
|
5
|
+
|
6
|
+
# RapiTapir Ruby library for building type-safe HTTP APIs
|
7
|
+
# Provides a declarative way to define REST APIs with automatic validation
|
8
|
+
module RapiTapir
|
9
|
+
# Fluent DSL for defining HTTP endpoints
|
10
|
+
# Provides chainable methods for declarative API definition
|
11
|
+
#
|
12
|
+
# @example Basic usage
|
13
|
+
# RapiTapir.get('/users')
|
14
|
+
# .ok(RapiTapir::Types.array(RapiTapir::Types.hash({"id" => RapiTapir::Types.integer})))
|
15
|
+
# .summary('Get all users')
|
16
|
+
# .build
|
17
|
+
#
|
18
|
+
# @example With parameters
|
19
|
+
# RapiTapir.get('/users/{id}')
|
20
|
+
# .path_param(:id, RapiTapir::Types.integer)
|
21
|
+
# .ok(RapiTapir::Types.hash({"id" => RapiTapir::Types.integer, "name" => RapiTapir::Types.string}))
|
22
|
+
# .build
|
23
|
+
|
24
|
+
# HTTP GET endpoint
|
25
|
+
# @param path [String] The endpoint path
|
26
|
+
# @return [DSL::FluentEndpointBuilder] Builder for method chaining
|
27
|
+
def self.get(path)
|
28
|
+
DSL::FluentEndpointBuilder.new(:get, path)
|
29
|
+
end
|
30
|
+
|
31
|
+
# HTTP POST endpoint
|
32
|
+
# @param path [String] The endpoint path
|
33
|
+
# @return [DSL::FluentEndpointBuilder] Builder for method chaining
|
34
|
+
def self.post(path)
|
35
|
+
DSL::FluentEndpointBuilder.new(:post, path)
|
36
|
+
end
|
37
|
+
|
38
|
+
# HTTP PUT endpoint
|
39
|
+
# @param path [String] The endpoint path
|
40
|
+
# @return [DSL::FluentEndpointBuilder] Builder for method chaining
|
41
|
+
def self.put(path)
|
42
|
+
DSL::FluentEndpointBuilder.new(:put, path)
|
43
|
+
end
|
44
|
+
|
45
|
+
# HTTP PATCH endpoint
|
46
|
+
# @param path [String] The endpoint path
|
47
|
+
# @return [DSL::FluentEndpointBuilder] Builder for method chaining
|
48
|
+
def self.patch(path)
|
49
|
+
DSL::FluentEndpointBuilder.new(:patch, path)
|
50
|
+
end
|
51
|
+
|
52
|
+
# HTTP DELETE endpoint
|
53
|
+
# @param path [String] The endpoint path
|
54
|
+
# @return [DSL::FluentEndpointBuilder] Builder for method chaining
|
55
|
+
def self.delete(path)
|
56
|
+
DSL::FluentEndpointBuilder.new(:delete, path)
|
57
|
+
end
|
58
|
+
|
59
|
+
# HTTP HEAD endpoint
|
60
|
+
# @param path [String] The endpoint path
|
61
|
+
# @return [DSL::FluentEndpointBuilder] Builder for method chaining
|
62
|
+
def self.head(path)
|
63
|
+
DSL::FluentEndpointBuilder.new(:head, path)
|
64
|
+
end
|
65
|
+
|
66
|
+
# HTTP OPTIONS endpoint
|
67
|
+
# @param path [String] The endpoint path
|
68
|
+
# @return [DSL::FluentEndpointBuilder] Builder for method chaining
|
69
|
+
def self.options(path)
|
70
|
+
DSL::FluentEndpointBuilder.new(:options, path)
|
71
|
+
end
|
72
|
+
end
|