Module: EasyTalk::TypeIntrospection

Defined in:
lib/easy_talk/type_introspection.rb

Overview

Centralized module for robust type introspection.

This module provides predicate methods for detecting types without relying on brittle string-based checks. It uses Sorbet's type system properly and handles edge cases gracefully.

Examples:

Checking if a type is boolean

TypeIntrospection.boolean_type?(T::Boolean) # => true
TypeIntrospection.boolean_type?(TrueClass)  # => true
TypeIntrospection.boolean_type?(String)     # => false

Getting JSON Schema type

TypeIntrospection.json_schema_type(Integer) # => 'integer'
TypeIntrospection.json_schema_type(Float)   # => 'number'

Constant Summary collapse

PRIMITIVE_TO_JSON_SCHEMA =

Mapping of Ruby classes to JSON Schema types

{
  String => 'string',
  Integer => 'integer',
  Float => 'number',
  BigDecimal => 'number',
  TrueClass => 'boolean',
  FalseClass => 'boolean',
  NilClass => 'null'
}.freeze

Class Method Summary collapse

Class Method Details

.boolean_type?(type) ⇒ Boolean

Check if type represents a boolean (T::Boolean or TrueClass/FalseClass).

Examples:

boolean_type?(T::Boolean)  # => true
boolean_type?(TrueClass)   # => true
boolean_type?(FalseClass)  # => true
boolean_type?(String)      # => false

Parameters:

  • type (Object)

    The type to check

Returns:

  • (Boolean)

    true if the type is a boolean type



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/easy_talk/type_introspection.rb', line 44

def boolean_type?(type)
  return false if type.nil?
  return true if [TrueClass, FalseClass].include?(type)
  return true if type.respond_to?(:raw_type) && [TrueClass, FalseClass].include?(type.raw_type)

  # Check for T::Boolean which is a TypeAlias with name 'T::Boolean'
  return true if type.respond_to?(:name) && type.name == 'T::Boolean'

  # Check for union types containing TrueClass and FalseClass
  if type.respond_to?(:types)
    type_classes = type.types.map { |t| t.respond_to?(:raw_type) ? t.raw_type : t }
    return type_classes.sort_by(&:name) == [FalseClass, TrueClass].sort_by(&:name)
  end

  false
end

.extract_inner_type(type) ⇒ Object

Extract inner type from nilable or complex types.

Examples:

extract_inner_type(T.nilable(String)) # => String

Parameters:

  • type (Object)

    The type to unwrap

Returns:

  • (Object)

    The inner type, or the original type if not wrapped



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/easy_talk/type_introspection.rb', line 158

def extract_inner_type(type)
  return type if type.nil?

  if type.respond_to?(:unwrap_nilable)
    unwrapped = type.unwrap_nilable
    return unwrapped.respond_to?(:raw_type) ? unwrapped.raw_type : unwrapped
  end

  if type.respond_to?(:types)
    non_nil = type.types.find do |t|
      raw = t.respond_to?(:raw_type) ? t.raw_type : t
      raw != NilClass
    end
    return non_nil.respond_to?(:raw_type) ? non_nil.raw_type : non_nil if non_nil
  end

  type
end

.get_type_class(type) ⇒ Class, ...

Get the Ruby class for a type, handling Sorbet types.

Examples:

get_type_class(String)           # => String
get_type_class(T::Boolean)       # => [TrueClass, FalseClass]
get_type_class(T::Array[String]) # => Array

Parameters:

  • type (Object)

    The type to resolve

Returns:

  • (Class, Array<Class>, nil)

    The resolved class or classes



136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/easy_talk/type_introspection.rb', line 136

def get_type_class(type)
  return nil if type.nil?
  return type if type.is_a?(Class)
  return type.raw_type if type.respond_to?(:raw_type)
  return Array if typed_array?(type)
  return [TrueClass, FalseClass] if boolean_type?(type)

  if nilable_type?(type)
    inner = extract_inner_type(type)
    return get_type_class(inner) if inner && inner != type
  end

  nil
end

.json_schema_type(type) ⇒ String

Get JSON Schema type string for a Ruby type.

Examples:

json_schema_type(Integer)    # => 'integer'
json_schema_type(Float)      # => 'number'
json_schema_type(BigDecimal) # => 'number'
json_schema_type(String)     # => 'string'

Parameters:

  • type (Object)

    The type to convert

Returns:

  • (String)

    The JSON Schema type string



114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/easy_talk/type_introspection.rb', line 114

def json_schema_type(type)
  return 'object' if type.nil?
  return 'boolean' if boolean_type?(type)

  resolved_class = if type.is_a?(Class)
                     type
                   elsif type.respond_to?(:raw_type)
                     type.raw_type
                   end

  PRIMITIVE_TO_JSON_SCHEMA[resolved_class] || resolved_class&.name&.downcase || 'object'
end

.nilable_type?(type) ⇒ Boolean

Check if type is nilable (T.nilable(...)).

Examples:

nilable_type?(T.nilable(String)) # => true
nilable_type?(String)            # => false

Parameters:

  • type (Object)

    The type to check

Returns:

  • (Boolean)

    true if the type is nilable



83
84
85
86
87
# File 'lib/easy_talk/type_introspection.rb', line 83

def nilable_type?(type)
  return false if type.nil?

  type.respond_to?(:nilable?) && type.nilable?
end

.primitive_type?(type) ⇒ Boolean

Check if type is a primitive Ruby type.

Parameters:

  • type (Object)

    The type to check

Returns:

  • (Boolean)

    true if the type is a primitive



93
94
95
96
97
98
99
100
101
102
# File 'lib/easy_talk/type_introspection.rb', line 93

def primitive_type?(type)
  return false if type.nil?

  resolved = if type.is_a?(Class)
               type
             elsif type.respond_to?(:raw_type)
               type.raw_type
             end
  PRIMITIVE_TO_JSON_SCHEMA.key?(resolved)
end

.typed_array?(type) ⇒ Boolean

Check if type is a typed array (T::Array[...]).

Examples:

typed_array?(T::Array[String]) # => true
typed_array?(Array)            # => false

Parameters:

  • type (Object)

    The type to check

Returns:

  • (Boolean)

    true if the type is a typed array



69
70
71
72
73
# File 'lib/easy_talk/type_introspection.rb', line 69

def typed_array?(type)
  return false if type.nil?

  type.is_a?(T::Types::TypedArray)
end