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.
- checksums.yaml +4 -4
- data/.rspec_status +195 -219
- data/CHANGELOG.md +42 -0
- data/README.md +744 -83
- data/castkit.gemspec +1 -0
- data/lib/castkit/attribute.rb +6 -24
- data/lib/castkit/castkit.rb +61 -10
- data/lib/castkit/cli/generate.rb +98 -0
- data/lib/castkit/cli/list.rb +200 -0
- data/lib/castkit/cli/main.rb +43 -0
- data/lib/castkit/cli.rb +24 -0
- data/lib/castkit/configuration.rb +116 -46
- data/lib/castkit/contract/base.rb +168 -0
- data/lib/castkit/contract/data_object.rb +62 -0
- data/lib/castkit/contract/result.rb +74 -0
- data/lib/castkit/contract/validator.rb +248 -0
- data/lib/castkit/contract.rb +67 -0
- data/lib/castkit/{data_object_extensions → core}/attribute_types.rb +21 -7
- data/lib/castkit/{data_object_extensions → core}/attributes.rb +8 -3
- data/lib/castkit/core/config.rb +74 -0
- data/lib/castkit/core/registerable.rb +59 -0
- data/lib/castkit/data_object.rb +56 -67
- data/lib/castkit/error.rb +15 -3
- data/lib/castkit/ext/attribute/access.rb +67 -0
- data/lib/castkit/ext/attribute/error_handling.rb +63 -0
- data/lib/castkit/ext/attribute/options.rb +142 -0
- data/lib/castkit/ext/attribute/validation.rb +85 -0
- data/lib/castkit/ext/data_object/contract.rb +96 -0
- data/lib/castkit/ext/data_object/deserialization.rb +167 -0
- data/lib/castkit/ext/data_object/plugins.rb +86 -0
- data/lib/castkit/ext/data_object/serialization.rb +61 -0
- data/lib/castkit/inflector.rb +47 -0
- data/lib/castkit/plugins.rb +82 -0
- data/lib/castkit/serializers/base.rb +94 -0
- data/lib/castkit/serializers/default_serializer.rb +156 -0
- data/lib/castkit/types/base.rb +122 -0
- data/lib/castkit/types/boolean.rb +47 -0
- data/lib/castkit/types/collection.rb +35 -0
- data/lib/castkit/types/date.rb +34 -0
- data/lib/castkit/types/date_time.rb +34 -0
- data/lib/castkit/types/float.rb +46 -0
- data/lib/castkit/types/integer.rb +46 -0
- data/lib/castkit/types/string.rb +44 -0
- data/lib/castkit/types.rb +15 -0
- data/lib/castkit/validators/base.rb +59 -0
- data/lib/castkit/validators/boolean_validator.rb +39 -0
- data/lib/castkit/validators/collection_validator.rb +29 -0
- data/lib/castkit/validators/float_validator.rb +31 -0
- data/lib/castkit/validators/integer_validator.rb +31 -0
- data/lib/castkit/validators/numeric_validator.rb +2 -2
- data/lib/castkit/validators/string_validator.rb +3 -4
- data/lib/castkit/version.rb +1 -1
- data/lib/castkit.rb +2 -0
- data/lib/generators/base.rb +97 -0
- data/lib/generators/contract.rb +68 -0
- data/lib/generators/data_object.rb +48 -0
- data/lib/generators/plugin.rb +25 -0
- data/lib/generators/serializer.rb +28 -0
- data/lib/generators/templates/contract.rb.tt +24 -0
- data/lib/generators/templates/contract_spec.rb.tt +76 -0
- data/lib/generators/templates/data_object.rb.tt +15 -0
- data/lib/generators/templates/data_object_spec.rb.tt +36 -0
- data/lib/generators/templates/plugin.rb.tt +37 -0
- data/lib/generators/templates/plugin_spec.rb.tt +18 -0
- data/lib/generators/templates/serializer.rb.tt +24 -0
- data/lib/generators/templates/serializer_spec.rb.tt +14 -0
- data/lib/generators/templates/type.rb.tt +55 -0
- data/lib/generators/templates/type_spec.rb.tt +42 -0
- data/lib/generators/templates/validator.rb.tt +26 -0
- data/lib/generators/templates/validator_spec.rb.tt +23 -0
- data/lib/generators/type.rb +29 -0
- data/lib/generators/validator.rb +41 -0
- metadata +74 -15
- data/lib/castkit/attribute_extensions/access.rb +0 -65
- data/lib/castkit/attribute_extensions/casting.rb +0 -147
- data/lib/castkit/attribute_extensions/error_handling.rb +0 -83
- data/lib/castkit/attribute_extensions/options.rb +0 -131
- data/lib/castkit/attribute_extensions/serialization.rb +0 -89
- data/lib/castkit/attribute_extensions/validation.rb +0 -72
- data/lib/castkit/data_object_extensions/config.rb +0 -113
- data/lib/castkit/data_object_extensions/deserialization.rb +0 -110
- data/lib/castkit/default_serializer.rb +0 -123
- data/lib/castkit/serializer.rb +0 -92
- data/lib/castkit/validators.rb +0 -4
@@ -1,113 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castkit
|
4
|
-
module DataObjectExtensions
|
5
|
-
# Provides per-class configuration for a Castkit::DataObject,
|
6
|
-
# including root key handling, strict mode, and unknown key behavior.
|
7
|
-
module Config
|
8
|
-
# Automatically extends class-level methods when included.
|
9
|
-
#
|
10
|
-
# @param base [Class]
|
11
|
-
def self.included(base)
|
12
|
-
base.extend(ClassMethods)
|
13
|
-
end
|
14
|
-
|
15
|
-
# Class-level configuration methods.
|
16
|
-
module ClassMethods
|
17
|
-
# Sets or retrieves the root key to wrap the object under during (de)serialization.
|
18
|
-
#
|
19
|
-
# @param value [String, Symbol, nil] optional root key
|
20
|
-
# @return [Symbol, nil]
|
21
|
-
def root(value = nil)
|
22
|
-
@root = value.to_s.strip.to_sym if value
|
23
|
-
@root
|
24
|
-
end
|
25
|
-
|
26
|
-
# Sets or retrieves whether to skip `nil` values in output.
|
27
|
-
#
|
28
|
-
# @param value [Boolean, nil]
|
29
|
-
# @return [Boolean, nil]
|
30
|
-
def ignore_nil(value = nil)
|
31
|
-
value.nil? ? @ignore_nil : (@ignore_nil = value)
|
32
|
-
end
|
33
|
-
|
34
|
-
# Sets or retrieves whether to skip blank values (`[]`, `{}`, `""`, etc.) in output.
|
35
|
-
#
|
36
|
-
# Defaults to true unless explicitly set to false.
|
37
|
-
#
|
38
|
-
# @param value [Boolean, nil]
|
39
|
-
# @return [Boolean]
|
40
|
-
def ignore_blank(value = nil)
|
41
|
-
@ignore_blank = value.nil? || value
|
42
|
-
end
|
43
|
-
|
44
|
-
# Sets or retrieves strict mode behavior.
|
45
|
-
#
|
46
|
-
# In strict mode, unknown keys during deserialization raise errors.
|
47
|
-
#
|
48
|
-
# @param value [Boolean, nil]
|
49
|
-
# @return [Boolean]
|
50
|
-
def strict(value = nil)
|
51
|
-
if value.nil?
|
52
|
-
@strict.nil? || @strict
|
53
|
-
else
|
54
|
-
@strict = value
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# Enables or disables ignoring unknown keys during deserialization.
|
59
|
-
#
|
60
|
-
# This is the inverse of `strict`.
|
61
|
-
#
|
62
|
-
# @param value [Boolean]
|
63
|
-
# @return [void]
|
64
|
-
def ignore_unknown(value = nil)
|
65
|
-
@strict = !value
|
66
|
-
end
|
67
|
-
|
68
|
-
# Sets or retrieves whether to emit warnings when unknown keys are encountered.
|
69
|
-
#
|
70
|
-
# @param value [Boolean, nil]
|
71
|
-
# @return [Boolean, nil]
|
72
|
-
def warn_on_unknown(value = nil)
|
73
|
-
value.nil? ? @warn_on_unknown : (@warn_on_unknown = value)
|
74
|
-
end
|
75
|
-
|
76
|
-
# Sets or retrieves whether to allow unknown keys when they are encountered.
|
77
|
-
#
|
78
|
-
# @param value [Boolean, nil]
|
79
|
-
# @return [Boolean, nil]
|
80
|
-
def allow_unknown(value = nil)
|
81
|
-
value.nil? ? @allow_unknown : (@allow_unknown = value)
|
82
|
-
end
|
83
|
-
|
84
|
-
# Returns a relaxed version of the current class with strict mode off.
|
85
|
-
#
|
86
|
-
# Useful for tolerant parsing scenarios.
|
87
|
-
#
|
88
|
-
# @param warn_on_unknown [Boolean]
|
89
|
-
# @return [Class] a subclass with relaxed rules
|
90
|
-
def relaxed(warn_on_unknown: true)
|
91
|
-
klass = Class.new(self)
|
92
|
-
klass.strict(false)
|
93
|
-
klass.warn_on_unknown(warn_on_unknown)
|
94
|
-
klass
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# Returns the root key for this instance.
|
99
|
-
#
|
100
|
-
# @return [Symbol]
|
101
|
-
def root_key
|
102
|
-
self.class.root.to_s.strip.to_sym
|
103
|
-
end
|
104
|
-
|
105
|
-
# Whether a root key is configured for this instance.
|
106
|
-
#
|
107
|
-
# @return [Boolean]
|
108
|
-
def root_key_set?
|
109
|
-
!self.class.root.to_s.strip.empty?
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
@@ -1,110 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Castkit
|
4
|
-
module DataObjectExtensions
|
5
|
-
# Adds deserialization support for Castkit::DataObject instances.
|
6
|
-
#
|
7
|
-
# Handles attribute loading, alias resolution, and unwrapped field extraction.
|
8
|
-
module Deserialization
|
9
|
-
# Hooks in class methods like `.from_hash` when included.
|
10
|
-
#
|
11
|
-
# @param base [Class]
|
12
|
-
def self.included(base)
|
13
|
-
base.extend(ClassMethods)
|
14
|
-
end
|
15
|
-
|
16
|
-
# Class-level deserialization helpers.
|
17
|
-
module ClassMethods
|
18
|
-
# Builds a new instance from a Hash, symbolizing keys as needed.
|
19
|
-
#
|
20
|
-
# @param hash [Hash]
|
21
|
-
# @return [Castkit::DataObject]
|
22
|
-
def from_hash(hash)
|
23
|
-
hash = hash.transform_keys { |k| k.respond_to?(:to_sym) ? k.to_sym : k }
|
24
|
-
new(hash)
|
25
|
-
end
|
26
|
-
|
27
|
-
# @!method from_h(hash)
|
28
|
-
# Alias for {.from_hash}
|
29
|
-
#
|
30
|
-
# @!method creator(hash)
|
31
|
-
# Alias for {.from_hash}
|
32
|
-
alias from_h from_hash
|
33
|
-
alias creator from_hash
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
# Loads attribute values from the given hash.
|
39
|
-
#
|
40
|
-
# Respects access control (e.g., `writeable?`) and uses `.load` for casting/validation.
|
41
|
-
#
|
42
|
-
# @param data [Hash]
|
43
|
-
# @return [void]
|
44
|
-
def deserialize_attributes!(data)
|
45
|
-
self.class.attributes.each do |field, attribute|
|
46
|
-
next if attribute.skip_deserialization?
|
47
|
-
|
48
|
-
value = fetch_attribute_key(data, attribute)
|
49
|
-
value = attribute.load(value, context: field)
|
50
|
-
|
51
|
-
instance_variable_set("@#{field}", value)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# Fetches the best matching value from the hash using attribute key and aliases.
|
56
|
-
#
|
57
|
-
# @param data [Hash]
|
58
|
-
# @param attribute [Castkit::Attribute]
|
59
|
-
# @return [Object]
|
60
|
-
def fetch_attribute_key(data, attribute)
|
61
|
-
attribute.key_path(with_aliases: true).each do |path|
|
62
|
-
value = path.reduce(data) { |memo, key| memo.is_a?(Hash) ? memo[key] : nil }
|
63
|
-
return value unless value.nil?
|
64
|
-
end
|
65
|
-
|
66
|
-
nil
|
67
|
-
end
|
68
|
-
|
69
|
-
# Extracts prefixed fields for unwrapped attributes and groups them under the original field key.
|
70
|
-
#
|
71
|
-
# @param data [Hash]
|
72
|
-
# @return [Hash] modified input hash with unwrapped values nested under their base field
|
73
|
-
def unwrap_prefixed_fields!(data)
|
74
|
-
self.class.attributes.each_value do |attribute|
|
75
|
-
next unless attribute.unwrapped?
|
76
|
-
|
77
|
-
unwrapped, keys_to_remove = unwrap_prefixed_values(data, attribute)
|
78
|
-
next if unwrapped.empty?
|
79
|
-
|
80
|
-
data[attribute.field] = unwrapped
|
81
|
-
keys_to_remove.each { |k| data.delete(k) }
|
82
|
-
end
|
83
|
-
|
84
|
-
data
|
85
|
-
end
|
86
|
-
|
87
|
-
# Returns the prefixed key-value pairs for a given unwrapped attribute.
|
88
|
-
#
|
89
|
-
# @param data [Hash]
|
90
|
-
# @param attribute [Castkit::Attribute]
|
91
|
-
# @return [Array<Hash, Array<Symbol>>] extracted key-value pairs and keys to delete
|
92
|
-
def unwrap_prefixed_values(data, attribute)
|
93
|
-
prefix = attribute.prefix.to_s
|
94
|
-
unwrapped_data = {}
|
95
|
-
keys_to_remove = []
|
96
|
-
|
97
|
-
data.each do |k, v|
|
98
|
-
k_str = k.to_s
|
99
|
-
next unless k_str.start_with?(prefix)
|
100
|
-
|
101
|
-
stripped = k_str.sub(prefix, "").to_sym
|
102
|
-
unwrapped_data[stripped] = v
|
103
|
-
keys_to_remove << k
|
104
|
-
end
|
105
|
-
|
106
|
-
[unwrapped_data, keys_to_remove]
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
@@ -1,123 +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 based on access rules, nil/blank filtering, and nested structure.
|
9
|
-
class DefaultSerializer < Castkit::Serializer
|
10
|
-
SKIP_ATTRIBUTE = :__castkit_skip_attribute
|
11
|
-
|
12
|
-
# @return [Hash{Symbol => Castkit::Attribute}] attributes to serialize
|
13
|
-
attr_reader :attributes
|
14
|
-
|
15
|
-
# @return [Hash{Symbol => Object}] attributes that are not predefined or known to the object
|
16
|
-
attr_reader :unknown_attributes
|
17
|
-
|
18
|
-
# @return [Hash] serialization options (root key, ignore_nil, allow_unknown, etc.)
|
19
|
-
attr_reader :options
|
20
|
-
|
21
|
-
# Returns the serialized object as a Hash.
|
22
|
-
#
|
23
|
-
# Includes root wrapping if configured. If `allow_unknown` is set to true, unknown attributes
|
24
|
-
# are merged into the final result.
|
25
|
-
#
|
26
|
-
# @return [Hash] The serialized object, potentially wrapped with a root key
|
27
|
-
def call
|
28
|
-
result = serialize_attributes
|
29
|
-
result.merge!(unknown_attributes) if options[:allow_unknown]
|
30
|
-
|
31
|
-
options[:root] ? { options[:root].to_sym => result } : result
|
32
|
-
end
|
33
|
-
|
34
|
-
private
|
35
|
-
|
36
|
-
# Initializes the serializer with the target object and context.
|
37
|
-
#
|
38
|
-
# @param raw [Castkit::DataObject] the object to serialize
|
39
|
-
# @param visited [Set, nil] used to detect circular references (default is nil)
|
40
|
-
def initialize(raw, visited: nil)
|
41
|
-
super
|
42
|
-
|
43
|
-
# Setting up attributes, unknown attributes, and options based on the class-level configuration
|
44
|
-
@attributes = raw.class.attributes
|
45
|
-
@unknown_attributes = raw.unknown_attributes
|
46
|
-
@options = {
|
47
|
-
root: raw.class.root,
|
48
|
-
ignore_nil: raw.class.ignore_nil || false,
|
49
|
-
allow_unknown: raw.class.allow_unknown || false
|
50
|
-
}
|
51
|
-
end
|
52
|
-
|
53
|
-
# Iterates over attributes and serializes each into a result hash.
|
54
|
-
#
|
55
|
-
# @return [Hash] The serialized attributes as a hash
|
56
|
-
def serialize_attributes
|
57
|
-
attributes.each_with_object({}) do |(_, attribute), hash|
|
58
|
-
next if attribute.skip_serialization?
|
59
|
-
|
60
|
-
# Serializing each attribute
|
61
|
-
serialized_value = serialize_attribute(attribute)
|
62
|
-
next if serialized_value == SKIP_ATTRIBUTE
|
63
|
-
|
64
|
-
# Assign the serialized value to the correct key in the hash
|
65
|
-
assign_attribute_key!(hash, attribute, serialized_value)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
# Process and serialize a given attribute.
|
70
|
-
#
|
71
|
-
# This handles value extraction, skipping when nil values are encountered, and ensuring
|
72
|
-
# attributes are serialized according to their rules.
|
73
|
-
#
|
74
|
-
# @param attribute [Castkit::Attribute] The attribute instance to serialize
|
75
|
-
# @return [Object, nil] The serialized value or SKIP_ATTRIBUTE if the value should be skipped
|
76
|
-
def serialize_attribute(attribute)
|
77
|
-
# Fetch the value of the attribute from the object
|
78
|
-
value = obj.public_send(attribute.field)
|
79
|
-
|
80
|
-
# Skip serialization if value is nil and ignore_nil is set to true
|
81
|
-
return SKIP_ATTRIBUTE if value.nil? && (attribute.ignore_nil? || options[:ignore_nil])
|
82
|
-
|
83
|
-
# Serialize the value using the attribute's dump method
|
84
|
-
serialized_value = attribute.dump(value, visited: visited)
|
85
|
-
|
86
|
-
# Skip if value is blank and ignore_blank is set to true
|
87
|
-
return SKIP_ATTRIBUTE if blank?(serialized_value) && (attribute.ignore_blank? || options[:ignore_blank])
|
88
|
-
|
89
|
-
serialized_value
|
90
|
-
end
|
91
|
-
|
92
|
-
# Assigns a serialized value into the hash using nested key paths.
|
93
|
-
#
|
94
|
-
# This ensures attributes with nested key paths (like `address.city`) are placed into nested hashes.
|
95
|
-
#
|
96
|
-
# @param hash [Hash] The resulting hash to populate with the serialized values
|
97
|
-
# @param attribute [Castkit::Attribute] The attribute being serialized
|
98
|
-
# @param value [Object] The serialized value
|
99
|
-
# @return [void] Updates the hash in-place
|
100
|
-
def assign_attribute_key!(hash, attribute, value)
|
101
|
-
key_path = attribute.key_path
|
102
|
-
last = key_path.pop
|
103
|
-
current = hash
|
104
|
-
|
105
|
-
# Traverse the key path and create nested hashes as needed
|
106
|
-
key_path.each do |key|
|
107
|
-
current[key] ||= {}
|
108
|
-
current = current[key]
|
109
|
-
end
|
110
|
-
|
111
|
-
# Assign the final value to the last key in the path
|
112
|
-
current[last] = value
|
113
|
-
end
|
114
|
-
|
115
|
-
# Determines if a value is blank (nil, empty array, empty hash, empty string, etc.)
|
116
|
-
#
|
117
|
-
# @param value [Object, nil] The value to check
|
118
|
-
# @return [Boolean] true if the value is blank, false otherwise
|
119
|
-
def blank?(value)
|
120
|
-
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
data/lib/castkit/serializer.rb
DELETED
@@ -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
|
data/lib/castkit/validators.rb
DELETED