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,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'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RapiTapir
4
+ VERSION = '0.1.0'
5
+ end
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