zero_ruby 0.1.0.alpha2 → 0.1.0.alpha5

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +120 -71
  3. data/lib/zero_ruby/configuration.rb +0 -5
  4. data/lib/zero_ruby/error_formatter.rb +173 -0
  5. data/lib/zero_ruby/errors.rb +10 -1
  6. data/lib/zero_ruby/input_object.rb +55 -93
  7. data/lib/zero_ruby/lmid_stores/active_record_store.rb +0 -1
  8. data/lib/zero_ruby/mutation.rb +235 -80
  9. data/lib/zero_ruby/push_processor.rb +54 -35
  10. data/lib/zero_ruby/schema.rb +38 -14
  11. data/lib/zero_ruby/type_names.rb +7 -15
  12. data/lib/zero_ruby/types.rb +52 -0
  13. data/lib/zero_ruby/typescript_generator.rb +167 -57
  14. data/lib/zero_ruby/version.rb +1 -1
  15. data/lib/zero_ruby.rb +12 -35
  16. metadata +46 -20
  17. data/lib/zero_ruby/argument.rb +0 -75
  18. data/lib/zero_ruby/types/base_type.rb +0 -54
  19. data/lib/zero_ruby/types/big_int.rb +0 -32
  20. data/lib/zero_ruby/types/boolean.rb +0 -30
  21. data/lib/zero_ruby/types/float.rb +0 -31
  22. data/lib/zero_ruby/types/id.rb +0 -33
  23. data/lib/zero_ruby/types/integer.rb +0 -31
  24. data/lib/zero_ruby/types/iso8601_date.rb +0 -43
  25. data/lib/zero_ruby/types/iso8601_date_time.rb +0 -43
  26. data/lib/zero_ruby/types/string.rb +0 -20
  27. data/lib/zero_ruby/validator.rb +0 -69
  28. data/lib/zero_ruby/validators/allow_blank_validator.rb +0 -31
  29. data/lib/zero_ruby/validators/allow_null_validator.rb +0 -26
  30. data/lib/zero_ruby/validators/exclusion_validator.rb +0 -29
  31. data/lib/zero_ruby/validators/format_validator.rb +0 -35
  32. data/lib/zero_ruby/validators/inclusion_validator.rb +0 -30
  33. data/lib/zero_ruby/validators/length_validator.rb +0 -42
  34. data/lib/zero_ruby/validators/numericality_validator.rb +0 -63
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-types"
4
+
5
+ module ZeroRuby
6
+ # Type definitions using dry-types.
7
+ #
8
+ # When inheriting from ZeroRuby::Mutation or ZeroRuby::InputObject, types are
9
+ # available via the Types module (e.g., Types::String, Types::ID).
10
+ #
11
+ # @example Basic usage
12
+ # class MyMutation < ZeroRuby::Mutation
13
+ # argument :id, Types::ID
14
+ # argument :name, Types::String
15
+ # argument :count, Types::Integer.optional
16
+ # argument :active, Types::Boolean.default(false)
17
+ # end
18
+ #
19
+ # @example With constraints
20
+ # class MyMutation < ZeroRuby::Mutation
21
+ # argument :title, Types::String.constrained(min_size: 1, max_size: 200)
22
+ # argument :count, Types::Integer.constrained(gt: 0)
23
+ # argument :status, Types::String.constrained(included_in: %w[draft published])
24
+ # end
25
+ module Types
26
+ include Dry.Types()
27
+
28
+ # Params types for JSON input
29
+ # These handle string coercions common in form/JSON data
30
+
31
+ # String type (passes through strings, coerces nil)
32
+ String = Params::String
33
+
34
+ # Coerces string numbers to integers (e.g., "42" -> 42)
35
+ Integer = Params::Integer
36
+
37
+ # Coerces string numbers to floats (e.g., "3.14" -> 3.14)
38
+ Float = Params::Float
39
+
40
+ # Coerces string booleans (e.g., "true" -> true, "false" -> false)
41
+ Boolean = Params::Bool
42
+
43
+ # Non-empty string ID type
44
+ ID = Params::String.constrained(filled: true)
45
+
46
+ # ISO8601 date string -> Date object
47
+ ISO8601Date = Params::Date
48
+
49
+ # ISO8601 datetime string -> DateTime object
50
+ ISO8601DateTime = Params::DateTime
51
+ end
52
+ end
@@ -9,15 +9,16 @@ module ZeroRuby
9
9
  # TypeScriptGenerator.new(ZeroSchema).generate
10
10
  # # => "// Auto-generated by zero-ruby - do not edit\n\nexport interface ..."
11
11
  class TypeScriptGenerator
12
- TYPE_MAP = {
13
- "ZeroRuby::Types::String" => "string",
14
- "ZeroRuby::Types::Integer" => "number",
15
- "ZeroRuby::Types::Float" => "number",
16
- "ZeroRuby::Types::Boolean" => "boolean",
17
- "ZeroRuby::Types::ID" => "string",
18
- "ZeroRuby::Types::BigInt" => "number",
19
- "ZeroRuby::Types::ISO8601Date" => "string",
20
- "ZeroRuby::Types::ISO8601DateTime" => "string"
12
+ # Map Ruby primitives to TypeScript types
13
+ PRIMITIVE_MAP = {
14
+ String => "string",
15
+ Integer => "number",
16
+ Float => "number",
17
+ TrueClass => "boolean",
18
+ FalseClass => "boolean",
19
+ Date => "string",
20
+ DateTime => "string",
21
+ Time => "string"
21
22
  }.freeze
22
23
 
23
24
  def initialize(schema)
@@ -32,7 +33,6 @@ module ZeroRuby
32
33
 
33
34
  parts = [
34
35
  generate_header,
35
- generate_scalars,
36
36
  generate_input_objects,
37
37
  generate_mutation_args,
38
38
  generate_mutation_map
@@ -46,20 +46,6 @@ module ZeroRuby
46
46
  def generate_header
47
47
  <<~TS
48
48
  // Auto-generated by zero-ruby - do not edit
49
- // Generated at: #{Time.now.utc.iso8601}
50
- TS
51
- end
52
-
53
- def generate_scalars
54
- <<~TS
55
-
56
- /** Scalar type mappings */
57
- export type Scalars = {
58
- String: string;
59
- Integer: number;
60
- Float: number;
61
- Boolean: boolean;
62
- };
63
49
  TS
64
50
  end
65
51
 
@@ -70,13 +56,31 @@ module ZeroRuby
70
56
  end
71
57
 
72
58
  def collect_input_objects_from_arguments(arguments)
73
- arguments.each do |_name, arg|
74
- if input_object_type?(arg.type)
75
- type_name = extract_type_name(arg.type)
59
+ arguments.each do |_name, config|
60
+ type = config[:type]
61
+
62
+ # Unwrap optional types first (Sum type: NilClass | actual_type)
63
+ if sum_type?(type)
64
+ inner = extract_non_nil_type(type)
65
+ type = inner if inner
66
+ end
67
+
68
+ # Then unwrap array types to check element type for InputObjects
69
+ if array_type?(type)
70
+ type = type.member
71
+ end
72
+
73
+ if input_object_type?(type)
74
+ type_name = extract_type_name(type)
76
75
  unless @input_objects.key?(type_name)
77
- @input_objects[type_name] = arg.type
78
- # Recursively collect nested InputObjects
79
- collect_input_objects_from_arguments(arg.type.arguments)
76
+ @input_objects[type_name] = type
77
+ # Recursively collect nested InputObjects from Dry::Struct schema
78
+ if type.respond_to?(:schema)
79
+ nested_args = type.schema.keys.each_with_object({}) do |key, hash|
80
+ hash[key.name] = {type: key.type, name: key.name}
81
+ end
82
+ collect_input_objects_from_arguments(nested_args)
83
+ end
80
84
  end
81
85
  end
82
86
  end
@@ -86,36 +90,57 @@ module ZeroRuby
86
90
  return nil if @input_objects.empty?
87
91
 
88
92
  interfaces = @input_objects.map do |name, klass|
89
- generate_interface(name, klass.arguments)
93
+ generate_input_object_interface(name, klass)
90
94
  end
91
95
 
92
96
  "\n" + interfaces.join("\n\n")
93
97
  end
94
98
 
99
+ def generate_input_object_interface(name, klass)
100
+ return empty_interface(name) unless klass.respond_to?(:schema)
101
+
102
+ keys = klass.schema.keys
103
+ return empty_interface(name) if keys.empty?
104
+
105
+ fields = keys.map do |key|
106
+ # Check if the type itself is optional (not just the key)
107
+ optional = optional_type?(key.type)
108
+ optional_mark = optional ? "?" : ""
109
+ ts_type = resolve_type(key.type)
110
+ ts_type = "#{ts_type} | null" if optional
111
+ " #{to_camel_case(key.name)}#{optional_mark}: #{ts_type};"
112
+ end
113
+
114
+ <<~TS.strip
115
+ /** #{name} */
116
+ export interface #{name} {
117
+ #{fields.join("\n")}
118
+ }
119
+ TS
120
+ end
121
+
95
122
  def generate_mutation_args
96
123
  return nil if @schema.mutations.empty?
97
124
 
98
125
  interfaces = @schema.mutations.map do |name, handler_class|
99
126
  interface_name = to_interface_name(name)
100
- generate_interface(interface_name, handler_class.arguments)
127
+ generate_mutation_interface(interface_name, handler_class.arguments)
101
128
  end
102
129
 
103
130
  "\n" + interfaces.join("\n\n")
104
131
  end
105
132
 
106
- def generate_interface(name, arguments)
107
- if arguments.empty?
108
- return <<~TS.strip
109
- /** #{name} */
110
- export interface #{name} {}
111
- TS
112
- end
113
-
114
- fields = arguments.map do |arg_name, arg|
115
- optional = arg.optional? ? "?" : ""
116
- ts_type = resolve_type(arg.type)
117
- description = arg.description ? " /** #{arg.description} */\n" : ""
118
- "#{description} #{to_camel_case(arg_name)}#{optional}: #{ts_type};"
133
+ def generate_mutation_interface(name, arguments)
134
+ return empty_interface(name) if arguments.empty?
135
+
136
+ fields = arguments.map do |arg_name, config|
137
+ type = config[:type]
138
+ optional = optional_type?(type)
139
+ optional_mark = optional ? "?" : ""
140
+ ts_type = resolve_type(type)
141
+ ts_type = "#{ts_type} | null" if optional
142
+ description = config[:description] ? " /** #{config[:description]} */\n" : ""
143
+ "#{description} #{to_camel_case(arg_name)}#{optional_mark}: #{ts_type};"
119
144
  end
120
145
 
121
146
  <<~TS.strip
@@ -126,6 +151,13 @@ module ZeroRuby
126
151
  TS
127
152
  end
128
153
 
154
+ def empty_interface(name)
155
+ <<~TS.strip
156
+ /** #{name} */
157
+ export interface #{name} {}
158
+ TS
159
+ end
160
+
129
161
  def generate_mutation_map
130
162
  return nil if @schema.mutations.empty?
131
163
 
@@ -140,27 +172,105 @@ module ZeroRuby
140
172
  export interface MutationArgs {
141
173
  #{entries.join("\n")}
142
174
  }
143
-
144
- export type MutationName = keyof MutationArgs;
145
- export type ArgsFor<T extends MutationName> = MutationArgs[T];
146
175
  TS
147
176
  end
148
177
 
149
178
  def resolve_type(type)
150
- type_key = type.to_s
179
+ # Handle InputObject classes
180
+ if input_object_type?(type)
181
+ return extract_type_name(type)
182
+ end
183
+
184
+ # Handle Array types
185
+ if array_type?(type)
186
+ element_type = type.member
187
+ return "#{resolve_type(element_type)}[]"
188
+ end
151
189
 
152
- if TYPE_MAP.key?(type_key)
153
- TYPE_MAP[type_key]
154
- elsif input_object_type?(type)
155
- extract_type_name(type)
156
- else
157
- # Unknown type, default to unknown
158
- "unknown"
190
+ # Handle optional types (Sum of NilClass and another type)
191
+ # This must come before unwrap_primitive to handle optional arrays
192
+ if sum_type?(type)
193
+ inner = extract_non_nil_type(type)
194
+ return resolve_type(inner) if inner
159
195
  end
196
+
197
+ # Unwrap the dry-type to get the primitive
198
+ primitive = unwrap_primitive(type)
199
+
200
+ # Check if it's a nested InputObject after unwrapping
201
+ if input_object_type?(primitive)
202
+ return extract_type_name(primitive)
203
+ end
204
+
205
+ # Map primitive to TypeScript
206
+ PRIMITIVE_MAP[primitive] || raise(
207
+ ArgumentError,
208
+ "Cannot map type #{type.inspect} (primitive: #{primitive.inspect}) to TypeScript. " \
209
+ "Supported primitives: #{PRIMITIVE_MAP.keys.map(&:name).join(", ")}"
210
+ )
211
+ end
212
+
213
+ # Unwrap dry-types wrappers to get the underlying primitive
214
+ def unwrap_primitive(type)
215
+ # Check for Sum type (optional = NilClass | type)
216
+ # For optional types, left is NilClass, right is the actual type
217
+ if type.respond_to?(:left) && type.respond_to?(:right)
218
+ left_primitive = unwrap_primitive(type.left)
219
+ right_primitive = unwrap_primitive(type.right)
220
+ # Return the non-NilClass side
221
+ return right_primitive if left_primitive == NilClass
222
+ return left_primitive if right_primitive == NilClass
223
+ # If neither is NilClass, return left (shouldn't happen for optional)
224
+ return left_primitive
225
+ end
226
+
227
+ # Try to get the primitive directly
228
+ if type.respond_to?(:primitive)
229
+ return type.primitive
230
+ end
231
+
232
+ # Check for Constrained type
233
+ if type.respond_to?(:type) && type.class.name&.include?("Constrained")
234
+ return unwrap_primitive(type.type)
235
+ end
236
+
237
+ # Check for Default type
238
+ if type.respond_to?(:type) && type.class.name&.include?("Default")
239
+ return unwrap_primitive(type.type)
240
+ end
241
+
242
+ # Check for other wrappers with .type method
243
+ if type.respond_to?(:type)
244
+ return unwrap_primitive(type.type)
245
+ end
246
+
247
+ type
248
+ end
249
+
250
+ def optional_type?(type)
251
+ return false unless type.respond_to?(:optional?)
252
+ type.optional? || (type.respond_to?(:default?) && type.default?)
160
253
  end
161
254
 
162
255
  def input_object_type?(type)
163
- defined?(ZeroRuby::InputObject) && type.is_a?(Class) && type < ZeroRuby::InputObject
256
+ type.is_a?(Class) && type < ZeroRuby::InputObject
257
+ end
258
+
259
+ def array_type?(type)
260
+ type.respond_to?(:primitive) && type.primitive == Array
261
+ end
262
+
263
+ def sum_type?(type)
264
+ type.respond_to?(:left) && type.respond_to?(:right)
265
+ end
266
+
267
+ def extract_non_nil_type(type)
268
+ return nil unless sum_type?(type)
269
+ if type.left.respond_to?(:primitive) && type.left.primitive == NilClass
270
+ type.right
271
+ elsif type.right.respond_to?(:primitive) && type.right.primitive == NilClass
272
+ type.left
273
+ end
164
274
  end
165
275
 
166
276
  def extract_type_name(type)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZeroRuby
4
- VERSION = "0.1.0.alpha2"
4
+ VERSION = "0.1.0.alpha5"
5
5
  end
data/lib/zero_ruby.rb CHANGED
@@ -4,33 +4,13 @@ require_relative "zero_ruby/version"
4
4
  require_relative "zero_ruby/configuration"
5
5
  require_relative "zero_ruby/errors"
6
6
 
7
- # Types (must be loaded before InputObject/Mutation which use them)
8
- require_relative "zero_ruby/types/base_type"
9
- require_relative "zero_ruby/types/string"
10
- require_relative "zero_ruby/types/integer"
11
- require_relative "zero_ruby/types/float"
12
- require_relative "zero_ruby/types/boolean"
13
- require_relative "zero_ruby/types/id"
14
- require_relative "zero_ruby/types/big_int"
15
- require_relative "zero_ruby/types/iso8601_date"
16
- require_relative "zero_ruby/types/iso8601_date_time"
7
+ # Types module (dry-types based)
8
+ require_relative "zero_ruby/types"
17
9
 
18
- # Type name shortcuts (ID, Boolean, etc. without ZeroRuby::Types:: prefix)
19
- # Must be loaded before InputObject/Mutation which include this module
10
+ # Provides Types constant for accessing ZeroRuby::Types
20
11
  require_relative "zero_ruby/type_names"
21
12
 
22
- # Validators
23
- require_relative "zero_ruby/validators/length_validator"
24
- require_relative "zero_ruby/validators/numericality_validator"
25
- require_relative "zero_ruby/validators/format_validator"
26
- require_relative "zero_ruby/validators/inclusion_validator"
27
- require_relative "zero_ruby/validators/exclusion_validator"
28
- require_relative "zero_ruby/validators/allow_blank_validator"
29
- require_relative "zero_ruby/validators/allow_null_validator"
30
-
31
13
  # Core classes
32
- require_relative "zero_ruby/argument"
33
- require_relative "zero_ruby/validator"
34
14
  require_relative "zero_ruby/input_object"
35
15
  require_relative "zero_ruby/mutation"
36
16
  require_relative "zero_ruby/schema"
@@ -39,6 +19,7 @@ require_relative "zero_ruby/typescript_generator"
39
19
  # LMID (Last Mutation ID) Tracking
40
20
  require_relative "zero_ruby/lmid_store"
41
21
  require_relative "zero_ruby/lmid_stores/active_record_store"
22
+ require_relative "zero_ruby/zero_client"
42
23
  require_relative "zero_ruby/push_processor"
43
24
 
44
25
  # ZeroRuby - A Ruby gem for handling Zero mutations with type safety
@@ -46,19 +27,19 @@ require_relative "zero_ruby/push_processor"
46
27
  # @example Basic usage with InputObject
47
28
  # # Define an input type
48
29
  # class Types::PostInput < ZeroRuby::InputObject
49
- # argument :id, ID, required: true
50
- # argument :title, String, required: true
51
- # argument :body, String, required: false
30
+ # argument :id, ZeroRuby::Types::ID
31
+ # argument :title, ZeroRuby::Types::String.constrained(min_size: 1, max_size: 200)
32
+ # argument :body, ZeroRuby::Types::String.optional
52
33
  # end
53
34
  #
54
35
  # # Define a mutation
55
36
  # class Mutations::PostCreate < ZeroRuby::Mutation
56
- # argument :post_input, Types::PostInput, required: true
57
- # argument :notify, Boolean, required: false
37
+ # argument :post_input, Types::PostInput
38
+ # argument :notify, ZeroRuby::Types::Boolean.default(false)
58
39
  #
59
- # def execute(post_input:, notify: false)
60
- # Post.create!(**post_input)
61
- # notify_subscribers if notify
40
+ # def execute
41
+ # Post.create!(**args[:post_input])
42
+ # notify_subscribers if args[:notify]
62
43
  # end
63
44
  # end
64
45
  #
@@ -80,8 +61,4 @@ require_relative "zero_ruby/push_processor"
80
61
  # end
81
62
  # end
82
63
  module ZeroRuby
83
- # Convenience method for quick type access
84
- module Types
85
- # Already defined in individual type files
86
- end
87
64
  end
metadata CHANGED
@@ -1,14 +1,56 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zero_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha2
4
+ version: 0.1.0.alpha5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Serban
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-12-21 00:00:00.000000000 Z
10
+ date: 2026-01-01 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: dry-struct
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.6'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.6'
26
+ - !ruby/object:Gem::Dependency
27
+ name: dry-types
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.7'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.7'
40
+ - !ruby/object:Gem::Dependency
41
+ name: dry-validation
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.10'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.10'
12
54
  - !ruby/object:Gem::Dependency
13
55
  name: bundler
14
56
  requirement: !ruby/object:Gem::Requirement
@@ -88,8 +130,8 @@ files:
88
130
  - LICENSE.txt
89
131
  - README.md
90
132
  - lib/zero_ruby.rb
91
- - lib/zero_ruby/argument.rb
92
133
  - lib/zero_ruby/configuration.rb
134
+ - lib/zero_ruby/error_formatter.rb
93
135
  - lib/zero_ruby/errors.rb
94
136
  - lib/zero_ruby/input_object.rb
95
137
  - lib/zero_ruby/lmid_store.rb
@@ -98,24 +140,8 @@ files:
98
140
  - lib/zero_ruby/push_processor.rb
99
141
  - lib/zero_ruby/schema.rb
100
142
  - lib/zero_ruby/type_names.rb
101
- - lib/zero_ruby/types/base_type.rb
102
- - lib/zero_ruby/types/big_int.rb
103
- - lib/zero_ruby/types/boolean.rb
104
- - lib/zero_ruby/types/float.rb
105
- - lib/zero_ruby/types/id.rb
106
- - lib/zero_ruby/types/integer.rb
107
- - lib/zero_ruby/types/iso8601_date.rb
108
- - lib/zero_ruby/types/iso8601_date_time.rb
109
- - lib/zero_ruby/types/string.rb
143
+ - lib/zero_ruby/types.rb
110
144
  - lib/zero_ruby/typescript_generator.rb
111
- - lib/zero_ruby/validator.rb
112
- - lib/zero_ruby/validators/allow_blank_validator.rb
113
- - lib/zero_ruby/validators/allow_null_validator.rb
114
- - lib/zero_ruby/validators/exclusion_validator.rb
115
- - lib/zero_ruby/validators/format_validator.rb
116
- - lib/zero_ruby/validators/inclusion_validator.rb
117
- - lib/zero_ruby/validators/length_validator.rb
118
- - lib/zero_ruby/validators/numericality_validator.rb
119
145
  - lib/zero_ruby/version.rb
120
146
  - lib/zero_ruby/zero_client.rb
121
147
  homepage: https://github.com/alse/zero-ruby
@@ -1,75 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ZeroRuby
4
- # Represents a declared argument for a mutation.
5
- # Holds type, required status, and validation configuration.
6
- class Argument
7
- # Sentinel value to distinguish "no default provided" from "default is nil"
8
- NOT_PROVIDED = Object.new.freeze
9
-
10
- # Maps Ruby built-in classes to ZeroRuby types.
11
- # This allows using String, Integer, Float directly in argument declarations.
12
- RUBY_TYPE_MAP = {
13
- ::String => -> { ZeroRuby::Types::String },
14
- ::Integer => -> { ZeroRuby::Types::Integer },
15
- ::Float => -> { ZeroRuby::Types::Float }
16
- }.freeze
17
-
18
- attr_reader :name, :type, :required, :validators, :default, :description
19
-
20
- def initialize(name:, type:, required: true, validates: nil, default: NOT_PROVIDED, description: nil, **options)
21
- @name = name.to_sym
22
- @type = resolve_type(type)
23
- @required = required
24
- @validators = validates || {}
25
- @has_default = default != NOT_PROVIDED
26
- @default = @has_default ? default : nil
27
- @description = description
28
- @options = options
29
- end
30
-
31
- def required?
32
- @required
33
- end
34
-
35
- def optional?
36
- !@required
37
- end
38
-
39
- def has_default?
40
- @has_default
41
- end
42
-
43
- # Coerce and validate a raw input value
44
- # @param raw_value [Object] The raw input value
45
- # @param ctx [Hash] The context hash
46
- # @return [Object] The coerced value
47
- def coerce(raw_value, ctx = nil)
48
- value = (raw_value.nil? && has_default?) ? @default : raw_value
49
-
50
- # Handle InputObject types (they use .coerce instead of .coerce_input)
51
- if input_object_type?
52
- @type.coerce(value, ctx)
53
- else
54
- @type.coerce_input(value, ctx)
55
- end
56
- end
57
-
58
- private
59
-
60
- # Resolve a type reference to a ZeroRuby type.
61
- # Handles Ruby built-in classes (String, Integer, Float) by mapping them
62
- # to the corresponding ZeroRuby::Types class.
63
- def resolve_type(type)
64
- if RUBY_TYPE_MAP.key?(type)
65
- RUBY_TYPE_MAP[type].call
66
- else
67
- type
68
- end
69
- end
70
-
71
- def input_object_type?
72
- defined?(ZeroRuby::InputObject) && @type.is_a?(Class) && @type < ZeroRuby::InputObject
73
- end
74
- end
75
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ZeroRuby
4
- module Types
5
- # Base class for all types. Provides the interface for type coercion.
6
- class BaseType
7
- class << self
8
- def name
9
- raise NotImplementedError, "Subclasses must implement .name"
10
- end
11
-
12
- def coerce_input(value, _ctx = nil)
13
- raise NotImplementedError, "Subclasses must implement .coerce_input"
14
- end
15
-
16
- def valid?(value)
17
- coerce_input(value)
18
- true
19
- rescue CoercionError
20
- false
21
- end
22
-
23
- protected
24
-
25
- # Helper to raise CoercionError with consistent formatting
26
- # @param value [Object] The invalid value
27
- # @param message [String, nil] Custom message (optional)
28
- def coercion_error!(value, message = nil)
29
- displayed_value = format_value_for_error(value)
30
- msg = message || "#{displayed_value} is not a valid #{name}"
31
- raise CoercionError.new(msg, value: value, expected_type: name)
32
- end
33
-
34
- private
35
-
36
- # Format a value for display in error messages
37
- # Truncates long strings and handles various types
38
- def format_value_for_error(value)
39
- case value
40
- when ::String
41
- truncated = (value.length > 50) ? "#{value[0, 50]}..." : value
42
- "'#{truncated}'"
43
- when ::Symbol
44
- ":#{value}"
45
- when ::NilClass
46
- "nil"
47
- else
48
- value.inspect.then { |s| (s.length > 50) ? "#{s[0, 50]}..." : s }
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "base_type"
4
-
5
- module ZeroRuby
6
- module Types
7
- # BigInt type for large integers beyond 32-bit range.
8
- # Ruby's Integer handles arbitrary precision natively.
9
- # Accepts integers and numeric strings, coerces to Integer.
10
- class BigInt < BaseType
11
- class << self
12
- def name
13
- "BigInt"
14
- end
15
-
16
- def coerce_input(value, _ctx = nil)
17
- return nil if value.nil?
18
- return value if value.is_a?(::Integer)
19
-
20
- if value.is_a?(::String)
21
- coercion_error!(value, "empty string is not a valid #{name}") if value.empty?
22
- result = Kernel.Integer(value, exception: false)
23
- coercion_error!(value) if result.nil?
24
- result
25
- else
26
- coercion_error!(value, "#{format_value_for_error(value)} (#{value.class}) cannot be coerced to #{name}")
27
- end
28
- end
29
- end
30
- end
31
- end
32
- end