Class: Whoosh::Schema

Inherits:
Object
  • Object
show all
Defined in:
lib/whoosh/schema.rb

Defined Under Namespace

Classes: Result

Class Method Summary collapse

Class Method Details

.custom_validatorsObject



34
35
36
# File 'lib/whoosh/schema.rb', line 34

def custom_validators
  @custom_validators || []
end

.field(name, type, **options) ⇒ Object



38
39
40
41
# File 'lib/whoosh/schema.rb', line 38

def field(name, type, **options)
  @fields[name] = options.merge(type: type)
  @contract = nil # Reset cached contract
end

.fieldsObject



43
44
45
# File 'lib/whoosh/schema.rb', line 43

def fields
  @fields
end

.inherited(subclass) ⇒ Object



22
23
24
25
26
27
# File 'lib/whoosh/schema.rb', line 22

def inherited(subclass)
  super
  subclass.instance_variable_set(:@fields, {})
  subclass.instance_variable_set(:@contract, nil)
  subclass.instance_variable_set(:@custom_validators, [])
end

.serialize(data) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/whoosh/schema.rb', line 101

def serialize(data)
  return data unless data.is_a?(Hash)

  @fields.each_with_object({}) do |(name, opts), hash|
    value = data[name]
    if value.nil?
      hash[name] = opts[:default]
    elsif schema_type?(opts[:type])
      hash[name] = opts[:type].serialize(value)
    else
      hash[name] = serialize_value(value)
    end
  end
end

.validate(data) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/whoosh/schema.rb', line 47

def validate(data)
  input = coerce_input(data)

  # First pass: dry-schema validation
  result = contract.call(input)

  unless result.success?
    errors = result.errors.to_h.flat_map do |field_name, messages|
      messages.map do |msg|
        { field: field_name, message: msg, value: input[field_name] || input[field_name.to_s] }
      end
    end
    return Result.new(data: nil, errors: errors)
  end

  validated = result.to_h

  # Second pass: min/max constraints + nested schema validation
  errors = []
  @fields.each do |name, opts|
    value = validated[name]
    next if value.nil?

    # Nested schema validation
    if schema_type?(opts[:type])
      nested_result = opts[:type].validate(value)
      unless nested_result.success?
        nested_result.errors.each do |err|
          errors << { field: :"#{name}.#{err[:field]}", message: err[:message], value: err[:value] }
        end
      else
        validated[name] = nested_result.data
      end
    end

    # Min/max constraints
    if opts[:min] && value.is_a?(Numeric) && value < opts[:min]
      errors << { field: name, message: "must be greater than or equal to #{opts[:min]}", value: value }
    end
    if opts[:max] && value.is_a?(Numeric) && value > opts[:max]
      errors << { field: name, message: "must be less than or equal to #{opts[:max]}", value: value }
    end
  end

  # Third pass: custom validators
  self.custom_validators.each do |validator|
    validator.call(validated, errors)
  end

  return Result.new(data: nil, errors: errors) unless errors.empty?

  Result.new(data: apply_defaults(validated), errors: [])
end

.validate_with(&block) ⇒ Object



29
30
31
32
# File 'lib/whoosh/schema.rb', line 29

def validate_with(&block)
  @custom_validators ||= []
  @custom_validators << block
end