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,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module RapiTapir
|
6
|
+
module Types
|
7
|
+
# String type with length and format validation
|
8
|
+
#
|
9
|
+
# Validates string values with optional constraints for length, pattern matching,
|
10
|
+
# and format validation (email, URI, etc.).
|
11
|
+
#
|
12
|
+
# @example Basic string
|
13
|
+
# RapiTapir::Types.string
|
14
|
+
#
|
15
|
+
# @example String with constraints
|
16
|
+
# RapiTapir::Types.string(min_length: 1, max_length: 255, format: :email)
|
17
|
+
class String < Base
|
18
|
+
def initialize(min_length: nil, max_length: nil, pattern: nil, format: nil, **options)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def validate_type(value)
|
25
|
+
return ["Expected string, got #{value.class}"] unless value.is_a?(::String)
|
26
|
+
|
27
|
+
[]
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate_constraints(value)
|
31
|
+
errors = []
|
32
|
+
|
33
|
+
errors.concat(validate_length_constraints(value))
|
34
|
+
errors.concat(validate_pattern_constraint(value))
|
35
|
+
errors.concat(validate_format_constraint(value))
|
36
|
+
|
37
|
+
errors
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_length_constraints(value)
|
41
|
+
errors = []
|
42
|
+
|
43
|
+
errors.concat(validate_min_length_constraint(value))
|
44
|
+
errors.concat(validate_max_length_constraint(value))
|
45
|
+
|
46
|
+
errors
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_min_length_constraint(value)
|
50
|
+
return [] unless constraints[:min_length] && value.length < constraints[:min_length]
|
51
|
+
|
52
|
+
["String length #{value.length} is below minimum #{constraints[:min_length]}"]
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_max_length_constraint(value)
|
56
|
+
return [] unless constraints[:max_length] && value.length > constraints[:max_length]
|
57
|
+
|
58
|
+
["String length #{value.length} exceeds maximum #{constraints[:max_length]}"]
|
59
|
+
end
|
60
|
+
|
61
|
+
def validate_pattern_constraint(value)
|
62
|
+
return [] unless constraints[:pattern] && !constraints[:pattern].match?(value)
|
63
|
+
|
64
|
+
["String '#{value}' does not match pattern #{constraints[:pattern].inspect}"]
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_format_constraint(value)
|
68
|
+
return [] unless constraints[:format]
|
69
|
+
|
70
|
+
validate_format(value, constraints[:format])
|
71
|
+
end
|
72
|
+
|
73
|
+
def coerce_value(value)
|
74
|
+
case value
|
75
|
+
when ::String then value
|
76
|
+
when Symbol, Numeric then value.to_s
|
77
|
+
else
|
78
|
+
raise CoercionError.new(value, 'String', 'Value does not respond to to_s') unless value.respond_to?(:to_s)
|
79
|
+
|
80
|
+
value.to_s
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def json_type
|
86
|
+
'string'
|
87
|
+
end
|
88
|
+
|
89
|
+
def apply_constraints_to_schema(schema)
|
90
|
+
super
|
91
|
+
apply_length_constraints_to_schema(schema)
|
92
|
+
apply_pattern_and_format_constraints_to_schema(schema)
|
93
|
+
end
|
94
|
+
|
95
|
+
def apply_length_constraints_to_schema(schema)
|
96
|
+
schema[:minLength] = constraints[:min_length] if constraints[:min_length]
|
97
|
+
schema[:maxLength] = constraints[:max_length] if constraints[:max_length]
|
98
|
+
end
|
99
|
+
|
100
|
+
def apply_pattern_and_format_constraints_to_schema(schema)
|
101
|
+
schema[:pattern] = constraints[:pattern].source if constraints[:pattern]
|
102
|
+
schema[:format] = constraints[:format].to_s if constraints[:format]
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def validate_format(value, format)
|
108
|
+
case format
|
109
|
+
when :email
|
110
|
+
validate_email_format(value)
|
111
|
+
when :uri, :url
|
112
|
+
validate_uri_format(value)
|
113
|
+
when :uuid
|
114
|
+
validate_uuid_format(value)
|
115
|
+
else
|
116
|
+
validate_datetime_and_ip_formats(value, format)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def validate_datetime_and_ip_formats(value, format)
|
121
|
+
case format
|
122
|
+
when :date
|
123
|
+
validate_date_format(value)
|
124
|
+
when :datetime, :'date-time'
|
125
|
+
validate_datetime_format(value)
|
126
|
+
when :ipv4
|
127
|
+
validate_ipv4_format(value)
|
128
|
+
when :ipv6
|
129
|
+
validate_ipv6_format(value)
|
130
|
+
else
|
131
|
+
[]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def validate_email_format(value)
|
136
|
+
# Basic email validation - in production, consider using a more robust library
|
137
|
+
email_pattern = /\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i
|
138
|
+
email_pattern.match?(value) ? [] : ['Invalid email format']
|
139
|
+
end
|
140
|
+
|
141
|
+
def validate_uri_format(value)
|
142
|
+
require 'uri'
|
143
|
+
URI.parse(value)
|
144
|
+
[]
|
145
|
+
rescue URI::InvalidURIError
|
146
|
+
['Invalid URI format']
|
147
|
+
end
|
148
|
+
|
149
|
+
def validate_uuid_format(value)
|
150
|
+
uuid_pattern = /\A[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i
|
151
|
+
uuid_pattern.match?(value) ? [] : ['Invalid UUID format']
|
152
|
+
end
|
153
|
+
|
154
|
+
def validate_date_format(value)
|
155
|
+
require 'date'
|
156
|
+
::Date.parse(value)
|
157
|
+
[]
|
158
|
+
rescue ArgumentError
|
159
|
+
['Invalid date format']
|
160
|
+
end
|
161
|
+
|
162
|
+
def validate_datetime_format(value)
|
163
|
+
require 'date'
|
164
|
+
::DateTime.parse(value)
|
165
|
+
[]
|
166
|
+
rescue ArgumentError
|
167
|
+
['Invalid datetime format']
|
168
|
+
end
|
169
|
+
|
170
|
+
def validate_ipv4_format(value)
|
171
|
+
ipv4_pattern = /\A(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\z/
|
172
|
+
ipv4_pattern.match?(value) ? [] : ['Invalid IPv4 format']
|
173
|
+
end
|
174
|
+
|
175
|
+
def validate_ipv6_format(value)
|
176
|
+
# Simplified IPv6 validation - in production, consider using a more robust library
|
177
|
+
require 'ipaddr'
|
178
|
+
IPAddr.new(value, Socket::AF_INET6)
|
179
|
+
[]
|
180
|
+
rescue IPAddr::InvalidAddressError
|
181
|
+
['Invalid IPv6 format']
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'string'
|
4
|
+
|
5
|
+
module RapiTapir
|
6
|
+
module Types
|
7
|
+
# UUID type for validating UUID (Universally Unique Identifier) strings
|
8
|
+
# Extends String type with UUID format validation
|
9
|
+
class UUID < String
|
10
|
+
UUID_PATTERN = /\A[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i
|
11
|
+
|
12
|
+
def initialize(**options)
|
13
|
+
super(pattern: UUID_PATTERN, **options)
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def validate_type(value)
|
19
|
+
return ["Expected string, got #{value.class}"] unless value.is_a?(::String)
|
20
|
+
return ['Invalid UUID format'] unless UUID_PATTERN.match?(value)
|
21
|
+
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
|
25
|
+
def apply_constraints_to_schema(schema)
|
26
|
+
super
|
27
|
+
schema[:format] = 'uuid'
|
28
|
+
schema[:pattern] = UUID_PATTERN.source
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'types/base'
|
4
|
+
require_relative 'types/string'
|
5
|
+
require_relative 'types/integer'
|
6
|
+
require_relative 'types/float'
|
7
|
+
require_relative 'types/boolean'
|
8
|
+
require_relative 'types/date'
|
9
|
+
require_relative 'types/datetime'
|
10
|
+
require_relative 'types/uuid'
|
11
|
+
require_relative 'types/email'
|
12
|
+
require_relative 'types/array'
|
13
|
+
require_relative 'types/hash'
|
14
|
+
require_relative 'types/optional'
|
15
|
+
require_relative 'types/object'
|
16
|
+
|
17
|
+
module RapiTapir
|
18
|
+
# Type system for RapiTapir providing validation, coercion, and schema generation
|
19
|
+
#
|
20
|
+
# Provides a comprehensive type system with built-in types for strings, integers,
|
21
|
+
# floats, booleans, dates, arrays, hashes, and objects. Supports validation,
|
22
|
+
# type coercion, constraint checking, and OpenAPI schema generation.
|
23
|
+
#
|
24
|
+
# @example Basic types
|
25
|
+
# RapiTapir::Types.string(min_length: 1, max_length: 255)
|
26
|
+
# RapiTapir::Types.integer(minimum: 0, maximum: 100)
|
27
|
+
# RapiTapir::Types.array(RapiTapir::Types.string)
|
28
|
+
#
|
29
|
+
# @example Custom objects
|
30
|
+
# user_type = RapiTapir::Types.object do
|
31
|
+
# field :id, RapiTapir::Types.integer
|
32
|
+
# field :name, RapiTapir::Types.string
|
33
|
+
# end
|
34
|
+
module Types
|
35
|
+
# Error raised when type validation fails
|
36
|
+
#
|
37
|
+
# Contains information about the validation failure including the
|
38
|
+
# invalid value, expected type, and specific error details.
|
39
|
+
class ValidationError < StandardError
|
40
|
+
attr_reader :value, :type, :errors
|
41
|
+
|
42
|
+
def initialize(value, type, errors = [])
|
43
|
+
@value = value
|
44
|
+
@type = type
|
45
|
+
@errors = errors
|
46
|
+
super(build_message)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def build_message
|
52
|
+
begin
|
53
|
+
type_name = type.to_s
|
54
|
+
rescue StandardError
|
55
|
+
type_name = type.class.name
|
56
|
+
end
|
57
|
+
base = "Validation failed for value #{value.inspect} against type #{type_name}"
|
58
|
+
return base if errors.empty?
|
59
|
+
|
60
|
+
"#{base}:\n#{errors.map { |error| " - #{error}" }.join("\n")}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Error raised when type coercion fails
|
65
|
+
#
|
66
|
+
# Occurs when a value cannot be automatically converted to the expected type,
|
67
|
+
# such as trying to coerce a non-numeric string to an integer.
|
68
|
+
class CoercionError < StandardError
|
69
|
+
attr_reader :value, :type, :reason
|
70
|
+
|
71
|
+
def initialize(value, type, reason = nil)
|
72
|
+
@value = value
|
73
|
+
@type = type
|
74
|
+
@reason = reason
|
75
|
+
super(build_message)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def build_message
|
81
|
+
base = "Cannot coerce #{value.inspect} to #{type}"
|
82
|
+
reason ? "#{base}: #{reason}" : base
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Convenience methods for creating types
|
87
|
+
def self.string(**options)
|
88
|
+
String.new(**options)
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.integer(**options)
|
92
|
+
Integer.new(**options)
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.float(**options)
|
96
|
+
Float.new(**options)
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.boolean
|
100
|
+
Boolean.new
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.date(**options)
|
104
|
+
Date.new(**options)
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.datetime(**options)
|
108
|
+
DateTime.new(**options)
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.uuid
|
112
|
+
UUID.new
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.email
|
116
|
+
Email.new
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.array(item_type, **options)
|
120
|
+
Array.new(item_type, **options)
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.hash(field_types = {}, **options)
|
124
|
+
Hash.new(field_types, **options)
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.optional(type)
|
128
|
+
Optional.new(type)
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.object(&block)
|
132
|
+
Object.new(&block)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Auto-derivation convenience methods
|
136
|
+
def self.from_hash(hash, **options)
|
137
|
+
Types::AutoDerivation.from_hash(hash, **options)
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.from_protobuf(proto_class, **options)
|
141
|
+
Types::AutoDerivation.from_protobuf(proto_class, **options)
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.from_json_schema(json_schema, **options)
|
145
|
+
Types::AutoDerivation.from_json_schema(json_schema, **options)
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.from_open_struct(open_struct, **options)
|
149
|
+
Types::AutoDerivation.from_open_struct(open_struct, **options)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Load auto-derivation after Types module is fully defined
|
155
|
+
require_relative 'types/auto_derivation'
|
data/lib/rapitapir.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'rapitapir/version'
|
4
|
+
require_relative 'rapitapir/types'
|
5
|
+
require_relative 'rapitapir/schema'
|
6
|
+
require_relative 'rapitapir/core/endpoint'
|
7
|
+
require_relative 'rapitapir/core/input'
|
8
|
+
require_relative 'rapitapir/core/output'
|
9
|
+
require_relative 'rapitapir/core/request'
|
10
|
+
require_relative 'rapitapir/core/response'
|
11
|
+
require_relative 'rapitapir/dsl/endpoint_dsl'
|
12
|
+
|
13
|
+
# Enhanced components for Phase 1
|
14
|
+
require_relative 'rapitapir/core/enhanced_endpoint'
|
15
|
+
require_relative 'rapitapir/dsl/enhanced_endpoint_dsl'
|
16
|
+
require_relative 'rapitapir/server/enhanced_rack_adapter'
|
17
|
+
require_relative 'rapitapir/server/sinatra_integration'
|
18
|
+
require_relative 'rapitapir/dsl/fluent_dsl'
|
19
|
+
require_relative 'rapitapir/dsl/http_verbs'
|
20
|
+
|
21
|
+
# Observability components (Phase 2.1)
|
22
|
+
require_relative 'rapitapir/observability'
|
23
|
+
|
24
|
+
# Authentication & Security components (Phase 2.2)
|
25
|
+
require_relative 'rapitapir/auth'
|
26
|
+
|
27
|
+
# Server components (optional, only load if needed)
|
28
|
+
begin
|
29
|
+
require_relative 'rapitapir/server/rack_adapter'
|
30
|
+
require_relative 'rapitapir/server/path_matcher'
|
31
|
+
require_relative 'rapitapir/server/middleware'
|
32
|
+
rescue LoadError
|
33
|
+
# Server dependencies not available
|
34
|
+
end
|
35
|
+
|
36
|
+
# Framework adapters (optional, only load if framework is available)
|
37
|
+
begin
|
38
|
+
if defined?(Sinatra)
|
39
|
+
require_relative 'rapitapir/server/sinatra_adapter'
|
40
|
+
require_relative 'rapitapir/sinatra_rapitapir'
|
41
|
+
end
|
42
|
+
rescue LoadError
|
43
|
+
# Sinatra not available
|
44
|
+
end
|
45
|
+
|
46
|
+
begin
|
47
|
+
require_relative 'rapitapir/server/rails_adapter' if defined?(Rails)
|
48
|
+
rescue LoadError
|
49
|
+
# Rails not available
|
50
|
+
end
|
51
|
+
|
52
|
+
# OpenAPI and client generation (optional)
|
53
|
+
begin
|
54
|
+
require_relative 'rapitapir/openapi/schema_generator'
|
55
|
+
require_relative 'rapitapir/client/generator_base'
|
56
|
+
require_relative 'rapitapir/client/typescript_generator'
|
57
|
+
rescue LoadError
|
58
|
+
# OpenAPI or client generation dependencies not available
|
59
|
+
end
|
60
|
+
|
61
|
+
# Documentation and CLI tools (optional)
|
62
|
+
begin
|
63
|
+
require_relative 'rapitapir/docs/markdown_generator'
|
64
|
+
require_relative 'rapitapir/docs/html_generator'
|
65
|
+
require_relative 'rapitapir/cli/command'
|
66
|
+
require_relative 'rapitapir/cli/server'
|
67
|
+
require_relative 'rapitapir/cli/validator'
|
68
|
+
rescue LoadError
|
69
|
+
# Documentation or CLI dependencies not available
|
70
|
+
end
|
71
|
+
|
72
|
+
# RapiTapir - A Ruby library for defining HTTP APIs declaratively
|
73
|
+
#
|
74
|
+
# Inspired by Scala's Tapir, this library provides type-safe input/output definitions,
|
75
|
+
# automatic OpenAPI documentation generation, and seamless integration with multiple Ruby stacks.
|
76
|
+
#
|
77
|
+
# @example Basic usage
|
78
|
+
# endpoint = RapiTapir.get('/users/{id}')
|
79
|
+
# .path_param(:id, :integer)
|
80
|
+
# .ok(:json, { id: :integer, name: :string })
|
81
|
+
# .build
|
82
|
+
#
|
83
|
+
# @example Enhanced DSL usage
|
84
|
+
# include RapiTapir::DSL::HTTPVerbs
|
85
|
+
# endpoint = GET('/users/{id}')
|
86
|
+
# .path_param(:id, :integer)
|
87
|
+
# .ok(:json, { id: :integer, name: :string })
|
88
|
+
# .build
|
89
|
+
#
|
90
|
+
# @see https://github.com/riccardomerolla/rapitapir
|
91
|
+
module RapiTapir
|
92
|
+
# Will be extended with FluentDSL later
|
93
|
+
@endpoints = []
|
94
|
+
|
95
|
+
# Extend the module with HTTP verbs for global access
|
96
|
+
extend DSL::HTTPVerbs
|
97
|
+
|
98
|
+
def self.endpoints
|
99
|
+
@endpoints
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.register_endpoint(endpoint)
|
103
|
+
@endpoints << endpoint
|
104
|
+
endpoint
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.clear_endpoints
|
108
|
+
@endpoints.clear
|
109
|
+
end
|
110
|
+
|
111
|
+
# Observability configuration
|
112
|
+
def self.configure
|
113
|
+
yield(Observability.configuration) if block_given?
|
114
|
+
|
115
|
+
# Initialize observability components after configuration
|
116
|
+
configure_metrics
|
117
|
+
configure_tracing
|
118
|
+
configure_logging
|
119
|
+
configure_health_check
|
120
|
+
end
|
121
|
+
|
122
|
+
private_class_method def self.configure_metrics
|
123
|
+
return unless Observability.config.metrics.enabled
|
124
|
+
|
125
|
+
Observability::Metrics.configure(
|
126
|
+
provider: Observability.config.metrics.provider,
|
127
|
+
namespace: Observability.config.metrics.namespace,
|
128
|
+
custom_labels: Observability.config.metrics.custom_labels
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
private_class_method def self.configure_tracing
|
133
|
+
return unless Observability.config.tracing.enabled
|
134
|
+
|
135
|
+
Observability::Tracing.configure(
|
136
|
+
service_name: Observability.config.tracing.service_name,
|
137
|
+
service_version: Observability.config.tracing.service_version,
|
138
|
+
provider: Observability.config.tracing.provider
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
private_class_method def self.configure_logging
|
143
|
+
return unless Observability.config.logging.enabled
|
144
|
+
|
145
|
+
Observability::Logging.configure(
|
146
|
+
level: Observability.config.logging.level,
|
147
|
+
format: Observability.config.logging.format,
|
148
|
+
structured: Observability.config.logging.structured
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
private_class_method def self.configure_health_check
|
153
|
+
return unless Observability.config.health_check.enabled
|
154
|
+
|
155
|
+
Observability::HealthCheck.configure(
|
156
|
+
endpoint: Observability.config.health_check.endpoint
|
157
|
+
)
|
158
|
+
|
159
|
+
# Register custom health checks
|
160
|
+
Observability.config.health_check.checks.each do |check|
|
161
|
+
Observability::HealthCheck.register(check[:name], &check[:check])
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Convenience methods for creating endpoints (will be replaced by FluentDSL)
|
166
|
+
def self.endpoint
|
167
|
+
Core::Endpoint.new
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Convenience constant for cleaner type syntax
|
172
|
+
# Users can use T instead of RapiTapir::Types for shorter, more readable code
|
173
|
+
T = RapiTapir::Types
|
data/rapitapir.gemspec
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/rapitapir/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'rapitapir'
|
7
|
+
spec.version = RapiTapir::VERSION
|
8
|
+
spec.authors = ['Riccardo Merolla']
|
9
|
+
spec.email = ['riccardo.merolla@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Type-safe HTTP API development for Ruby'
|
12
|
+
spec.description = <<~DESC
|
13
|
+
RapiTapir is a Ruby library inspired by Scala's Tapir for building type-safe HTTP APIs.#{' '}
|
14
|
+
It provides declarative endpoint definitions, automatic OpenAPI documentation generation,#{' '}
|
15
|
+
client code generation, and seamless integration with Sinatra, Rails, and Rack applications.
|
16
|
+
DESC
|
17
|
+
|
18
|
+
spec.homepage = 'https://riccardomerolla.github.io/rapitapir/'
|
19
|
+
spec.license = 'MIT'
|
20
|
+
spec.required_ruby_version = '>= 3.1.0'
|
21
|
+
|
22
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
23
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
24
|
+
spec.metadata['source_code_uri'] = 'https://github.com/riccardomerolla/rapitapir'
|
25
|
+
spec.metadata['changelog_uri'] = 'https://github.com/riccardomerolla/rapitapir/blob/main/CHANGELOG.md'
|
26
|
+
spec.metadata['documentation_uri'] = 'https://riccardomerolla.github.io/rapitapir/docs'
|
27
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/riccardomerolla/rapitapir/issues'
|
28
|
+
|
29
|
+
# Specify which files should be added to the gem when it is released.
|
30
|
+
spec.files = Dir.chdir(__dir__) do
|
31
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
32
|
+
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[
|
33
|
+
bin/ test/ spec/ features/ .git .github appveyor Gemfile
|
34
|
+
])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
spec.bindir = 'exe'
|
39
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
40
|
+
spec.require_paths = ['lib']
|
41
|
+
|
42
|
+
# Runtime dependencies
|
43
|
+
spec.add_dependency 'json', '~> 2.0'
|
44
|
+
spec.add_dependency 'rack', '~> 2.0', '>= 2.0'
|
45
|
+
|
46
|
+
# Optional framework dependencies
|
47
|
+
spec.add_dependency 'sinatra', '>= 2.0', '< 4.0'
|
48
|
+
|
49
|
+
# Development dependencies
|
50
|
+
spec.add_development_dependency 'rack-test', '~> 2.0'
|
51
|
+
spec.add_development_dependency 'rspec', '~> 3.12'
|
52
|
+
spec.add_development_dependency 'simplecov', '~> 0.22'
|
53
|
+
spec.add_development_dependency 'webrick', '~> 1.8'
|
54
|
+
|
55
|
+
# Documentation
|
56
|
+
spec.add_development_dependency 'redcarpet', '~> 3.6'
|
57
|
+
spec.add_development_dependency 'yard', '~> 0.9'
|
58
|
+
|
59
|
+
# Code quality
|
60
|
+
spec.add_development_dependency 'rubocop', '~> 1.50'
|
61
|
+
spec.add_development_dependency 'rubocop-performance', '~> 1.18'
|
62
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 2.20'
|
63
|
+
|
64
|
+
# For more information and examples about making a new gem, check out our
|
65
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
66
|
+
end
|