Class: TRuby::RuntimeValidator
- Inherits:
-
Object
- Object
- TRuby::RuntimeValidator
- Defined in:
- lib/t_ruby/runtime_validator.rb
Overview
Generates runtime type validation code
Constant Summary collapse
- TYPE_CHECKS =
Type mappings for runtime checks
{ "String" => ".is_a?(String)", "Integer" => ".is_a?(Integer)", "Float" => ".is_a?(Float)", "Numeric" => ".is_a?(Numeric)", "Boolean" => " == true || %s == false", "Symbol" => ".is_a?(Symbol)", "Array" => ".is_a?(Array)", "Hash" => ".is_a?(Hash)", "nil" => ".nil?", "Object" => ".is_a?(Object)", "Class" => ".is_a?(Class)", "Module" => ".is_a?(Module)", "Proc" => ".is_a?(Proc)", "Regexp" => ".is_a?(Regexp)", "Range" => ".is_a?(Range)", "Time" => ".is_a?(Time)", "Date" => ".is_a?(Date)", "IO" => ".is_a?(IO)", "File" => ".is_a?(File)" }.freeze
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
Returns the value of attribute config.
Instance Method Summary collapse
-
#generate_function_validation(function_info) ⇒ Object
Generate validation code for a function.
-
#generate_generic_check(var_name, generic_type) ⇒ Object
Generate generic type check.
-
#generate_intersection_check(var_name, intersection_type) ⇒ Object
Generate intersection type check.
-
#generate_param_validation(param_name, type_annotation) ⇒ Object
Generate validation for a single parameter.
-
#generate_return_validation(return_type) ⇒ Object
Generate return value validation wrapper.
-
#generate_simple_type_check(var_name, type_name) ⇒ Object
Generate simple type check.
-
#generate_type_check(var_name, type_annotation) ⇒ Object
Generate type check expression.
-
#generate_union_check(var_name, union_type) ⇒ Object
Generate union type check.
-
#generate_validation_module(functions) ⇒ Object
Generate a validation module that can be included.
-
#initialize(config = nil) ⇒ RuntimeValidator
constructor
A new instance of RuntimeValidator.
-
#should_validate?(visibility) ⇒ Boolean
Check if validation should be applied based on config.
-
#transform(source, parse_result) ⇒ Object
Transform source code to include runtime validations.
Constructor Details
#initialize(config = nil) ⇒ RuntimeValidator
Returns a new instance of RuntimeValidator.
45 46 47 |
# File 'lib/t_ruby/runtime_validator.rb', line 45 def initialize(config = nil) @config = config || ValidationConfig.new end |
Instance Attribute Details
#config ⇒ Object (readonly)
Returns the value of attribute config.
20 21 22 |
# File 'lib/t_ruby/runtime_validator.rb', line 20 def config @config end |
Instance Method Details
#generate_function_validation(function_info) ⇒ Object
Generate validation code for a function
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/t_ruby/runtime_validator.rb', line 50 def generate_function_validation(function_info) validations = [] # Parameter validations function_info[:params].each do |param| next unless param[:type] validation = generate_param_validation(param[:name], param[:type]) validations << validation if validation end # Return type validation (if specified) if function_info[:return_type] return_validation = generate_return_validation(function_info[:return_type]) validations << return_validation if return_validation end validations end |
#generate_generic_check(var_name, generic_type) ⇒ Object
Generate generic type check
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/t_ruby/runtime_validator.rb', line 135 def generate_generic_check(var_name, generic_type) match = generic_type.match(/^(\w+)<(.+)>$/) return nil unless match container_type = match[1] element_type = match[2] case container_type when "Array" "#{var_name}.is_a?(Array) && #{var_name}.all? { |_e| #{generate_type_check('_e', element_type)} }" when "Hash" if element_type.include?(",") key_type, value_type = element_type.split(",").map(&:strip) "#{var_name}.is_a?(Hash) && #{var_name}.all? { |_k, _v| #{generate_type_check('_k', key_type)} && #{generate_type_check('_v', value_type)} }" else "#{var_name}.is_a?(Hash)" end when "Set" "#{var_name}.is_a?(Set) && #{var_name}.all? { |_e| #{generate_type_check('_e', element_type)} }" else # Generic with unknown container - just check container type "#{var_name}.is_a?(#{container_type})" end end |
#generate_intersection_check(var_name, intersection_type) ⇒ Object
Generate intersection type check
161 162 163 164 165 |
# File 'lib/t_ruby/runtime_validator.rb', line 161 def generate_intersection_check(var_name, intersection_type) types = intersection_type.split("&").map(&:strip) checks = types.map { |t| generate_type_check(var_name, t) } "(#{checks.join(' && ')})" end |
#generate_param_validation(param_name, type_annotation) ⇒ Object
Generate validation for a single parameter
71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/t_ruby/runtime_validator.rb', line 71 def generate_param_validation(param_name, type_annotation) check_code = generate_type_check(param_name, type_annotation) return nil unless check_code = "TypeError: #{param_name} must be #{type_annotation}, got \#{#{param_name}.class}" if @config.raise_on_error "raise #{error_message.inspect.gsub('\#{', '#{')} unless #{check_code}" else "warn #{error_message.inspect.gsub('\#{', '#{')} unless #{check_code}" end end |
#generate_return_validation(return_type) ⇒ Object
Generate return value validation wrapper
168 169 170 171 172 173 174 |
# File 'lib/t_ruby/runtime_validator.rb', line 168 def generate_return_validation(return_type) { type: :return, check: generate_type_check("_result", return_type), return_type: return_type } end |
#generate_simple_type_check(var_name, type_name) ⇒ Object
Generate simple type check
116 117 118 119 120 121 122 123 124 125 |
# File 'lib/t_ruby/runtime_validator.rb', line 116 def generate_simple_type_check(var_name, type_name) if type_name == "Boolean" "(#{var_name} == true || #{var_name} == false)" elsif TYPE_CHECKS.key?(type_name) "#{var_name}#{TYPE_CHECKS[type_name]}" else # Custom type - use is_a? or respond_to? "#{var_name}.is_a?(#{type_name})" end end |
#generate_type_check(var_name, type_annotation) ⇒ Object
Generate type check expression
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/t_ruby/runtime_validator.rb', line 85 def generate_type_check(var_name, type_annotation) # Handle nil type return "#{var_name}.nil?" if type_annotation == "nil" # Handle union types if type_annotation.include?("|") return generate_union_check(var_name, type_annotation) end # Handle generic types if type_annotation.include?("<") return generate_generic_check(var_name, type_annotation) end # Handle intersection types if type_annotation.include?("&") return generate_intersection_check(var_name, type_annotation) end # Handle optional types (ending with ?) if type_annotation.end_with?("?") base_type = type_annotation[0..-2] base_check = generate_simple_type_check(var_name, base_type) return "(#{var_name}.nil? || #{base_check})" end # Simple type check generate_simple_type_check(var_name, type_annotation) end |
#generate_union_check(var_name, union_type) ⇒ Object
Generate union type check
128 129 130 131 132 |
# File 'lib/t_ruby/runtime_validator.rb', line 128 def generate_union_check(var_name, union_type) types = union_type.split("|").map(&:strip) checks = types.map { |t| generate_type_check(var_name, t) } "(#{checks.join(' || ')})" end |
#generate_validation_module(functions) ⇒ Object
Generate a validation module that can be included
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/t_ruby/runtime_validator.rb', line 224 def generate_validation_module(functions) module_code = " # frozen_string_literal: true\n # Auto-generated runtime type validation module\n\n module TRubyValidation\n class TypeError < StandardError; end\n\n def self.validate_type(value, expected_type, param_name = \"value\")\n RUBY\n\n module_code += <<~RUBY\n case expected_type\n when \"String\"\n raise TypeError, \"\\\#{param_name} must be String, got \\\#{value.class}\" unless value.is_a?(String)\n when \"Integer\"\n raise TypeError, \"\\\#{param_name} must be Integer, got \\\#{value.class}\" unless value.is_a?(Integer)\n when \"Float\"\n raise TypeError, \"\\\#{param_name} must be Float, got \\\#{value.class}\" unless value.is_a?(Float)\n when \"Boolean\"\n raise TypeError, \"\\\#{param_name} must be Boolean, got \\\#{value.class}\" unless value == true || value == false\n when \"Symbol\"\n raise TypeError, \"\\\#{param_name} must be Symbol, got \\\#{value.class}\" unless value.is_a?(Symbol)\n when \"Array\"\n raise TypeError, \"\\\#{param_name} must be Array, got \\\#{value.class}\" unless value.is_a?(Array)\n when \"Hash\"\n raise TypeError, \"\\\#{param_name} must be Hash, got \\\#{value.class}\" unless value.is_a?(Hash)\n when \"nil\"\n raise TypeError, \"\\\#{param_name} must be nil, got \\\#{value.class}\" unless value.nil?\n else\n # Custom type check\n begin\n type_class = Object.const_get(expected_type)\n raise TypeError, \"\\\#{param_name} must be \\\#{expected_type}, got \\\#{value.class}\" unless value.is_a?(type_class)\n rescue NameError\n # Unknown type, skip validation\n end\n end\n true\n end\n RUBY\n\n # Generate validation methods for each function\n functions.each do |func|\n next if func[:params].empty? && !func[:return_type]\n\n method_name = \"validate_\#{func[:name]}_params\"\n param_list = func[:params].map { |p| p[:name] }.join(\", \")\n\n module_code += \"\\n def self.\#{method_name}(\#{param_list})\\n\"\n\n func[:params].each do |param|\n next unless param[:type]\n module_code += \" validate_type(\#{param[:name]}, \#{param[:type].inspect}, \#{param[:name].inspect})\\n\"\n end\n\n module_code += \" true\\n end\\n\"\n end\n\n module_code += \"end\\n\"\n module_code\nend\n" |
#should_validate?(visibility) ⇒ Boolean
Check if validation should be applied based on config
288 289 290 291 292 |
# File 'lib/t_ruby/runtime_validator.rb', line 288 def should_validate?(visibility) return true if @config.validate_all return visibility == :public if @config.validate_public_only false end |
#transform(source, parse_result) ⇒ Object
Transform source code to include runtime validations
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/t_ruby/runtime_validator.rb', line 177 def transform(source, parse_result) lines = source.split("\n") output_lines = [] in_function = false current_function = nil function_indent = 0 parse_result[:functions].each do |func| @function_validations ||= {} @function_validations[func[:name]] = generate_function_validation(func) end lines.each_with_index do |line, idx| # Check for function definition if line.match?(/^\s*def\s+(\w+)/) match = line.match(/^(\s*)def\s+(\w+)/) function_indent = match[1].length function_name = match[2] # Add validation code after function definition output_lines << line if @function_validations && @function_validations[function_name] validations = @function_validations[function_name] param_validations = validations.select { |v| v.is_a?(String) } param_validations.each do |validation| output_lines << "#{' ' * (function_indent + 2)}#{validation}" end end in_function = true current_function = function_name elsif in_function && line.match?(/^\s*end\s*$/) # End of function in_function = false current_function = nil output_lines << line else output_lines << line end end output_lines.join("\n") end |