castkit 0.2.0 → 0.3.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec_status +118 -119
  3. data/CHANGELOG.md +1 -1
  4. data/README.md +287 -11
  5. data/castkit.gemspec +1 -0
  6. data/lib/castkit/castkit.rb +5 -2
  7. data/lib/castkit/cli/generate.rb +98 -0
  8. data/lib/castkit/cli/list.rb +200 -0
  9. data/lib/castkit/cli/main.rb +43 -0
  10. data/lib/castkit/cli.rb +24 -0
  11. data/lib/castkit/configuration.rb +31 -8
  12. data/lib/castkit/contract/{generic.rb → base.rb} +5 -5
  13. data/lib/castkit/contract/result.rb +2 -2
  14. data/lib/castkit/contract.rb +5 -5
  15. data/lib/castkit/data_object.rb +11 -7
  16. data/lib/castkit/ext/data_object/contract.rb +1 -1
  17. data/lib/castkit/ext/data_object/plugins.rb +86 -0
  18. data/lib/castkit/inflector.rb +1 -1
  19. data/lib/castkit/plugins.rb +82 -0
  20. data/lib/castkit/serializers/base.rb +94 -0
  21. data/lib/castkit/serializers/default_serializer.rb +156 -0
  22. data/lib/castkit/types/{generic.rb → base.rb} +6 -7
  23. data/lib/castkit/types/boolean.rb +14 -10
  24. data/lib/castkit/types/collection.rb +13 -2
  25. data/lib/castkit/types/date.rb +2 -2
  26. data/lib/castkit/types/date_time.rb +2 -2
  27. data/lib/castkit/types/float.rb +5 -5
  28. data/lib/castkit/types/integer.rb +5 -5
  29. data/lib/castkit/types/string.rb +2 -2
  30. data/lib/castkit/types.rb +1 -1
  31. data/lib/castkit/validators/base.rb +59 -0
  32. data/lib/castkit/validators/boolean_validator.rb +39 -0
  33. data/lib/castkit/validators/collection_validator.rb +29 -0
  34. data/lib/castkit/validators/float_validator.rb +31 -0
  35. data/lib/castkit/validators/integer_validator.rb +31 -0
  36. data/lib/castkit/validators/numeric_validator.rb +2 -2
  37. data/lib/castkit/validators/string_validator.rb +3 -4
  38. data/lib/castkit/version.rb +1 -1
  39. data/lib/generators/base.rb +97 -0
  40. data/lib/generators/contract.rb +68 -0
  41. data/lib/generators/data_object.rb +48 -0
  42. data/lib/generators/plugin.rb +25 -0
  43. data/lib/generators/serializer.rb +28 -0
  44. data/lib/generators/templates/contract.rb.tt +24 -0
  45. data/lib/generators/templates/contract_spec.rb.tt +76 -0
  46. data/lib/generators/templates/data_object.rb.tt +15 -0
  47. data/lib/generators/templates/data_object_spec.rb.tt +36 -0
  48. data/lib/generators/templates/plugin.rb.tt +37 -0
  49. data/lib/generators/templates/plugin_spec.rb.tt +18 -0
  50. data/lib/generators/templates/serializer.rb.tt +24 -0
  51. data/lib/generators/templates/serializer_spec.rb.tt +14 -0
  52. data/lib/generators/templates/type.rb.tt +55 -0
  53. data/lib/generators/templates/type_spec.rb.tt +42 -0
  54. data/lib/generators/templates/validator.rb.tt +26 -0
  55. data/lib/generators/templates/validator_spec.rb.tt +23 -0
  56. data/lib/generators/type.rb +29 -0
  57. data/lib/generators/validator.rb +41 -0
  58. metadata +50 -7
  59. data/lib/castkit/default_serializer.rb +0 -154
  60. data/lib/castkit/serializer.rb +0 -92
  61. data/lib/castkit/validators/base_validator.rb +0 -39
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor/group"
4
+ require "castkit/inflector"
5
+ require_relative "base"
6
+
7
+ module Castkit
8
+ module Generators
9
+ # Generator for creating a new Castkit type.
10
+ #
11
+ # Types define custom deserialization, serialization, and validation behavior
12
+ # for attributes used in `Castkit::DataObject` or `Castkit::Contract`.
13
+ #
14
+ # Example usage:
15
+ # $ castkit generate type Money
16
+ #
17
+ # Generates:
18
+ # - lib/castkit/types/money.rb
19
+ # - spec/castkit/types/money_spec.rb
20
+ #
21
+ # These files scaffold a `Castkit::Types::Money` class inheriting from `Castkit::Types::Base`,
22
+ # along with a basic RSpec test suite.
23
+ #
24
+ # @see Castkit::Generators::Base
25
+ class Type < Castkit::Generators::Base
26
+ component :type
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor/group"
4
+ require "castkit/inflector"
5
+ require_relative "base"
6
+
7
+ module Castkit
8
+ module Generators
9
+ # Generator for creating a new Castkit validator.
10
+ #
11
+ # Validators are responsible for asserting that a given value conforms to a rule.
12
+ # They are typically used inside a type’s `#validate!` method or within custom contract logic.
13
+ #
14
+ # Example usage:
15
+ # $ castkit generate validator Money
16
+ #
17
+ # Generates:
18
+ # - lib/castkit/validators/money.rb
19
+ # - spec/castkit/validators/money_spec.rb
20
+ #
21
+ # These files scaffold a `Castkit::Validators::Money` class with a `#call` method
22
+ # and a corresponding RSpec test suite.
23
+ #
24
+ # @see Castkit::Generators::Base
25
+ class Validator < Castkit::Generators::Base
26
+ component :validator
27
+
28
+ private
29
+
30
+ # Provides extra context used within ERB templates for this generator.
31
+ #
32
+ # @return [Hash]
33
+ def config
34
+ super.merge(
35
+ default_value: "example",
36
+ sample_context: "field_name"
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: castkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Lucas
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-04-16 00:00:00.000000000 Z
11
+ date: 2025-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rspec
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -73,10 +87,14 @@ files:
73
87
  - lib/castkit.rb
74
88
  - lib/castkit/attribute.rb
75
89
  - lib/castkit/castkit.rb
90
+ - lib/castkit/cli.rb
91
+ - lib/castkit/cli/generate.rb
92
+ - lib/castkit/cli/list.rb
93
+ - lib/castkit/cli/main.rb
76
94
  - lib/castkit/configuration.rb
77
95
  - lib/castkit/contract.rb
96
+ - lib/castkit/contract/base.rb
78
97
  - lib/castkit/contract/data_object.rb
79
- - lib/castkit/contract/generic.rb
80
98
  - lib/castkit/contract/result.rb
81
99
  - lib/castkit/contract/validator.rb
82
100
  - lib/castkit/core/attribute_types.rb
@@ -84,7 +102,6 @@ files:
84
102
  - lib/castkit/core/config.rb
85
103
  - lib/castkit/core/registerable.rb
86
104
  - lib/castkit/data_object.rb
87
- - lib/castkit/default_serializer.rb
88
105
  - lib/castkit/error.rb
89
106
  - lib/castkit/ext/attribute/access.rb
90
107
  - lib/castkit/ext/attribute/error_handling.rb
@@ -92,23 +109,49 @@ files:
92
109
  - lib/castkit/ext/attribute/validation.rb
93
110
  - lib/castkit/ext/data_object/contract.rb
94
111
  - lib/castkit/ext/data_object/deserialization.rb
112
+ - lib/castkit/ext/data_object/plugins.rb
95
113
  - lib/castkit/ext/data_object/serialization.rb
96
114
  - lib/castkit/inflector.rb
97
- - lib/castkit/serializer.rb
115
+ - lib/castkit/plugins.rb
116
+ - lib/castkit/serializers/base.rb
117
+ - lib/castkit/serializers/default_serializer.rb
98
118
  - lib/castkit/types.rb
119
+ - lib/castkit/types/base.rb
99
120
  - lib/castkit/types/boolean.rb
100
121
  - lib/castkit/types/collection.rb
101
122
  - lib/castkit/types/date.rb
102
123
  - lib/castkit/types/date_time.rb
103
124
  - lib/castkit/types/float.rb
104
- - lib/castkit/types/generic.rb
105
125
  - lib/castkit/types/integer.rb
106
126
  - lib/castkit/types/string.rb
107
127
  - lib/castkit/validator.rb
108
- - lib/castkit/validators/base_validator.rb
128
+ - lib/castkit/validators/base.rb
129
+ - lib/castkit/validators/boolean_validator.rb
130
+ - lib/castkit/validators/collection_validator.rb
131
+ - lib/castkit/validators/float_validator.rb
132
+ - lib/castkit/validators/integer_validator.rb
109
133
  - lib/castkit/validators/numeric_validator.rb
110
134
  - lib/castkit/validators/string_validator.rb
111
135
  - lib/castkit/version.rb
136
+ - lib/generators/base.rb
137
+ - lib/generators/contract.rb
138
+ - lib/generators/data_object.rb
139
+ - lib/generators/plugin.rb
140
+ - lib/generators/serializer.rb
141
+ - lib/generators/templates/contract.rb.tt
142
+ - lib/generators/templates/contract_spec.rb.tt
143
+ - lib/generators/templates/data_object.rb.tt
144
+ - lib/generators/templates/data_object_spec.rb.tt
145
+ - lib/generators/templates/plugin.rb.tt
146
+ - lib/generators/templates/plugin_spec.rb.tt
147
+ - lib/generators/templates/serializer.rb.tt
148
+ - lib/generators/templates/serializer_spec.rb.tt
149
+ - lib/generators/templates/type.rb.tt
150
+ - lib/generators/templates/type_spec.rb.tt
151
+ - lib/generators/templates/validator.rb.tt
152
+ - lib/generators/templates/validator_spec.rb.tt
153
+ - lib/generators/type.rb
154
+ - lib/generators/validator.rb
112
155
  - sig/castkit.rbs
113
156
  homepage: https://github.com/bnlucas/castkit
114
157
  licenses:
@@ -1,154 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "serializer"
4
-
5
- module Castkit
6
- # Default serializer for Castkit::DataObject instances.
7
- #
8
- # Serializes attributes into a plain Ruby hash, applying access rules, nil/blank filtering,
9
- # and nested structure handling. The output format supports JSON-compatible structures
10
- # and respects the class-level serialization configuration.
11
- class DefaultSerializer < Castkit::Serializer
12
- # @return [Hash{Symbol => Castkit::Attribute}] the attributes to serialize
13
- attr_reader :attributes
14
-
15
- # @return [Hash{Symbol => Object}] unrecognized attributes captured during deserialization
16
- attr_reader :unknown_attributes
17
-
18
- # @return [Hash] serialization config flags like :root, :ignore_nil, :allow_unknown
19
- attr_reader :options
20
-
21
- # Serializes the object to a hash.
22
- #
23
- # Includes unknown attributes if configured, and wraps in a root key if defined.
24
- #
25
- # @return [Hash] the fully serialized result
26
- def call
27
- result = serialize_attributes
28
- result.merge!(unknown_attributes) if options[:allow_unknown]
29
-
30
- options[:root] ? { options[:root].to_sym => result } : result
31
- end
32
-
33
- private
34
-
35
- # Initializes the serializer.
36
- #
37
- # @param obj [Castkit::DataObject] the object to serialize
38
- # @param visited [Set, nil] tracks circular references
39
- def initialize(obj, visited: nil)
40
- super
41
-
42
- @skip_flag = "__castkit_#{obj.object_id}"
43
- @attributes = obj.class.attributes.freeze
44
- @unknown_attributes = obj.unknown_attributes.freeze
45
- @options = {
46
- root: obj.class.root,
47
- ignore_nil: obj.class.ignore_nil || false,
48
- allow_unknown: obj.class.allow_unknown || false
49
- }
50
- end
51
-
52
- # Serializes all defined attributes.
53
- #
54
- # @return [Hash] serialized attribute key-value pairs
55
- def serialize_attributes
56
- attributes.values.each_with_object({}) do |attribute, hash|
57
- next if attribute.skip_serialization?
58
-
59
- serialized_value = serialize_attribute(attribute)
60
- next if serialized_value == @skip_flag
61
-
62
- assign_attribute_key!(attribute, serialized_value, hash)
63
- end
64
- end
65
-
66
- # Serializes a single attribute.
67
- #
68
- # @param attribute [Castkit::Attribute]
69
- # @return [Object] the serialized value or skip flag
70
- def serialize_attribute(attribute)
71
- value = obj.public_send(attribute.field)
72
- return @skip_flag if skip_nil?(attribute, value)
73
-
74
- serialized_value = process_attribute(attribute, value)
75
- return @skip_flag if skip_blank?(attribute, serialized_value)
76
-
77
- serialized_value
78
- end
79
-
80
- # Delegates serialization based on type.
81
- #
82
- # @param attribute [Castkit::Attribute]
83
- # @param value [Object]
84
- # @return [Object]
85
- def process_attribute(attribute, value)
86
- if attribute.dataobject?
87
- serialize_dataobject(attribute, value)
88
- elsif attribute.dataobject_collection?
89
- Array(value).map { |v| serialize_dataobject(attribute, v) }
90
- else
91
- type = Array(attribute.type).first
92
- Castkit.type_serializer(type).call(value)
93
- end
94
- end
95
-
96
- # Assigns value into nested hash structure based on key path.
97
- #
98
- # @param attribute [Castkit::Attribute]
99
- # @param value [Object]
100
- # @param hash [Hash]
101
- # @return [void]
102
- def assign_attribute_key!(attribute, value, hash)
103
- key_path = attribute.key_path
104
- last = key_path.pop
105
- current = hash
106
-
107
- key_path.each do |key|
108
- current[key] ||= {}
109
- current = current[key]
110
- end
111
-
112
- current[last] = value
113
- end
114
-
115
- # Whether to skip serialization for nil values.
116
- #
117
- # @param attribute [Castkit::Attribute]
118
- # @param value [Object]
119
- # @return [Boolean]
120
- def skip_nil?(attribute, value)
121
- value.nil? && (attribute.ignore_nil? || options[:ignore_nil])
122
- end
123
-
124
- # Whether to skip serialization for blank values.
125
- #
126
- # @param attribute [Castkit::Attribute]
127
- # @param value [Object]
128
- # @return [Boolean]
129
- def skip_blank?(attribute, value)
130
- blank?(value) && (attribute.ignore_blank? || options[:ignore_blank])
131
- end
132
-
133
- # True if value is nil or empty.
134
- #
135
- # @param value [Object]
136
- # @return [Boolean]
137
- def blank?(value)
138
- value.nil? || (value.respond_to?(:empty?) && value.empty?)
139
- end
140
-
141
- # Serializes a DataObject using the proper serializer.
142
- #
143
- # @param attribute [Castkit::Attribute]
144
- # @param value [Castkit::DataObject]
145
- # @return [Object]
146
- def serialize_dataobject(attribute, value)
147
- serializer = attribute.options[:serializer]
148
- serializer ||= value.class.serializer
149
- serializer ||= Castkit::DefaultSerializer
150
-
151
- serializer.call(value, visited: visited)
152
- end
153
- end
154
- end
@@ -1,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "set"
4
-
5
- module Castkit
6
- # Abstract base class for defining custom serializers for Castkit::DataObject instances.
7
- #
8
- # Handles circular reference detection and provides a consistent `call` API.
9
- #
10
- # Subclasses must implement an instance method `#call` that returns a hash-like representation.
11
- #
12
- # @example Usage
13
- # class CustomSerializer < Castkit::Serializer
14
- # private
15
- #
16
- # def call
17
- # { type: obj.class.name, data: obj.to_h }
18
- # end
19
- # end
20
- #
21
- # CustomSerializer.call(user_dto)
22
- class Serializer
23
- class << self
24
- # Entrypoint for serializing an object.
25
- #
26
- # @param obj [Castkit::DataObject] the object to serialize
27
- # @param visited [Set, nil] used to track visited object IDs
28
- # @return [Object] result of custom serialization
29
- def call(obj, visited: nil)
30
- new(obj, visited: visited).send(:serialize)
31
- end
32
- end
33
-
34
- # @return [Castkit::DataObject] the object being serialized
35
- attr_reader :obj
36
-
37
- protected
38
-
39
- # Fallback to the default serializer.
40
- #
41
- # @return [Hash]
42
- def serialize_with_default
43
- Castkit::DefaultSerializer.call(obj, visited: visited)
44
- end
45
-
46
- private
47
-
48
- # @return [Set<Integer>] a set of visited object IDs to detect circular references
49
- attr_reader :visited
50
-
51
- # Initializes the serializer instance.
52
- #
53
- # @param obj [Castkit::DataObject]
54
- # @param visited [Set, nil]
55
- def initialize(obj, visited: nil)
56
- @obj = obj
57
- @visited = visited || Set.new
58
- end
59
-
60
- # Subclasses must override this method to implement serialization logic.
61
- #
62
- # @raise [NotImplementedError]
63
- # @return [Object]
64
- def call
65
- raise NotImplementedError, "#{self.class.name} must implement `#call`"
66
- end
67
-
68
- # Wraps the actual serialization logic with circular reference detection.
69
- #
70
- # @return [Object]
71
- # @raise [Castkit::SerializationError] if a circular reference is detected
72
- def serialize
73
- check_circular_reference!
74
- visited << obj.object_id
75
-
76
- result = call
77
- visited.delete(obj.object_id)
78
-
79
- result
80
- end
81
-
82
- # Raises if the object has already been visited (circular reference).
83
- #
84
- # @raise [Castkit::SerializationError]
85
- # @return [void]
86
- def check_circular_reference!
87
- return unless visited.include?(obj.object_id)
88
-
89
- raise Castkit::SerializationError, "Circular reference detected for #{obj.class}"
90
- end
91
- end
92
- end
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Castkit
4
- module Validators
5
- # Abstract base class for all attribute validators.
6
- #
7
- # Validators ensure that a value conforms to specific rules (e.g., type, format, range).
8
- # Subclasses must implement the instance method `#call`.
9
- #
10
- # @abstract
11
- class BaseValidator
12
- class << self
13
- # Invokes the validator with the given arguments.
14
- #
15
- # @param value [Object] the attribute value to validate
16
- # @param options [Hash] the attribute options (e.g., `min`, `max`, `format`)
17
- # @param context [Symbol, String, Hash] the attribute name or context for error reporting
18
- # @return [void]
19
- # @raise [Castkit::AttributeError] if validation fails
20
- def call(value, options:, context:)
21
- new.call(value, options: options, context: context)
22
- end
23
- end
24
-
25
- # Validates the attribute value.
26
- #
27
- # @abstract Override in subclasses.
28
- #
29
- # @param value [Object] the attribute value to validate
30
- # @param options [Hash] the attribute options
31
- # @param context [Symbol, String, Hash] the attribute name or context
32
- # @return [void]
33
- # @raise [NotImplementedError] unless implemented in a subclass
34
- def call(value, options:, context:)
35
- raise NotImplementedError, "#{self.class.name} must implement `#call`"
36
- end
37
- end
38
- end
39
- end