skit 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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +469 -0
  4. data/exe/skit +31 -0
  5. data/lib/active_model/validations/skit_validator.rb +54 -0
  6. data/lib/skit/attribute.rb +63 -0
  7. data/lib/skit/json_schema/class_name_path.rb +67 -0
  8. data/lib/skit/json_schema/cli.rb +166 -0
  9. data/lib/skit/json_schema/code_generator.rb +132 -0
  10. data/lib/skit/json_schema/config.rb +67 -0
  11. data/lib/skit/json_schema/definitions/array_property_type.rb +36 -0
  12. data/lib/skit/json_schema/definitions/const_type.rb +68 -0
  13. data/lib/skit/json_schema/definitions/enum_type.rb +71 -0
  14. data/lib/skit/json_schema/definitions/hash_property_type.rb +36 -0
  15. data/lib/skit/json_schema/definitions/module.rb +54 -0
  16. data/lib/skit/json_schema/definitions/property_type.rb +39 -0
  17. data/lib/skit/json_schema/definitions/property_types.rb +13 -0
  18. data/lib/skit/json_schema/definitions/struct.rb +99 -0
  19. data/lib/skit/json_schema/definitions/struct_property.rb +75 -0
  20. data/lib/skit/json_schema/definitions/union_property_type.rb +40 -0
  21. data/lib/skit/json_schema/naming_utils.rb +25 -0
  22. data/lib/skit/json_schema/schema_analyzer.rb +407 -0
  23. data/lib/skit/json_schema/types/const.rb +69 -0
  24. data/lib/skit/json_schema.rb +77 -0
  25. data/lib/skit/serialization/errors.rb +23 -0
  26. data/lib/skit/serialization/path.rb +69 -0
  27. data/lib/skit/serialization/processor/array.rb +65 -0
  28. data/lib/skit/serialization/processor/base.rb +47 -0
  29. data/lib/skit/serialization/processor/boolean.rb +35 -0
  30. data/lib/skit/serialization/processor/date.rb +40 -0
  31. data/lib/skit/serialization/processor/enum.rb +54 -0
  32. data/lib/skit/serialization/processor/float.rb +36 -0
  33. data/lib/skit/serialization/processor/hash.rb +93 -0
  34. data/lib/skit/serialization/processor/integer.rb +31 -0
  35. data/lib/skit/serialization/processor/json_schema_const.rb +55 -0
  36. data/lib/skit/serialization/processor/nilable.rb +87 -0
  37. data/lib/skit/serialization/processor/simple_type.rb +51 -0
  38. data/lib/skit/serialization/processor/string.rb +31 -0
  39. data/lib/skit/serialization/processor/struct.rb +84 -0
  40. data/lib/skit/serialization/processor/symbol.rb +36 -0
  41. data/lib/skit/serialization/processor/time.rb +40 -0
  42. data/lib/skit/serialization/processor/union.rb +120 -0
  43. data/lib/skit/serialization/registry.rb +33 -0
  44. data/lib/skit/serialization.rb +60 -0
  45. data/lib/skit/version.rb +6 -0
  46. data/lib/skit.rb +46 -0
  47. data/lib/tapioca/dsl/compilers/skit.rb +105 -0
  48. metadata +135 -0
@@ -0,0 +1,69 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module JsonSchema
6
+ module Types
7
+ # Base class for JSON Schema const values.
8
+ #
9
+ # Subclasses should define a VALUE constant with the const value:
10
+ #
11
+ # class Dog < Skit::JsonSchema::Types::Const
12
+ # VALUE = "dog"
13
+ # end
14
+ #
15
+ # This enables type-safe discriminated unions:
16
+ #
17
+ # T.any(Dog, Cat) # "dog" deserializes to Dog, "cat" to Cat
18
+ #
19
+ class Const
20
+ extend T::Sig
21
+
22
+ # Returns the const value defined in the subclass.
23
+ sig { returns(T.untyped) }
24
+ def self.value
25
+ # rubocop:disable Sorbet/ConstantsFromStrings
26
+ const_get(:VALUE)
27
+ # rubocop:enable Sorbet/ConstantsFromStrings
28
+ end
29
+
30
+ # Returns the const value for this instance.
31
+ sig { returns(T.untyped) }
32
+ def value
33
+ self.class.value
34
+ end
35
+
36
+ # Two Const instances are equal if they are of the same class.
37
+ sig { params(other: T.untyped).returns(T::Boolean) }
38
+ def ==(other)
39
+ other.is_a?(self.class)
40
+ end
41
+
42
+ # Alias for == to support Hash key comparison.
43
+ sig { params(other: T.untyped).returns(T::Boolean) }
44
+ def eql?(other)
45
+ self == other
46
+ end
47
+
48
+ # Hash code based on the class, so all instances of the same class
49
+ # have the same hash code.
50
+ sig { returns(Integer) }
51
+ def hash
52
+ self.class.hash
53
+ end
54
+
55
+ # Returns a string representation for debugging.
56
+ sig { returns(String) }
57
+ def inspect
58
+ "#<#{self.class.name} value=#{value.inspect}>"
59
+ end
60
+
61
+ # Returns the string representation (the const value as string).
62
+ sig { returns(String) }
63
+ def to_s
64
+ value.to_s
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,77 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "json_schema/types/const"
5
+ require_relative "json_schema/naming_utils"
6
+ require_relative "json_schema/definitions/property_type"
7
+ require_relative "json_schema/definitions/const_type"
8
+ require_relative "json_schema/definitions/enum_type"
9
+ require_relative "json_schema/definitions/property_types"
10
+ require_relative "json_schema/definitions/array_property_type"
11
+ require_relative "json_schema/definitions/hash_property_type"
12
+ require_relative "json_schema/definitions/union_property_type"
13
+ require_relative "json_schema/definitions/struct_property"
14
+ require_relative "json_schema/definitions/struct"
15
+ require_relative "json_schema/definitions/module"
16
+ require_relative "json_schema/config"
17
+ require_relative "json_schema/class_name_path"
18
+ require_relative "json_schema/schema_analyzer"
19
+ require_relative "json_schema/code_generator"
20
+ require_relative "json_schema/cli"
21
+
22
+ module Skit
23
+ module JsonSchema
24
+ extend T::Sig
25
+
26
+ # Generate Sorbet T::Struct code from JSON Schema
27
+ #
28
+ # @param schema [Hash] The JSON Schema hash
29
+ # @param options [Hash] Configuration options
30
+ # @option options [String] :class_name Class name for the root struct (optional)
31
+ # @option options [String] :module_name Module name to wrap the generated struct (optional)
32
+ # @option options [String] :typed_strictness Sorbet typing level ('ignore', 'false', 'true', 'strict', 'strong')
33
+ #
34
+ # @return [String] Generated Ruby/Sorbet code
35
+ #
36
+ # @example Basic usage
37
+ # schema = { "type" => "object", "properties" => { "name" => { "type" => "string" } } }
38
+ # code = Skit::JsonSchema.generate(schema, class_name: "User")
39
+ # puts code
40
+ #
41
+ # @example With all options
42
+ # code = Skit::JsonSchema.generate(
43
+ # schema,
44
+ # class_name: "User",
45
+ # module_name: "MyModule",
46
+ # typed_strictness: "strict"
47
+ # )
48
+ sig do
49
+ params(
50
+ schema: T::Hash[String, T.untyped],
51
+ options: T::Hash[T.any(Symbol, String), T.untyped]
52
+ ).returns(String)
53
+ end
54
+ def self.generate(schema, options = {})
55
+ # Extract and validate options (support both symbol and string keys)
56
+ class_name = T.cast(options[:class_name] || options["class_name"], T.nilable(String))
57
+ module_name = T.cast(options[:module_name] || options["module_name"], T.nilable(String))
58
+ typed_strictness = T.cast(options[:typed_strictness] || options["typed_strictness"],
59
+ T.nilable(String)) || "strict"
60
+
61
+ # Create config with smart defaults
62
+ config = Config.new(
63
+ class_name: class_name,
64
+ module_name: module_name,
65
+ typed_strictness: typed_strictness
66
+ )
67
+
68
+ # Analyze schema
69
+ analyzer = SchemaAnalyzer.new(schema, config)
70
+ module_definition = analyzer.analyze
71
+
72
+ # Generate code
73
+ generator = CodeGenerator.new(module_definition, config)
74
+ generator.generate
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ class Error < Skit::Error
7
+ extend T::Sig
8
+
9
+ sig { returns(Path) }
10
+ attr_reader :path
11
+
12
+ sig { params(message: ::String, path: Path).void }
13
+ def initialize(message = "", path: Path.new)
14
+ @path = T.let(path, Path)
15
+ super(path.empty? ? message : "#{message} (at #{path})")
16
+ end
17
+ end
18
+
19
+ class UnknownTypeError < Error; end
20
+ class SerializeError < Error; end
21
+ class DeserializeError < Error; end
22
+ end
23
+ end
@@ -0,0 +1,69 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ class Path
7
+ extend T::Sig
8
+
9
+ Segment = T.type_alias { T.any(::String, ::Integer) }
10
+
11
+ sig { params(segments: T::Array[Segment]).void }
12
+ def initialize(segments = [])
13
+ @segments = T.let(segments.freeze, T::Array[Segment])
14
+ end
15
+
16
+ sig { params(segment: Segment).returns(Path) }
17
+ def append(segment)
18
+ Path.new(@segments + [segment])
19
+ end
20
+
21
+ sig { returns(T::Array[Segment]) }
22
+ attr_reader :segments
23
+
24
+ sig { returns(T::Boolean) }
25
+ def empty?
26
+ @segments.empty?
27
+ end
28
+
29
+ sig { returns(::String) }
30
+ def to_s
31
+ result = +""
32
+ @segments.each do |segment|
33
+ case segment
34
+ when ::Integer
35
+ result << "[#{segment}]"
36
+ when ::String
37
+ result << "." unless result.empty?
38
+ result << segment
39
+ end
40
+ end
41
+ result.freeze
42
+ end
43
+
44
+ sig { returns(::String) }
45
+ def to_json_pointer
46
+ return "" if @segments.empty?
47
+
48
+ "/#{@segments.map { |s| s.to_s.gsub("~", "~0").gsub("/", "~1") }.join("/")}"
49
+ end
50
+
51
+ sig { params(other: T.untyped).returns(T::Boolean) }
52
+ def ==(other)
53
+ return false unless other.is_a?(Path)
54
+
55
+ @segments == other.segments
56
+ end
57
+
58
+ sig { returns(::Integer) }
59
+ def hash
60
+ @segments.hash
61
+ end
62
+
63
+ sig { params(other: T.untyped).returns(T::Boolean) }
64
+ def eql?(other)
65
+ self == other
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,65 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ module Processor
7
+ class Array < Base
8
+ extend T::Sig
9
+
10
+ sig { override.params(type_spec: T.untyped).returns(T::Boolean) }
11
+ def self.handles?(type_spec)
12
+ type_spec.is_a?(T::Types::TypedArray)
13
+ end
14
+
15
+ sig { params(type_spec: T.untyped, registry: Registry).void }
16
+ def initialize(type_spec, registry:)
17
+ super
18
+ unless type_spec.is_a?(T::Types::TypedArray)
19
+ raise ArgumentError, "Expected T::Types::TypedArray, got #{type_spec.class}"
20
+ end
21
+
22
+ @element_type = T.let(type_spec.type, T.untyped)
23
+ end
24
+
25
+ sig { override.params(value: T.untyped, path: Path).returns(T::Array[T.untyped]) }
26
+ def serialize(value, path: Path.new)
27
+ raise SerializeError.new("Expected Array, got #{value.class}", path: path) unless value.is_a?(::Array)
28
+
29
+ value.each_with_index.map do |item, index|
30
+ processor = @registry.processor_for(@element_type)
31
+ processor.serialize(item, path: path.append(index))
32
+ end
33
+ end
34
+
35
+ sig { override.params(value: T.untyped, path: Path).returns(T::Array[T.untyped]) }
36
+ def deserialize(value, path: Path.new)
37
+ raise DeserializeError.new("Expected Array, got #{value.class}", path: path) unless value.is_a?(::Array)
38
+
39
+ value.each_with_index.map do |item, index|
40
+ processor = @registry.processor_for(@element_type)
41
+ processor.deserialize(item, path: path.append(index))
42
+ end
43
+ end
44
+
45
+ sig do
46
+ override.params(
47
+ value: T.untyped,
48
+ path: Path,
49
+ blk: T.proc.params(type_spec: T.untyped, node: T.untyped, path: Path).void
50
+ ).void
51
+ end
52
+ def traverse(value, path: Path.new, &blk)
53
+ super
54
+
55
+ return unless value.is_a?(::Array)
56
+
57
+ value.each_with_index do |item, index|
58
+ processor = @registry.processor_for(@element_type)
59
+ processor.traverse(item, path: path.append(index), &blk)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,47 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ module Processor
7
+ class Base
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ abstract!
12
+
13
+ sig { overridable.params(type_spec: T.untyped).returns(T::Boolean) }
14
+ def self.handles?(type_spec)
15
+ raise NotImplementedError, "#{self}.handles? must be implemented"
16
+ end
17
+
18
+ sig { params(type_spec: T.untyped, registry: Registry).void }
19
+ def initialize(type_spec, registry:)
20
+ @type_spec = type_spec
21
+ @registry = T.let(registry, Registry)
22
+ end
23
+
24
+ sig { overridable.params(value: T.untyped, path: Path).returns(T.untyped) }
25
+ def serialize(value, path: Path.new)
26
+ raise NotImplementedError, "#{self.class}#serialize must be implemented"
27
+ end
28
+
29
+ sig { overridable.params(value: T.untyped, path: Path).returns(T.untyped) }
30
+ def deserialize(value, path: Path.new)
31
+ raise NotImplementedError, "#{self.class}#deserialize must be implemented"
32
+ end
33
+
34
+ sig do
35
+ overridable.params(
36
+ value: T.untyped,
37
+ path: Path,
38
+ blk: T.proc.params(type_spec: T.untyped, node: T.untyped, path: Path).void
39
+ ).void
40
+ end
41
+ def traverse(value, path: Path.new, &blk)
42
+ blk.call(@type_spec, value, path)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,35 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ module Processor
7
+ class Boolean < Base
8
+ extend T::Sig
9
+
10
+ sig { override.params(type_spec: T.untyped).returns(T::Boolean) }
11
+ def self.handles?(type_spec)
12
+ type_spec == T::Boolean
13
+ end
14
+
15
+ sig { override.params(value: T.untyped, path: Path).returns(T::Boolean) }
16
+ def serialize(value, path: Path.new)
17
+ unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
18
+ raise SerializeError.new("Expected TrueClass or FalseClass, got #{value.class}", path: path)
19
+ end
20
+
21
+ value
22
+ end
23
+
24
+ sig { override.params(value: T.untyped, path: Path).returns(T::Boolean) }
25
+ def deserialize(value, path: Path.new)
26
+ unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
27
+ raise DeserializeError.new("Expected TrueClass or FalseClass, got #{value.class}", path: path)
28
+ end
29
+
30
+ value
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,40 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "date"
5
+
6
+ module Skit
7
+ module Serialization
8
+ module Processor
9
+ class Date < Base
10
+ extend T::Sig
11
+
12
+ sig { override.params(type_spec: T.untyped).returns(T::Boolean) }
13
+ def self.handles?(type_spec)
14
+ type_spec == ::Date
15
+ end
16
+
17
+ sig { override.params(value: T.untyped, path: Path).returns(::String) }
18
+ def serialize(value, path: Path.new)
19
+ raise SerializeError.new("Expected Date, got #{value.class}", path: path) unless value.is_a?(::Date)
20
+
21
+ value.iso8601
22
+ end
23
+
24
+ sig { override.params(value: T.untyped, path: Path).returns(::Date) }
25
+ def deserialize(value, path: Path.new)
26
+ case value
27
+ when ::Date
28
+ value
29
+ when ::String
30
+ ::Date.iso8601(value)
31
+ else
32
+ raise DeserializeError.new("Expected Date or String, got #{value.class}", path: path)
33
+ end
34
+ rescue ArgumentError => e
35
+ raise DeserializeError.new("Failed to deserialize Date: #{e.message}", path: path)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,54 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ module Processor
7
+ class Enum < Base
8
+ extend T::Sig
9
+
10
+ sig { override.params(type_spec: T.untyped).returns(T::Boolean) }
11
+ def self.handles?(type_spec)
12
+ return false unless type_spec.is_a?(Class)
13
+
14
+ !!(type_spec < T::Enum)
15
+ end
16
+
17
+ sig { params(type_spec: T.untyped, registry: Registry).void }
18
+ def initialize(type_spec, registry:)
19
+ super
20
+ unless type_spec.is_a?(Class) && type_spec < T::Enum
21
+ raise ArgumentError, "Expected T::Enum subclass, got #{type_spec}"
22
+ end
23
+
24
+ @enum_class = T.let(type_spec, T.class_of(T::Enum))
25
+ end
26
+
27
+ sig { override.params(value: T.untyped, path: Path).returns(T.untyped) }
28
+ def serialize(value, path: Path.new)
29
+ unless value.is_a?(@enum_class)
30
+ raise SerializeError.new("Expected #{@enum_class}, got #{value.class}",
31
+ path: path)
32
+ end
33
+
34
+ value.serialize
35
+ end
36
+
37
+ sig { override.params(value: T.untyped, path: Path).returns(T::Enum) }
38
+ def deserialize(value, path: Path.new)
39
+ return value if value.is_a?(@enum_class)
40
+
41
+ begin
42
+ @enum_class.deserialize(value)
43
+ rescue KeyError
44
+ valid_values = @enum_class.values.map(&:serialize)
45
+ raise DeserializeError.new(
46
+ "Invalid value #{value.inspect} for #{@enum_class}. Valid values: #{valid_values.inspect}",
47
+ path: path
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ module Processor
7
+ class Float < Base
8
+ extend T::Sig
9
+
10
+ sig { override.params(type_spec: T.untyped).returns(T::Boolean) }
11
+ def self.handles?(type_spec)
12
+ type_spec == ::Float
13
+ end
14
+
15
+ sig { override.params(value: T.untyped, path: Path).returns(::Float) }
16
+ def serialize(value, path: Path.new)
17
+ raise SerializeError.new("Expected Float, got #{value.class}", path: path) unless value.is_a?(::Float)
18
+
19
+ value
20
+ end
21
+
22
+ sig { override.params(value: T.untyped, path: Path).returns(::Float) }
23
+ def deserialize(value, path: Path.new)
24
+ case value
25
+ when ::Float
26
+ value
27
+ when ::Integer
28
+ value.to_f
29
+ else
30
+ raise DeserializeError.new("Expected Float or Integer, got #{value.class}", path: path)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,93 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ module Processor
7
+ class Hash < Base
8
+ extend T::Sig
9
+
10
+ sig { override.params(type_spec: T.untyped).returns(T::Boolean) }
11
+ def self.handles?(type_spec)
12
+ type_spec.is_a?(T::Types::TypedHash)
13
+ end
14
+
15
+ sig { params(type_spec: T.untyped, registry: Registry).void }
16
+ def initialize(type_spec, registry:)
17
+ super
18
+ unless type_spec.is_a?(T::Types::TypedHash)
19
+ raise ArgumentError, "Expected T::Types::TypedHash, got #{type_spec.class}"
20
+ end
21
+
22
+ @key_type = T.let(extract_raw_key_type(type_spec.keys), T.untyped)
23
+ @value_type = T.let(type_spec.values, T.untyped)
24
+ end
25
+
26
+ sig { override.params(value: T.untyped, path: Path).returns(T::Hash[::String, T.untyped]) }
27
+ def serialize(value, path: Path.new)
28
+ raise SerializeError.new("Expected Hash, got #{value.class}", path: path) unless value.is_a?(::Hash)
29
+
30
+ result = T.let({}, T::Hash[::String, T.untyped])
31
+ value.each do |key, item|
32
+ processor = @registry.processor_for(@value_type)
33
+ result[key.to_s] = processor.serialize(item, path: path.append(key.to_s))
34
+ end
35
+ result
36
+ end
37
+
38
+ sig { override.params(value: T.untyped, path: Path).returns(T::Hash[T.untyped, T.untyped]) }
39
+ def deserialize(value, path: Path.new)
40
+ raise DeserializeError.new("Expected Hash, got #{value.class}", path: path) unless value.is_a?(::Hash)
41
+
42
+ result = T.let({}, T::Hash[T.untyped, T.untyped])
43
+ value.each do |key, item|
44
+ normalized_key = normalize_key(key)
45
+ processor = @registry.processor_for(@value_type)
46
+ result[normalized_key] = processor.deserialize(item, path: path.append(key.to_s))
47
+ end
48
+ result
49
+ end
50
+
51
+ sig do
52
+ override.params(
53
+ value: T.untyped,
54
+ path: Path,
55
+ blk: T.proc.params(type_spec: T.untyped, node: T.untyped, path: Path).void
56
+ ).void
57
+ end
58
+ def traverse(value, path: Path.new, &blk)
59
+ super
60
+
61
+ return unless value.is_a?(::Hash)
62
+
63
+ value.each do |key, val|
64
+ processor = @registry.processor_for(@value_type)
65
+ processor.traverse(val, path: path.append(key.to_s), &blk)
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ sig { params(key_type: T.untyped).returns(T.untyped) }
72
+ def extract_raw_key_type(key_type)
73
+ if key_type.is_a?(T::Types::Simple)
74
+ key_type.raw_type
75
+ else
76
+ key_type
77
+ end
78
+ end
79
+
80
+ sig { params(key: T.untyped).returns(T.untyped) }
81
+ def normalize_key(key)
82
+ if @key_type == ::String
83
+ key.to_s
84
+ elsif @key_type == ::Symbol
85
+ key.to_sym
86
+ else
87
+ key
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,31 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ module Processor
7
+ class Integer < Base
8
+ extend T::Sig
9
+
10
+ sig { override.params(type_spec: T.untyped).returns(T::Boolean) }
11
+ def self.handles?(type_spec)
12
+ type_spec == ::Integer
13
+ end
14
+
15
+ sig { override.params(value: T.untyped, path: Path).returns(::Integer) }
16
+ def serialize(value, path: Path.new)
17
+ raise SerializeError.new("Expected Integer, got #{value.class}", path: path) unless value.is_a?(::Integer)
18
+
19
+ value
20
+ end
21
+
22
+ sig { override.params(value: T.untyped, path: Path).returns(::Integer) }
23
+ def deserialize(value, path: Path.new)
24
+ raise DeserializeError.new("Expected Integer, got #{value.class}", path: path) unless value.is_a?(::Integer)
25
+
26
+ value
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end