Class: Philiprehberger::ConfigValidator::Schema

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

Overview

DSL for defining configuration schemas

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeSchema

Returns a new instance of Schema.



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

def initialize
  @rules = []
  @nested_schemas = []
  @custom_validators = []
  @pattern_validators = []
  @range_validators = []
end

Instance Attribute Details

#custom_validatorsArray<Hash> (readonly)

Returns custom predicate validations.

Returns:

  • (Array<Hash>)

    custom predicate validations



14
15
16
# File 'lib/philiprehberger/config_validator/schema.rb', line 14

def custom_validators
  @custom_validators
end

#nested_schemasArray<Hash> (readonly)

Returns nested schema definitions.

Returns:

  • (Array<Hash>)

    nested schema definitions



11
12
13
# File 'lib/philiprehberger/config_validator/schema.rb', line 11

def nested_schemas
  @nested_schemas
end

#pattern_validatorsArray<Hash> (readonly)

Returns pattern validations.

Returns:

  • (Array<Hash>)

    pattern validations



17
18
19
# File 'lib/philiprehberger/config_validator/schema.rb', line 17

def pattern_validators
  @pattern_validators
end

#range_validatorsArray<Hash> (readonly)

Returns range validations.

Returns:

  • (Array<Hash>)

    range validations



20
21
22
# File 'lib/philiprehberger/config_validator/schema.rb', line 20

def range_validators
  @range_validators
end

#rulesArray<Rule> (readonly)

Returns the defined rules.

Returns:

  • (Array<Rule>)

    the defined rules



8
9
10
# File 'lib/philiprehberger/config_validator/schema.rb', line 8

def rules
  @rules
end

Instance Method Details

#coerce(config) ⇒ Hash

Coerce string values in a config hash to their expected types.

Useful when config comes from ENV where all values are strings. Modifies the hash in place and returns it.

Parameters:

  • config (Hash)

    the configuration to coerce

Returns:

  • (Hash)

    the coerced configuration



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/philiprehberger/config_validator/schema.rb', line 153

def coerce(config)
  @rules.each do |rule|
    value = config.key?(rule.key) ? config[rule.key] : config[rule.key.to_s]
    next unless value.is_a?(String)

    coerced = coerce_value(value, rule.type)
    next if coerced.nil?

    if config.key?(rule.key)
      config[rule.key] = coerced
    elsif config.key?(rule.key.to_s)
      config[rule.key.to_s] = coerced
    end
  end
  config
end

#keysArray<Symbol>

Return all defined key names

Returns:

  • (Array<Symbol>)

    the key names



118
119
120
# File 'lib/philiprehberger/config_validator/schema.rb', line 118

def keys
  @rules.map(&:key)
end

#nested(key, required: true) {|Schema| ... } ⇒ void

This method returns an undefined value.

Define a nested schema for a hash key

Parameters:

  • key (Symbol)

    the configuration key

  • required (Boolean) (defaults to: true)

    whether the key is required

Yields:

  • (Schema)

    the nested schema instance for DSL evaluation



57
58
59
60
61
# File 'lib/philiprehberger/config_validator/schema.rb', line 57

def nested(key, required: true, &block)
  child = Schema.new
  child.instance_eval(&block)
  @nested_schemas << { key: key, schema: child, required: required }
end

#optional(key, type, default: nil, one_of: nil) ⇒ void

This method returns an undefined value.

Define an optional configuration key

Parameters:

  • key (Symbol)

    the configuration key

  • type (Class)

    the expected type

  • default (Object, nil) (defaults to: nil)

    the default value

  • one_of (Array, nil) (defaults to: nil)

    allowed values constraint



47
48
49
# File 'lib/philiprehberger/config_validator/schema.rb', line 47

def optional(key, type, default: nil, one_of: nil)
  @rules << Rule.new(key, type, required: false, default: default, one_of: one_of)
end

#optional_keysArray<Symbol>

Return the array of keys declared as optional.

Includes keys defined via #optional as well as nested schema keys declared with required: false.

Returns:

  • (Array<Symbol>)

    the optional key names



140
141
142
143
144
# File 'lib/philiprehberger/config_validator/schema.rb', line 140

def optional_keys
  rule_keys = @rules.reject(&:required).map(&:key)
  nested_keys = @nested_schemas.reject { |entry| entry[:required] }.map { |entry| entry[:key] }
  rule_keys + nested_keys
end

#pattern(key, regex, message: nil) ⇒ void

This method returns an undefined value.

Define a regex pattern validation for string values

Parameters:

  • key (Symbol)

    the configuration key

  • regex (Regexp)

    the pattern to match

  • message (String, nil) (defaults to: nil)

    custom error message



80
81
82
# File 'lib/philiprehberger/config_validator/schema.rb', line 80

def pattern(key, regex, message: nil)
  @pattern_validators << { key: key, regex: regex, message: message || 'does not match expected pattern' }
end

#range(key, min: nil, max: nil) ⇒ void

This method returns an undefined value.

Define a numeric range validation

Parameters:

  • key (Symbol)

    the configuration key

  • min (Numeric, nil) (defaults to: nil)

    minimum value (inclusive)

  • max (Numeric, nil) (defaults to: nil)

    maximum value (inclusive)



90
91
92
# File 'lib/philiprehberger/config_validator/schema.rb', line 90

def range(key, min: nil, max: nil)
  @range_validators << { key: key, min: min, max: max }
end

#required(key, type, one_of: nil) ⇒ void

This method returns an undefined value.

Define a required configuration key

Parameters:

  • key (Symbol)

    the configuration key

  • type (Class)

    the expected type

  • one_of (Array, nil) (defaults to: nil)

    allowed values constraint



36
37
38
# File 'lib/philiprehberger/config_validator/schema.rb', line 36

def required(key, type, one_of: nil)
  @rules << Rule.new(key, type, required: true, one_of: one_of)
end

#required_keysArray<Symbol>

Return the array of keys declared as required.

Includes keys defined via #required as well as nested schema keys declared with required: true (the default for #nested).

Returns:

  • (Array<Symbol>)

    the required key names



128
129
130
131
132
# File 'lib/philiprehberger/config_validator/schema.rb', line 128

def required_keys
  rule_keys = @rules.select(&:required).map(&:key)
  nested_keys = @nested_schemas.select { |entry| entry[:required] }.map { |entry| entry[:key] }
  rule_keys + nested_keys
end

#to_docArray<Hash>

Generate documentation for the schema.

Returns:

  • (Array<Hash>)

    one hash per key with :key, :type, :required, :default, :constraints



173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/philiprehberger/config_validator/schema.rb', line 173

def to_doc
  @rules.map do |rule|
    constraints = []
    constraints << "one of: #{rule.allowed_values.inspect}" if rule.allowed_values&.any?

    {
      key: rule.key,
      type: rule.type.name,
      required: rule.required,
      default: rule.default,
      constraints: constraints.empty? ? nil : constraints.join(', ')
    }
  end
end

#to_exampleHash

Generate a sample configuration hash from the schema definition

Uses defaults where available, first allowed value for one_of constraints, and type-appropriate placeholders for required fields.

Returns:

  • (Hash)

    a sample configuration hash



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

def to_example
  result = {}
  @rules.each do |rule|
    value = if rule.default
              rule.default
            elsif rule.allowed_values&.any?
              rule.allowed_values.first
            else
              placeholder_for(rule.type)
            end
    result[rule.key] = value
  end
  result
end

#validate(config) ⇒ Array<String>

Validate a configuration hash against all rules

Parameters:

  • config (Hash)

    the configuration to validate

Returns:

  • (Array<String>)

    validation error messages



192
193
194
195
196
197
198
199
200
# File 'lib/philiprehberger/config_validator/schema.rb', line 192

def validate(config)
  apply_defaults(config)
  errors = rules.flat_map { |rule| rule.validate(config) }
  errors.concat(validate_nested(config))
  errors.concat(validate_custom(config))
  errors.concat(validate_patterns(config))
  errors.concat(validate_ranges(config))
  errors
end

#validate!(config) ⇒ Hash

Validate a configuration hash and raise on errors

Parameters:

  • config (Hash)

    the configuration to validate

Returns:

  • (Hash)

    the validated configuration with defaults applied

Raises:



207
208
209
210
211
212
# File 'lib/philiprehberger/config_validator/schema.rb', line 207

def validate!(config)
  errors = validate(config)
  raise ValidationError, "Configuration invalid: #{errors.join('; ')}" unless errors.empty?

  config
end

#validate_with(key, message: 'is invalid') {|Object| ... } ⇒ void

This method returns an undefined value.

Define a custom predicate validation

Parameters:

  • key (Symbol)

    the configuration key

  • message (String) (defaults to: 'is invalid')

    error message when validation fails

Yields:

  • (Object)

    the value to validate

Yield Returns:

  • (Boolean)

    true if valid, false if invalid



70
71
72
# File 'lib/philiprehberger/config_validator/schema.rb', line 70

def validate_with(key, message: 'is invalid', &block)
  @custom_validators << { key: key, message: message, block: block }
end