castkit 0.1.2 → 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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec_status +195 -219
  3. data/CHANGELOG.md +42 -0
  4. data/README.md +744 -83
  5. data/castkit.gemspec +1 -0
  6. data/lib/castkit/attribute.rb +6 -24
  7. data/lib/castkit/castkit.rb +61 -10
  8. data/lib/castkit/cli/generate.rb +98 -0
  9. data/lib/castkit/cli/list.rb +200 -0
  10. data/lib/castkit/cli/main.rb +43 -0
  11. data/lib/castkit/cli.rb +24 -0
  12. data/lib/castkit/configuration.rb +116 -46
  13. data/lib/castkit/contract/base.rb +168 -0
  14. data/lib/castkit/contract/data_object.rb +62 -0
  15. data/lib/castkit/contract/result.rb +74 -0
  16. data/lib/castkit/contract/validator.rb +248 -0
  17. data/lib/castkit/contract.rb +67 -0
  18. data/lib/castkit/{data_object_extensions → core}/attribute_types.rb +21 -7
  19. data/lib/castkit/{data_object_extensions → core}/attributes.rb +8 -3
  20. data/lib/castkit/core/config.rb +74 -0
  21. data/lib/castkit/core/registerable.rb +59 -0
  22. data/lib/castkit/data_object.rb +56 -67
  23. data/lib/castkit/error.rb +15 -3
  24. data/lib/castkit/ext/attribute/access.rb +67 -0
  25. data/lib/castkit/ext/attribute/error_handling.rb +63 -0
  26. data/lib/castkit/ext/attribute/options.rb +142 -0
  27. data/lib/castkit/ext/attribute/validation.rb +85 -0
  28. data/lib/castkit/ext/data_object/contract.rb +96 -0
  29. data/lib/castkit/ext/data_object/deserialization.rb +167 -0
  30. data/lib/castkit/ext/data_object/plugins.rb +86 -0
  31. data/lib/castkit/ext/data_object/serialization.rb +61 -0
  32. data/lib/castkit/inflector.rb +47 -0
  33. data/lib/castkit/plugins.rb +82 -0
  34. data/lib/castkit/serializers/base.rb +94 -0
  35. data/lib/castkit/serializers/default_serializer.rb +156 -0
  36. data/lib/castkit/types/base.rb +122 -0
  37. data/lib/castkit/types/boolean.rb +47 -0
  38. data/lib/castkit/types/collection.rb +35 -0
  39. data/lib/castkit/types/date.rb +34 -0
  40. data/lib/castkit/types/date_time.rb +34 -0
  41. data/lib/castkit/types/float.rb +46 -0
  42. data/lib/castkit/types/integer.rb +46 -0
  43. data/lib/castkit/types/string.rb +44 -0
  44. data/lib/castkit/types.rb +15 -0
  45. data/lib/castkit/validators/base.rb +59 -0
  46. data/lib/castkit/validators/boolean_validator.rb +39 -0
  47. data/lib/castkit/validators/collection_validator.rb +29 -0
  48. data/lib/castkit/validators/float_validator.rb +31 -0
  49. data/lib/castkit/validators/integer_validator.rb +31 -0
  50. data/lib/castkit/validators/numeric_validator.rb +2 -2
  51. data/lib/castkit/validators/string_validator.rb +3 -4
  52. data/lib/castkit/version.rb +1 -1
  53. data/lib/castkit.rb +2 -0
  54. data/lib/generators/base.rb +97 -0
  55. data/lib/generators/contract.rb +68 -0
  56. data/lib/generators/data_object.rb +48 -0
  57. data/lib/generators/plugin.rb +25 -0
  58. data/lib/generators/serializer.rb +28 -0
  59. data/lib/generators/templates/contract.rb.tt +24 -0
  60. data/lib/generators/templates/contract_spec.rb.tt +76 -0
  61. data/lib/generators/templates/data_object.rb.tt +15 -0
  62. data/lib/generators/templates/data_object_spec.rb.tt +36 -0
  63. data/lib/generators/templates/plugin.rb.tt +37 -0
  64. data/lib/generators/templates/plugin_spec.rb.tt +18 -0
  65. data/lib/generators/templates/serializer.rb.tt +24 -0
  66. data/lib/generators/templates/serializer_spec.rb.tt +14 -0
  67. data/lib/generators/templates/type.rb.tt +55 -0
  68. data/lib/generators/templates/type_spec.rb.tt +42 -0
  69. data/lib/generators/templates/validator.rb.tt +26 -0
  70. data/lib/generators/templates/validator_spec.rb.tt +23 -0
  71. data/lib/generators/type.rb +29 -0
  72. data/lib/generators/validator.rb +41 -0
  73. metadata +74 -15
  74. data/lib/castkit/attribute_extensions/access.rb +0 -65
  75. data/lib/castkit/attribute_extensions/casting.rb +0 -147
  76. data/lib/castkit/attribute_extensions/error_handling.rb +0 -83
  77. data/lib/castkit/attribute_extensions/options.rb +0 -131
  78. data/lib/castkit/attribute_extensions/serialization.rb +0 -89
  79. data/lib/castkit/attribute_extensions/validation.rb +0 -72
  80. data/lib/castkit/data_object_extensions/config.rb +0 -113
  81. data/lib/castkit/data_object_extensions/deserialization.rb +0 -110
  82. data/lib/castkit/default_serializer.rb +0 -123
  83. data/lib/castkit/serializer.rb +0 -92
  84. data/lib/castkit/validators.rb +0 -4
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../validators/collection_validator"
5
+
6
+ module Castkit
7
+ module Types
8
+ # Type definition for `:array` attributes.
9
+ #
10
+ # Wraps any value in an array using `Array(value)` coercion. This ensures consistent array representation
11
+ # even if the input is a single value or nil.
12
+ #
13
+ # This class is used internally by Castkit when an attribute is defined with:
14
+ # `array :tags, of: :string`
15
+ class Collection < Base
16
+ # Deserializes the value into an array using `Array(value)`.
17
+ #
18
+ # @param value [Object]
19
+ # @return [::Array]
20
+ def deserialize(value)
21
+ Array(value)
22
+ end
23
+
24
+ # Validates the Array value.
25
+ #
26
+ # @param value [Object]
27
+ # @param options [Hash] validation options
28
+ # @param context [Symbol, String, nil] attribute context for error messages
29
+ # @return [void]
30
+ def validate!(value, options: {}, context: nil)
31
+ Castkit::Validators::CollectionValidator.call(value, options: options, context: context)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require_relative "base"
5
+
6
+ module Castkit
7
+ module Types
8
+ # Type definition for `:date` attributes.
9
+ #
10
+ # Handles deserialization from strings and other input into `Date` objects,
11
+ # and serializes `Date` values into ISO8601 strings.
12
+ #
13
+ # This class is used internally by Castkit when an attribute is defined with:
14
+ # `date :published_on`
15
+ class Date < Base
16
+ # Deserializes the input value to a `Date` instance.
17
+ #
18
+ # @param value [Object]
19
+ # @return [::Date]
20
+ # @raise [ArgumentError] if parsing fails
21
+ def deserialize(value)
22
+ ::Date.parse(value.to_s)
23
+ end
24
+
25
+ # Serializes a `Date` object to ISO8601 string format.
26
+ #
27
+ # @param value [::Date]
28
+ # @return [String]
29
+ def serialize(value)
30
+ value.iso8601
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require_relative "base"
5
+
6
+ module Castkit
7
+ module Types
8
+ # Type definition for `:datetime` attributes.
9
+ #
10
+ # Handles deserialization from strings and other input into `DateTime` objects,
11
+ # and serializes `DateTime` values into ISO8601 strings.
12
+ #
13
+ # This class is used internally by Castkit when an attribute is defined with:
14
+ # `datetime :published_ad`
15
+ class DateTime < Base
16
+ # Deserializes the input value to a `DateTime` instance.
17
+ #
18
+ # @param value [Object]
19
+ # @return [::DateTime]
20
+ # @raise [ArgumentError] if parsing fails
21
+ def deserialize(value)
22
+ ::DateTime.parse(value.to_s)
23
+ end
24
+
25
+ # Serializes a `DateTime` object to ISO8601 string format.
26
+ #
27
+ # @param value [::DateTime]
28
+ # @return [String]
29
+ def serialize(value)
30
+ value.iso8601
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../validators/float_validator"
5
+
6
+ module Castkit
7
+ module Types
8
+ # Type definition for `:integer` attributes.
9
+ #
10
+ # Handles deserialization from raw input (e.g., strings, floats) to Float,
11
+ # applies optional numeric validation rules (e.g., `min`, `max`), and returns
12
+ # the value unchanged during serialization.
13
+ #
14
+ # This class is used internally by Castkit when an attribute is defined with:
15
+ # `integer :count`
16
+ class Float < Base
17
+ # Deserializes the input value to an Float.
18
+ #
19
+ # @param value [Object]
20
+ # @return [Float]
21
+ def deserialize(value)
22
+ value.to_f
23
+ end
24
+
25
+ # Serializes the Float value.
26
+ #
27
+ # @param value [Float]
28
+ # @return [Float]
29
+ def serialize(value)
30
+ value
31
+ end
32
+
33
+ # Validates the Float value using Castkit's FloatValidator.
34
+ #
35
+ # Supports options like `min:` and `max:`.
36
+ #
37
+ # @param value [Object]
38
+ # @param options [Hash] validation options
39
+ # @param context [Symbol, String] attribute context for error messages
40
+ # @return [void]
41
+ def validate!(value, options: {}, context: {})
42
+ Castkit::Validators::FloatValidator.call(value, options: options, context: context)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../validators/integer_validator"
5
+
6
+ module Castkit
7
+ module Types
8
+ # Type definition for `:integer` attributes.
9
+ #
10
+ # Handles deserialization from raw input (e.g., strings, floats) to Integer,
11
+ # applies optional numeric validation rules (e.g., `min`, `max`), and returns
12
+ # the value unchanged during serialization.
13
+ #
14
+ # This class is used internally by Castkit when an attribute is defined with:
15
+ # `integer :count`
16
+ class Integer < Base
17
+ # Deserializes the input value to an Integer.
18
+ #
19
+ # @param value [Object]
20
+ # @return [Integer]
21
+ def deserialize(value)
22
+ value.to_i
23
+ end
24
+
25
+ # Serializes the Integer value.
26
+ #
27
+ # @param value [Integer]
28
+ # @return [Integer]
29
+ def serialize(value)
30
+ value
31
+ end
32
+
33
+ # Validates the Integer value using Castkit's IntegerValidator.
34
+ #
35
+ # Supports options like `min:` and `max:`.
36
+ #
37
+ # @param value [Object]
38
+ # @param options [Hash] validation options
39
+ # @param context [Symbol, String] attribute context for error messages
40
+ # @return [void]
41
+ def validate!(value, options: {}, context: {})
42
+ Castkit::Validators::IntegerValidator.call(value, options: options, context: context)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require_relative "../validators/string_validator"
5
+
6
+ module Castkit
7
+ module Types
8
+ # Type definition for `:string` attributes.
9
+ #
10
+ # Coerces any input to a string using `to_s`, and validates that the resulting value is a `String`.
11
+ # Supports optional format validation via a `:format` option (Regexp or Proc).
12
+ #
13
+ # This class is used internally by Castkit when an attribute is defined with:
14
+ # `string :id`
15
+ class String < Base
16
+ # Deserializes the value by coercing it to a string using `to_s`.
17
+ #
18
+ # @param value [Object]
19
+ # @return [String]
20
+ def deserialize(value)
21
+ value.to_s
22
+ end
23
+
24
+ # Serializes the value as-is.
25
+ #
26
+ # @param value [String]
27
+ # @return [String]
28
+ def serialize(value)
29
+ value
30
+ end
31
+
32
+ # Validates the value is a `String` and optionally matches a format.
33
+ #
34
+ # @param value [Object]
35
+ # @param options [Hash] validation options (e.g., `format: /regex/`)
36
+ # @param context [Symbol, String]
37
+ # @raise [Castkit::AttributeError] if validation fails
38
+ # @return [void]
39
+ def validate!(value, options: {}, context: {})
40
+ Castkit::Validators::StringValidator.call(value, options: options, context: context)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "types/base"
4
+ require_relative "types/collection"
5
+ require_relative "types/boolean"
6
+ require_relative "types/date"
7
+ require_relative "types/date_time"
8
+ require_relative "types/float"
9
+ require_relative "types/integer"
10
+ require_relative "types/string"
11
+
12
+ module Castkit
13
+ # Object types supported natively by Castkit.
14
+ module Types; end
15
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Castkit
4
+ module Validators
5
+ # Abstract base class for all attribute validators.
6
+ #
7
+ # Validators are responsible for enforcing value constraints (e.g., type checks,
8
+ # format rules, numerical bounds). All validators must inherit from this base class
9
+ # and implement the `#call` instance method.
10
+ #
11
+ # Supports both instance-level and class-level invocation via `.call`.
12
+ #
13
+ # @abstract Subclasses must implement `#call`.
14
+ class Base
15
+ class << self
16
+ # Entry point for validating a value using the validator class.
17
+ #
18
+ # Instantiates the validator and invokes `#call` with the provided arguments.
19
+ #
20
+ # @param value [Object] the value to validate
21
+ # @param options [Hash] additional validation options (e.g., `min`, `max`, `format`)
22
+ # @param context [Symbol, String, Hash] context for the validation (usually the attribute name)
23
+ # @return [void]
24
+ # @raise [Castkit::AttributeError] if validation fails and `raise_type_errors` is true
25
+ def call(value, options:, context:)
26
+ new.call(value, options: options, context: context)
27
+ end
28
+ end
29
+
30
+ # Validates the value.
31
+ #
32
+ # @abstract
33
+ # @param value [Object] the value to validate
34
+ # @param options [Hash] validation options (e.g., min, max, format)
35
+ # @param context [Symbol, String, Hash] context for validation errors
36
+ # @return [void]
37
+ # @raise [NotImplementedError] if not implemented in subclass
38
+ def call(value, options:, context:)
39
+ raise NotImplementedError, "#{self.class.name} must implement `#call`"
40
+ end
41
+
42
+ protected
43
+
44
+ # Emits or raises a type error depending on global configuration.
45
+ #
46
+ # @param type [Symbol] the expected type (e.g., `:integer`)
47
+ # @param value [Object, nil] the received value
48
+ # @param context [Symbol, String, nil] context to include in error messages
49
+ # @raise [Castkit::AttributeError] if `raise_type_errors` is enabled
50
+ # @return [void]
51
+ def type_error!(type, value, context: nil)
52
+ message = "#{context || "value"} must be a #{type}, got #{value}"
53
+ raise Castkit::AttributeError, message if Castkit.configuration.raise_type_errors
54
+
55
+ Castkit.warning "[Castkit] #{message}" if Castkit.configuration.enable_warnings
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Castkit
6
+ module Validators
7
+ # Validator for boolean attributes.
8
+ #
9
+ # Accepts various representations of boolean values, including strings and integers.
10
+ # Converts common truthy/falsy string values into booleans, otherwise raises a type error.
11
+ #
12
+ # This validator is typically used internally by `Castkit::Types::Boolean`.
13
+ #
14
+ # @example
15
+ # validator = Castkit::Validators::BooleanValidator.new
16
+ # validator.call("true", _options: {}, context: :enabled) # => true
17
+ # validator.call("0", _options: {}, context: :enabled) # => false
18
+ # validator.call("nope", _options: {}, context: :enabled) # raises Castkit::AttributeError
19
+ class BooleanValidator
20
+ # Validates the Boolean value.
21
+ #
22
+ # @param value [Object] the input to validate
23
+ # @param _options [Hash] unused, provided for consistency with other validators
24
+ # @param context [Symbol, String] the attribute name or path for error messages
25
+ # @return [Boolean]
26
+ # @raise [Castkit::AttributeError] if the value is not a recognizable boolean
27
+ def call(value, _options:, context:)
28
+ case value.to_s.downcase
29
+ when "true", "1"
30
+ true
31
+ when "false", "0"
32
+ false
33
+ else
34
+ type_error!(:boolean, value, context: context)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../validators/base"
4
+
5
+ module Castkit
6
+ module Validators
7
+ # Validator for array (collection) attributes.
8
+ #
9
+ # Ensures that the provided value is an instance of `Array`. This validator is
10
+ # typically used by `Castkit::Types::Collection` for attributes defined as arrays.
11
+ #
12
+ # @example
13
+ # validator = Castkit::Validators::CollectionValidator.new
14
+ # validator.call([1, 2, 3], _options: {}, context: :tags) # => passes
15
+ # validator.call("foo", _options: {}, context: :tags) # raises Castkit::AttributeError
16
+ class CollectionValidator < Castkit::Validators::Base
17
+ # Validates that the value is an Array.
18
+ #
19
+ # @param value [Object] the value to validate
20
+ # @param _options [Hash] unused, for interface consistency
21
+ # @param context [Symbol, String] the field or context for error messaging
22
+ # @return [void]
23
+ # @raise [Castkit::AttributeError] if value is not an Array
24
+ def call(value, _options:, context:)
25
+ type_error!(:array, value, context: context) unless value.is_a?(::Array)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "numeric_validator"
4
+
5
+ module Castkit
6
+ module Validators
7
+ # Validator for Float attributes.
8
+ #
9
+ # Ensures the value is a `Float`, and applies any numeric bounds (`min`, `max`)
10
+ # defined in the attribute options. Inherits shared logic from `NumericValidator`.
11
+ #
12
+ # @example
13
+ # validator = Castkit::Validators::FloatValidator.new
14
+ # validator.call(3.14, options: { min: 0.0 }, context: :price) # => passes
15
+ # validator.call(42, options: {}, context: :price) # raises Castkit::AttributeError
16
+ class FloatValidator < Castkit::Validators::NumericValidator
17
+ # Validates that the value is a Float and within optional bounds.
18
+ #
19
+ # @param value [Object, nil] the value to validate
20
+ # @param options [Hash] validation options (e.g., `min`, `max`)
21
+ # @param context [Symbol, String] the attribute name or key for error messages
22
+ # @raise [Castkit::AttributeError] if value is not a Float or out of range
23
+ # @return [void]
24
+ def call(value, options:, context:)
25
+ return type_error!(:float, value, context: context) unless value.is_a?(::Float)
26
+
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "numeric_validator"
4
+
5
+ module Castkit
6
+ module Validators
7
+ # Validator for Integer attributes.
8
+ #
9
+ # Ensures the value is an instance of `Integer` and optionally checks numerical constraints
10
+ # such as `min` and `max`, inherited from `Castkit::Validators::NumericValidator`.
11
+ #
12
+ # @example Validating an Integer attribute
13
+ # IntegerValidator.call(42, options: { min: 10, max: 100 }, context: :count)
14
+ #
15
+ # @see Castkit::Validators::NumericValidator
16
+ class IntegerValidator < Castkit::Validators::NumericValidator
17
+ # Validates the Integer value.
18
+ #
19
+ # @param value [Object, nil] the value to validate
20
+ # @param options [Hash] validation options (e.g., `min`, `max`)
21
+ # @param context [Symbol, String] the attribute name or context for error messages
22
+ # @raise [Castkit::AttributeError] if the value is not an Integer or fails validation rules
23
+ # @return [void]
24
+ def call(value, options:, context:)
25
+ return type_error!(:integer, value, context: context) unless value.is_a?(::Integer)
26
+
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../validator"
3
+ require_relative "base"
4
4
 
5
5
  module Castkit
6
6
  module Validators
7
7
  # Validates that a numeric value falls within the allowed range.
8
8
  #
9
9
  # Supports `:min` and `:max` options to enforce bounds.
10
- class NumericValidator < Castkit::Validator
10
+ class NumericValidator < Castkit::Validators::Base
11
11
  # Validates the numeric value.
12
12
  #
13
13
  # @param value [Numeric] the value to validate
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../validator"
3
+ require_relative "base"
4
4
 
5
5
  module Castkit
6
6
  module Validators
7
7
  # Validates that a value is a String and optionally conforms to a format.
8
8
  #
9
9
  # Supports format validation using a Regexp or a custom Proc.
10
- class StringValidator < Castkit::Validator
10
+ class StringValidator < Castkit::Validators::Base
11
11
  # Validates the string value.
12
12
  #
13
13
  # @param value [Object] the value to validate
@@ -16,8 +16,7 @@ module Castkit
16
16
  # @raise [Castkit::AttributeError] if value is not a string or fails format validation
17
17
  # @return [void]
18
18
  def call(value, options:, context:)
19
- raise Castkit::AttributeError, "#{context} must be a String" unless value.is_a?(String)
20
-
19
+ return type_error!(:string, value, context: context) unless value.is_a?(::String)
21
20
  return unless options[:format]
22
21
 
23
22
  case options[:format]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Castkit
4
- VERSION = "0.1.2"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/castkit.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "castkit/version"
4
+ require_relative "castkit/attribute"
5
+ require_relative "castkit/contract"
4
6
  require_relative "castkit/data_object"
5
7
 
6
8
  # Castkit is a lightweight, type-safe data object system for Ruby.
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor/group"
4
+ require "castkit/inflector"
5
+
6
+ module Castkit
7
+ module Generators
8
+ # Abstract base class for all Castkit generators.
9
+ #
10
+ # Provides standard behavior for generating a component and optional spec file from
11
+ # ERB templates. Subclasses must define a `component` (e.g., `:type`, `:contract`)
12
+ # and may override `config` or `default_values`.
13
+ #
14
+ # Template variables are injected using the `config` hash, which includes:
15
+ # - `:name` – underscored version of the component name
16
+ # - `:class_name` – PascalCase version of the component name
17
+ #
18
+ # @abstract
19
+ class Base < Thor::Group
20
+ include Thor::Actions
21
+
22
+ class << self
23
+ # Sets or retrieves the component type (e.g., :type, :data_object).
24
+ #
25
+ # @param value [Symbol, nil]
26
+ # @return [Symbol]
27
+ def component(value = nil)
28
+ value.nil? ? @component : (@component = value)
29
+ end
30
+
31
+ # @return [String] the root path to look for templates
32
+ def source_root
33
+ File.expand_path("templates", __dir__)
34
+ end
35
+ end
36
+
37
+ argument :name, desc: "The name of the component to generate"
38
+ class_option :spec, type: :boolean, default: true, desc: "Also generate a spec file"
39
+
40
+ # Creates the main component file using a template.
41
+ #
42
+ # Template: `component.rb.tt`
43
+ # Target: `lib/castkit/#{component}s/#{name}.rb`
44
+ #
45
+ # @return [void]
46
+ def create_component
47
+ template(
48
+ "#{self.class.component}.rb.tt",
49
+ "lib/castkit/#{self.class.component}s/#{config[:name]}.rb", **config
50
+ )
51
+ end
52
+
53
+ # Creates the associated spec file, if enabled.
54
+ #
55
+ # Template: `component_spec.rb.tt`
56
+ # Target: `spec/castkit/#{component}s/#{name}_spec.rb`
57
+ #
58
+ # @return [void]
59
+ def create_spec
60
+ return unless options[:spec]
61
+
62
+ template(
63
+ "#{self.class.component}_spec.rb.tt",
64
+ "spec/castkit/#{self.class.component}s/#{config[:name]}_spec.rb", **config
65
+ )
66
+ end
67
+
68
+ private
69
+
70
+ # Default values for test inputs based on type.
71
+ #
72
+ # These are used in spec templates to provide sample data.
73
+ #
74
+ # @return [Hash{Symbol => Object}]
75
+ def default_values
76
+ {
77
+ string: '"example"',
78
+ integer: 42,
79
+ float: 3.14,
80
+ boolean: true,
81
+ array: [],
82
+ hash: {}
83
+ }
84
+ end
85
+
86
+ # Returns the default config hash passed into templates.
87
+ #
88
+ # @return [Hash{Symbol => Object}]
89
+ def config
90
+ {
91
+ name: Castkit::Inflector.underscore(name),
92
+ class_name: Castkit::Inflector.pascalize(name)
93
+ }
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,68 @@
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 Castkit contracts.
10
+ #
11
+ # Generates a contract class and optionally a corresponding spec file.
12
+ # Accepts an optional list of attribute definitions in the form `name:type`.
13
+ #
14
+ # Example:
15
+ # $ castkit generate contract User name:string age:integer
16
+ #
17
+ # This will generate:
18
+ # - lib/castkit/contracts/user.rb
19
+ # - spec/castkit/contracts/user_spec.rb
20
+ #
21
+ # @see Castkit::Generators::Base
22
+ class Contract < Castkit::Generators::Base
23
+ component :contract
24
+
25
+ argument :fields, type: :array, default: [], desc: "Attribute definitions (e.g., name:string age:integer)"
26
+
27
+ private
28
+
29
+ # @return [Hash] configuration passed into templates
30
+ def config
31
+ super.merge(
32
+ attributes: parsed_fields,
33
+ default_values: default_values,
34
+ invalid_types: invalid_types
35
+ )
36
+ end
37
+
38
+ # Parses `name:type` fields into attribute definitions.
39
+ #
40
+ # @return [Array<Hash{Symbol => Object}>] list of parsed attribute hashes
41
+ def parsed_fields
42
+ fields.map do |field|
43
+ name, type = field.split(":")
44
+ { name: name, type: (type || "string").to_sym }
45
+ end
46
+ end
47
+
48
+ # Default "invalid" test values for each supported type.
49
+ #
50
+ # Used in generated specs to simulate bad input.
51
+ #
52
+ # @return [Hash{Symbol => Object}]
53
+ def invalid_types
54
+ {
55
+ string: true,
56
+ integer: '"invalid"',
57
+ float: '"bad"',
58
+ boolean: '"not_a_bool"',
59
+ date: 123,
60
+ datetime: [],
61
+ array: {},
62
+ hash: [],
63
+ uuid: 999
64
+ }
65
+ end
66
+ end
67
+ end
68
+ end