Class: EasyTalk::ValidationAdapters::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/easy_talk/validation_adapters/base.rb

Overview

Abstract base class for validation adapters.

Validation adapters are responsible for converting JSON Schema constraints into validation rules for the target validation framework (e.g., ActiveModel, dry-validation, or custom validators).

To create a custom adapter, subclass this class and implement the apply_validations method.

Examples:

Creating a custom adapter

class MyCustomAdapter < EasyTalk::ValidationAdapters::Base
  def apply_validations
    # Apply custom validations to @klass based on @constraints
    @klass.validates @property_name, presence: true unless optional?
  end
end

Registering and using a custom adapter

EasyTalk::ValidationAdapters::Registry.register(:custom, MyCustomAdapter)

class User
  include EasyTalk::Model
  define_schema(validations: :custom) do
    property :name, String
  end
end

Direct Known Subclasses

ActiveModelAdapter, NoneAdapter

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(klass, property_name, type, constraints) ⇒ Base

Initialize a new validation adapter instance.

Parameters:

  • klass (Class)

    The model class to apply validations to

  • property_name (Symbol, String)

    The name of the property

  • type (Class, Object)

    The type of the property

  • constraints (Hash)

    The JSON Schema constraints for the property



53
54
55
56
57
58
# File 'lib/easy_talk/validation_adapters/base.rb', line 53

def initialize(klass, property_name, type, constraints)
  @klass = klass
  @property_name = property_name.to_sym
  @type = type
  @constraints = constraints || {}
end

Instance Attribute Details

#constraintsObject (readonly, protected)

Returns the value of attribute constraints.



72
73
74
# File 'lib/easy_talk/validation_adapters/base.rb', line 72

def constraints
  @constraints
end

#klassObject (readonly, protected)

Returns the value of attribute klass.



72
73
74
# File 'lib/easy_talk/validation_adapters/base.rb', line 72

def klass
  @klass
end

#property_nameObject (readonly, protected)

Returns the value of attribute property_name.



72
73
74
# File 'lib/easy_talk/validation_adapters/base.rb', line 72

def property_name
  @property_name
end

#typeObject (readonly, protected)

Returns the value of attribute type.



72
73
74
# File 'lib/easy_talk/validation_adapters/base.rb', line 72

def type
  @type
end

Class Method Details

.build_validations(klass, property_name, type, constraints)

This method returns an undefined value.

Build validations for a property and apply them to the model class. This is the primary interface that adapters must implement.

Parameters:

  • klass (Class)

    The model class to apply validations to

  • property_name (Symbol, String)

    The name of the property

  • type (Class, Object)

    The type of the property (Ruby class or Sorbet type)

  • constraints (Hash)

    The JSON Schema constraints for the property Possible keys: :min_length, :max_length, :minimum, :maximum, :pattern, :format, :enum, :const, :min_items, :max_items, :unique_items, :optional



43
44
45
# File 'lib/easy_talk/validation_adapters/base.rb', line 43

def self.build_validations(klass, property_name, type, constraints)
  new(klass, property_name, type, constraints).apply_validations
end

Instance Method Details

#apply_validations

This method is abstract.

This method returns an undefined value.

Apply validations based on property type and constraints. Subclasses MUST implement this method.

Raises:

  • (NotImplementedError)

    if the subclass does not implement this method



66
67
68
# File 'lib/easy_talk/validation_adapters/base.rb', line 66

def apply_validations
  raise NotImplementedError, "#{self.class} must implement #apply_validations"
end

#extract_inner_type(type_to_unwrap = @type) ⇒ Class, Object (protected)

Extract the inner type from a complex type like T.nilable(String) or T.nilable(T::Array[Model]).

Parameters:

  • type_to_unwrap (Class, Object) (defaults to: @type)

    The type to unwrap (defaults to @type)

Returns:

  • (Class, Object)

    The inner type, or the original type if not wrapped



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/easy_talk/validation_adapters/base.rb', line 98

def extract_inner_type(type_to_unwrap = @type)
  if type_to_unwrap.respond_to?(:unwrap_nilable)
    unwrapped = type_to_unwrap.unwrap_nilable
    # Return TypedArray directly (for T.nilable(T::Array[Model]))
    return unwrapped if unwrapped.is_a?(T::Types::TypedArray)
    # Return raw_type for simple types (for T.nilable(String))
    return unwrapped.raw_type if unwrapped.respond_to?(:raw_type)
  end

  if type_to_unwrap.respond_to?(:types)
    # For union types, find the non-nil type
    # Prefer TypedArray if present, otherwise find type with raw_type
    type_to_unwrap.types.find { |t| t.is_a?(T::Types::TypedArray) } ||
      type_to_unwrap.types.find { |t| t.respond_to?(:raw_type) && t.raw_type != NilClass }
  else
    type_to_unwrap
  end
end

#get_type_class(type_to_resolve) ⇒ Class+ (protected)

Determine the actual class for a type, handling Sorbet types.

Parameters:

  • type_to_resolve (Class, Object)

    The type to resolve

Returns:

  • (Class, Array<Class>)

    The resolved class or classes



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/easy_talk/validation_adapters/base.rb', line 121

def get_type_class(type_to_resolve)
  if type_to_resolve.is_a?(Class)
    type_to_resolve
  elsif type_to_resolve.respond_to?(:raw_type)
    type_to_resolve.raw_type
  elsif type_to_resolve.is_a?(T::Types::TypedArray)
    Array
  elsif type_to_resolve.is_a?(Symbol) || type_to_resolve.is_a?(String)
    begin
      type_to_resolve.to_s.classify.constantize
    rescue StandardError
      String
    end
  elsif TypeIntrospection.boolean_type?(type_to_resolve)
    [TrueClass, FalseClass]
  elsif nilable_type?(type_to_resolve)
    extract_inner_type(type_to_resolve)
  else
    String
  end
end

#nilable_type?(type_to_check = @type) ⇒ Boolean (protected)

Check if the type is nilable (e.g., T.nilable(String)).

Parameters:

  • t (Class, Object)

    The type to check (defaults to @type)

Returns:

  • (Boolean)

    true if the type is nilable



90
91
92
# File 'lib/easy_talk/validation_adapters/base.rb', line 90

def nilable_type?(type_to_check = @type)
  type_to_check.respond_to?(:nilable?) && type_to_check.nilable?
end

#optional?Boolean (protected)

Check if a property is optional based on constraints and configuration.

A property is considered optional if:

  • The :optional constraint is explicitly set to true
  • The type is nilable AND nilable_is_optional configuration is true

Returns:

  • (Boolean)

    true if the property is optional



81
82
83
84
# File 'lib/easy_talk/validation_adapters/base.rb', line 81

def optional?
  @constraints[:optional] == true ||
    (@type.respond_to?(:nilable?) && @type.nilable? && EasyTalk.configuration.nilable_is_optional)
end