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,55 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ module Processor
7
+ class JsonSchemaConst < 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 < Skit::JsonSchema::Types::Const)
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 < Skit::JsonSchema::Types::Const
21
+ raise ArgumentError, "Expected Skit::JsonSchema::Types::Const subclass, got #{type_spec}"
22
+ end
23
+
24
+ @const_class = T.let(type_spec, T.class_of(Skit::JsonSchema::Types::Const))
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?(@const_class)
30
+ raise SerializeError.new("Expected #{@const_class}, got #{value.class}",
31
+ path: path)
32
+ end
33
+
34
+ value.value
35
+ end
36
+
37
+ sig { override.params(value: T.untyped, path: Path).returns(Skit::JsonSchema::Types::Const) }
38
+ def deserialize(value, path: Path.new)
39
+ return value if value.is_a?(@const_class)
40
+
41
+ expected = @const_class.value
42
+
43
+ unless value == expected
44
+ raise DeserializeError.new(
45
+ "Expected #{expected.inspect}, got #{value.inspect} for #{@const_class}",
46
+ path: path
47
+ )
48
+ end
49
+
50
+ @const_class.new
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,87 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ module Processor
7
+ class Nilable < Base
8
+ extend T::Sig
9
+
10
+ sig { override.params(type_spec: T.untyped).returns(T::Boolean) }
11
+ def self.handles?(type_spec)
12
+ nilable_type?(type_spec)
13
+ end
14
+
15
+ sig { params(type_spec: T.untyped, registry: Registry).void }
16
+ def initialize(type_spec, registry:)
17
+ super
18
+ unless self.class.nilable_type?(type_spec)
19
+ raise ArgumentError,
20
+ "Expected nilable type, got #{type_spec.class}"
21
+ end
22
+
23
+ @inner_type = T.let(extract_inner_type(type_spec), T.untyped)
24
+ end
25
+
26
+ sig { override.params(value: T.untyped, path: Path).returns(T.untyped) }
27
+ def serialize(value, path: Path.new)
28
+ return nil if value.nil?
29
+
30
+ processor = @registry.processor_for(@inner_type)
31
+ processor.serialize(value, path: path)
32
+ end
33
+
34
+ sig { override.params(value: T.untyped, path: Path).returns(T.untyped) }
35
+ def deserialize(value, path: Path.new)
36
+ return nil if value.nil?
37
+
38
+ processor = @registry.processor_for(@inner_type)
39
+ processor.deserialize(value, path: path)
40
+ end
41
+
42
+ sig do
43
+ override.params(
44
+ value: T.untyped,
45
+ path: Path,
46
+ blk: T.proc.params(type_spec: T.untyped, node: T.untyped, path: Path).void
47
+ ).void
48
+ end
49
+ def traverse(value, path: Path.new, &blk)
50
+ super
51
+
52
+ return if value.nil?
53
+
54
+ processor = @registry.processor_for(@inner_type)
55
+ processor.traverse(value, path: path, &blk)
56
+ end
57
+
58
+ class << self
59
+ extend T::Sig
60
+
61
+ sig { params(type_object: T.untyped).returns(T::Boolean) }
62
+ def nilable_type?(type_object)
63
+ return false unless type_object.is_a?(T::Types::Union)
64
+
65
+ types = type_object.types
66
+ has_nil = types.any? { |t| t.is_a?(T::Types::Simple) && t.raw_type == NilClass }
67
+ non_nil_count = types.count { |t| !(t.is_a?(T::Types::Simple) && t.raw_type == NilClass) }
68
+
69
+ has_nil && non_nil_count == 1
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ sig { params(nilable_type: T.untyped).returns(T.untyped) }
76
+ def extract_inner_type(nilable_type)
77
+ types = nilable_type.types
78
+ non_nil_types = types.reject { |t| t.is_a?(T::Types::Simple) && t.raw_type == NilClass }
79
+
80
+ raise ArgumentError, "T.nilable must have exactly one non-nil type" if non_nil_types.length != 1
81
+
82
+ non_nil_types.first
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,51 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ module Processor
7
+ class SimpleType < 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::Simple)
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::Simple)
19
+ raise ArgumentError, "Expected T::Types::Simple, got #{type_spec.class}"
20
+ end
21
+
22
+ @raw_type = T.let(type_spec.raw_type, T.untyped)
23
+ end
24
+
25
+ sig { override.params(value: T.untyped, path: Path).returns(T.untyped) }
26
+ def serialize(value, path: Path.new)
27
+ processor = @registry.processor_for(@raw_type)
28
+ processor.serialize(value, path: path)
29
+ end
30
+
31
+ sig { override.params(value: T.untyped, path: Path).returns(T.untyped) }
32
+ def deserialize(value, path: Path.new)
33
+ processor = @registry.processor_for(@raw_type)
34
+ processor.deserialize(value, path: path)
35
+ end
36
+
37
+ sig do
38
+ override.params(
39
+ value: T.untyped,
40
+ path: Path,
41
+ blk: T.proc.params(type_spec: T.untyped, node: T.untyped, path: Path).void
42
+ ).void
43
+ end
44
+ def traverse(value, path: Path.new, &blk)
45
+ processor = @registry.processor_for(@raw_type)
46
+ processor.traverse(value, path: path, &blk)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ 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 String < 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 == ::String
13
+ end
14
+
15
+ sig { override.params(value: T.untyped, path: Path).returns(::String) }
16
+ def serialize(value, path: Path.new)
17
+ raise SerializeError.new("Expected String, got #{value.class}", path: path) unless value.is_a?(::String)
18
+
19
+ value
20
+ end
21
+
22
+ sig { override.params(value: T.untyped, path: Path).returns(::String) }
23
+ def deserialize(value, path: Path.new)
24
+ raise DeserializeError.new("Expected String, got #{value.class}", path: path) unless value.is_a?(::String)
25
+
26
+ value
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,84 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ module Processor
7
+ class Struct < 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::Struct)
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::Struct
21
+ raise ArgumentError, "Expected T::Struct, got #{type_spec}"
22
+ end
23
+
24
+ @struct_class = T.let(type_spec, T.class_of(T::Struct))
25
+ end
26
+
27
+ sig { override.params(value: T.untyped, path: Path).returns(T::Hash[::String, T.untyped]) }
28
+ def serialize(value, path: Path.new)
29
+ unless value.is_a?(@struct_class)
30
+ raise SerializeError.new("Expected #{@struct_class}, got #{value.class}",
31
+ path: path)
32
+ end
33
+
34
+ @struct_class.props.each_with_object({}) do |(name, prop_def), hash|
35
+ prop_value = value.public_send(name)
36
+ prop_type = prop_def[:type_object]
37
+ processor = @registry.processor_for(prop_type)
38
+ hash[name.to_s] = processor.serialize(prop_value, path: path.append(name.to_s))
39
+ end
40
+ end
41
+
42
+ sig { override.params(value: T.untyped, path: Path).returns(T::Struct) }
43
+ def deserialize(value, path: Path.new)
44
+ return value if value.is_a?(@struct_class)
45
+
46
+ raise DeserializeError.new("Expected Hash, got #{value.class}", path: path) unless value.is_a?(::Hash)
47
+
48
+ symbolized = value.transform_keys(&:to_sym)
49
+
50
+ deserialized = @struct_class.props.each_with_object({}) do |(name, prop_def), hash|
51
+ next unless symbolized.key?(name)
52
+
53
+ prop_value = symbolized[name]
54
+ prop_type = prop_def[:type_object]
55
+ processor = @registry.processor_for(prop_type)
56
+ hash[name] = processor.deserialize(prop_value, path: path.append(name.to_s))
57
+ end
58
+
59
+ @struct_class.new(**deserialized)
60
+ end
61
+
62
+ sig do
63
+ override.params(
64
+ value: T.untyped,
65
+ path: Path,
66
+ blk: T.proc.params(type_spec: T.untyped, node: T.untyped, path: Path).void
67
+ ).void
68
+ end
69
+ def traverse(value, path: Path.new, &blk)
70
+ super
71
+
72
+ return unless value.is_a?(@struct_class)
73
+
74
+ @struct_class.props.each do |name, prop_def|
75
+ prop_value = value.public_send(name)
76
+ prop_type = prop_def[:type_object]
77
+ processor = @registry.processor_for(prop_type)
78
+ processor.traverse(prop_value, path: path.append(name.to_s), &blk)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ 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 Symbol < 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 == ::Symbol
13
+ end
14
+
15
+ sig { override.params(value: T.untyped, path: Path).returns(::String) }
16
+ def serialize(value, path: Path.new)
17
+ raise SerializeError.new("Expected Symbol, got #{value.class}", path: path) unless value.is_a?(::Symbol)
18
+
19
+ value.to_s
20
+ end
21
+
22
+ sig { override.params(value: T.untyped, path: Path).returns(::Symbol) }
23
+ def deserialize(value, path: Path.new)
24
+ return value if value.is_a?(::Symbol)
25
+
26
+ unless value.is_a?(::String)
27
+ raise DeserializeError.new("Expected String or Symbol, got #{value.class}",
28
+ path: path)
29
+ end
30
+
31
+ value.to_sym
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "time"
5
+
6
+ module Skit
7
+ module Serialization
8
+ module Processor
9
+ class Time < 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 == ::Time
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 Time, got #{value.class}", path: path) unless value.is_a?(::Time)
20
+
21
+ value.iso8601
22
+ end
23
+
24
+ sig { override.params(value: T.untyped, path: Path).returns(::Time) }
25
+ def deserialize(value, path: Path.new)
26
+ case value
27
+ when ::Time
28
+ value
29
+ when ::String
30
+ ::Time.iso8601(value)
31
+ else
32
+ raise DeserializeError.new("Expected Time or String, got #{value.class}", path: path)
33
+ end
34
+ rescue ArgumentError => e
35
+ raise DeserializeError.new("Failed to deserialize Time: #{e.message}", path: path)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,120 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ module Processor
7
+ class Union < 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?(T::Types::Union)
13
+ return false if contains_nil_class?(type_spec)
14
+ return false if boolean_union?(type_spec)
15
+
16
+ struct_types(type_spec).length == type_spec.types.length
17
+ end
18
+
19
+ sig { params(type_spec: T.untyped, registry: Registry).void }
20
+ def initialize(type_spec, registry:)
21
+ super
22
+ @struct_classes = T.let(
23
+ self.class.struct_types(type_spec),
24
+ T::Array[T.class_of(T::Struct)]
25
+ )
26
+ end
27
+
28
+ sig { override.params(value: T.untyped, path: Path).returns(T::Hash[::String, T.untyped]) }
29
+ def serialize(value, path: Path.new)
30
+ struct_class = find_struct_class_for_value(value)
31
+
32
+ unless struct_class
33
+ raise SerializeError.new(
34
+ "#{value.class} is not a member of this union: #{@struct_classes.map(&:name).join(", ")}",
35
+ path: path
36
+ )
37
+ end
38
+
39
+ processor = @registry.processor_for(struct_class)
40
+ processor.serialize(value, path: path)
41
+ end
42
+
43
+ sig { override.params(value: T.untyped, path: Path).returns(T.untyped) }
44
+ def deserialize(value, path: Path.new)
45
+ return value if value_is_union_member?(value)
46
+
47
+ unless value.is_a?(::Hash)
48
+ raise DeserializeError.new("Expected Hash or union member struct, got #{value.class}",
49
+ path: path)
50
+ end
51
+
52
+ @struct_classes.each do |struct_class|
53
+ result = try_deserialize(value, struct_class, path: path)
54
+ return result if result
55
+ end
56
+
57
+ raise DeserializeError.new(
58
+ "No matching struct found for union: #{@struct_classes.map(&:name).join(", ")}",
59
+ path: path
60
+ )
61
+ end
62
+
63
+ class << self
64
+ extend T::Sig
65
+
66
+ sig { params(type_spec: T.untyped).returns(T::Boolean) }
67
+ def contains_nil_class?(type_spec)
68
+ type_spec.types.any? do |t|
69
+ t.is_a?(T::Types::Simple) && t.raw_type == NilClass
70
+ end
71
+ end
72
+
73
+ sig { params(type_spec: T.untyped).returns(T::Boolean) }
74
+ def boolean_union?(type_spec)
75
+ raw_types = type_spec.types.filter_map do |t|
76
+ t.is_a?(T::Types::Simple) ? t.raw_type : nil
77
+ end
78
+ raw_types.sort_by { |t| t.name.to_s } == [FalseClass, TrueClass]
79
+ end
80
+
81
+ sig { params(type_spec: T.untyped).returns(T::Array[T.class_of(T::Struct)]) }
82
+ def struct_types(type_spec)
83
+ type_spec.types.filter_map do |t|
84
+ next unless t.is_a?(T::Types::Simple)
85
+
86
+ klass = t.raw_type
87
+ klass if klass.is_a?(Class) && klass < T::Struct
88
+ end
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ sig { params(value: T.untyped).returns(T.nilable(T.class_of(T::Struct))) }
95
+ def find_struct_class_for_value(value)
96
+ @struct_classes.find { |klass| value.is_a?(klass) }
97
+ end
98
+
99
+ sig { params(value: T.untyped).returns(T::Boolean) }
100
+ def value_is_union_member?(value)
101
+ @struct_classes.any? { |klass| value.is_a?(klass) }
102
+ end
103
+
104
+ sig do
105
+ params(
106
+ value: T::Hash[T.untyped, T.untyped],
107
+ struct_class: T.class_of(T::Struct),
108
+ path: Path
109
+ ).returns(T.nilable(T::Struct))
110
+ end
111
+ def try_deserialize(value, struct_class, path: Path.new)
112
+ processor = @registry.processor_for(struct_class)
113
+ processor.deserialize(value, path: path)
114
+ rescue SerializeError, DeserializeError, ArgumentError, TypeError
115
+ nil
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,33 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ module Serialization
6
+ class Registry
7
+ extend T::Sig
8
+
9
+ sig { void }
10
+ def initialize
11
+ @processors = T.let([], T::Array[T.class_of(Processor::Base)])
12
+ end
13
+
14
+ sig { params(processor_class: T.class_of(Processor::Base)).void }
15
+ def register(processor_class)
16
+ @processors << processor_class
17
+ end
18
+
19
+ sig { params(type_spec: T.untyped).returns(T.class_of(Processor::Base)) }
20
+ def find_processor(type_spec)
21
+ processor_class = @processors.find { |p| p.handles?(type_spec) }
22
+ raise UnknownTypeError, "No processor for #{type_spec}" unless processor_class
23
+
24
+ processor_class
25
+ end
26
+
27
+ sig { params(type_spec: T.untyped).returns(Processor::Base) }
28
+ def processor_for(type_spec)
29
+ find_processor(type_spec).new(type_spec, registry: self)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,60 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "serialization/errors"
5
+ require_relative "serialization/path"
6
+ require_relative "serialization/processor/base"
7
+ require_relative "serialization/registry"
8
+
9
+ # Processors
10
+ require_relative "serialization/processor/string"
11
+ require_relative "serialization/processor/integer"
12
+ require_relative "serialization/processor/float"
13
+ require_relative "serialization/processor/boolean"
14
+ require_relative "serialization/processor/symbol"
15
+ require_relative "serialization/processor/date"
16
+ require_relative "serialization/processor/time"
17
+ require_relative "serialization/processor/struct"
18
+ require_relative "serialization/processor/simple_type"
19
+ require_relative "serialization/processor/array"
20
+ require_relative "serialization/processor/hash"
21
+ require_relative "serialization/processor/nilable"
22
+ require_relative "serialization/processor/union"
23
+ require_relative "serialization/processor/json_schema_const"
24
+ require_relative "serialization/processor/enum"
25
+
26
+ module Skit
27
+ module Serialization
28
+ extend T::Sig
29
+
30
+ sig { returns(Registry) }
31
+ def self.default_registry
32
+ @default_registry ||= T.let(build_default_registry, T.nilable(Registry))
33
+ @default_registry
34
+ end
35
+
36
+ sig { returns(Registry) }
37
+ def self.build_default_registry
38
+ registry = Registry.new
39
+ # Register processors in order of specificity (most specific first)
40
+ registry.register(Processor::Nilable)
41
+ registry.register(Processor::Union)
42
+ registry.register(Processor::Array)
43
+ registry.register(Processor::Hash)
44
+ registry.register(Processor::JsonSchemaConst)
45
+ registry.register(Processor::Enum)
46
+ registry.register(Processor::Struct)
47
+ registry.register(Processor::SimpleType)
48
+ registry.register(Processor::Date)
49
+ registry.register(Processor::Time)
50
+ registry.register(Processor::String)
51
+ registry.register(Processor::Integer)
52
+ registry.register(Processor::Float)
53
+ registry.register(Processor::Boolean)
54
+ registry.register(Processor::Symbol)
55
+ registry
56
+ end
57
+
58
+ private_class_method :build_default_registry
59
+ end
60
+ end
@@ -0,0 +1,6 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Skit
5
+ VERSION = "0.1.0"
6
+ end
data/lib/skit.rb ADDED
@@ -0,0 +1,46 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ module Skit
7
+ class Error < StandardError; end
8
+ end
9
+
10
+ require_relative "skit/version"
11
+ # Load JsonSchema::Types::Const early so it can be used by serialization processors
12
+ require_relative "skit/json_schema/types/const"
13
+ require_relative "skit/serialization"
14
+ require_relative "skit/attribute"
15
+ require_relative "skit/json_schema"
16
+ require_relative "active_model/validations/skit_validator"
17
+
18
+ module Skit # rubocop:disable Style/OneClassPerFile
19
+ extend T::Sig
20
+
21
+ # Serialize a T::Struct instance to a Hash with string keys.
22
+ #
23
+ # @param struct [T::Struct] The struct instance to serialize
24
+ # @return [Hash] The serialized Hash
25
+ # @raise [Serialization::SerializeError] If the value is not a T::Struct
26
+ sig { params(struct: T::Struct).returns(T::Hash[::String, T.untyped]) }
27
+ def self.serialize(struct)
28
+ struct_class = struct.class
29
+ raise Serialization::SerializeError, "Expected T::Struct, got #{struct_class}" unless struct_class < T::Struct
30
+
31
+ processor = Serialization.default_registry.processor_for(struct_class)
32
+ processor.serialize(struct)
33
+ end
34
+
35
+ # Deserialize a Hash to a T::Struct instance.
36
+ #
37
+ # @param hash [Hash] The hash to deserialize
38
+ # @param type [Class] The T::Struct class to deserialize to
39
+ # @return [T::Struct] The deserialized struct instance
40
+ # @raise [Serialization::DeserializeError] If deserialization fails
41
+ sig { params(hash: T.untyped, type: T.class_of(T::Struct)).returns(T::Struct) }
42
+ def self.deserialize(hash, type)
43
+ processor = Serialization.default_registry.processor_for(type)
44
+ processor.deserialize(hash)
45
+ end
46
+ end