Class: Servus::Support::Validator

Inherits:
Object
  • Object
show all
Defined in:
lib/servus/support/validator.rb

Overview

Validates service arguments and results, and event payloads, against the JSON schemas declared with the schema DSL.

Arguments are validated before call runs, so a service body can trust the shape of its inputs. Result data is validated after it returns, so a service that stops honouring its own contract fails loudly rather than passing the wrong shape to its callers. Both raise Errors::ValidationError, which signals a bug — in the caller for arguments, in the service itself for results — and is not meant to be rescued.

Schemas come from the schema DSL and nowhere else. The class-level readers resolve any +$ref+s against Servus::Schema, so what arrives here is always a self-contained schema.

Examples:

class MyService < Servus::Base
  schema arguments: { type: 'object', required: ['user_id'] }
end

See Also:

Constant Summary collapse

SCHEMA_TYPES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Schema kinds that may be requested from load_schema.

%w[arguments result failure payload].freeze

Class Method Summary collapse

Class Method Details

.cacheHash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the current schema cache.

Returns:

  • (Hash)

    cache mapping [class, type] pairs to compiled schemas



175
176
177
# File 'lib/servus/support/validator.rb', line 175

def self.cache
  @schema_cache
end

.clear_cache!Hash

Clears the schema cache.

Useful in tests, and in development after changing a schema. Registry changes invalidate compiled schemas on their own, so this is rarely needed in application code.

Examples:

In a test suite

before(:each) do
  Servus::Support::Validator.clear_cache!
end

Returns:

  • (Hash)

    empty hash



167
168
169
# File 'lib/servus/support/validator.rb', line 167

def self.clear_cache!
  @schema_cache = {}
end

.enforce_schema_presence!(schema, klass, config_flag) ⇒ Hash?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Raises if a schema is absent and the corresponding config flag is on.

Parameters:

  • schema (Hash, nil)

    the loaded schema

  • klass (Class)

    the service or Event class

  • config_flag (Symbol)

    the config method to check

Returns:

  • (Hash, nil)

    the schema, unchanged

Raises:



204
205
206
207
208
209
210
211
# File 'lib/servus/support/validator.rb', line 204

def self.enforce_schema_presence!(schema, klass, config_flag)
  return schema if schema

  return unless Servus.config.public_send(config_flag)

  raise Servus::Support::Errors::SchemaRequiredError,
        "#{klass.name} schema missing! #{config_flag} is set to true."
end

.load_schema(klass, type) ⇒ Hash?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a class's compiled schema of the given kind.

Cached per class and kind. The underlying compilation is also memoized on the class itself and rebuilds when Servus::Schema changes, so this cache exists to skip the lookup, not to hold compilation results.

Parameters:

Returns:

  • (Hash, nil)

    the compiled schema, or nil if none is declared

Raises:

  • (ArgumentError)

    if type is not a known schema kind



142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/servus/support/validator.rb', line 142

def self.load_schema(klass, type)
  type = type.to_s

  unless SCHEMA_TYPES.include?(type)
    raise ArgumentError, "unknown schema type #{type.inspect}. Valid: #{SCHEMA_TYPES.join(', ')}."
  end

  key = [klass, type]
  return @schema_cache[key] if @schema_cache.key?(key)

  @schema_cache[key] = klass.public_send(:"#{type}_schema")
end

.result_schema_for(service_class, result) ⇒ Array(Hash, String), Array(nil, nil)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Resolves the schema and type label for a service result.

Parameters:

Returns:

  • (Array(Hash, String), Array(nil, nil))

    the schema and type label



95
96
97
98
99
100
101
102
103
# File 'lib/servus/support/validator.rb', line 95

def self.result_schema_for(service_class, result)
  if result.success?
    schema = load_schema(service_class, 'result')
    enforce_schema_presence!(schema, service_class, :require_service_result_schema)
    [schema, 'result']
  elsif result.data
    [load_schema(service_class, 'failure'), 'failure']
  end
end

.validate_arguments!(service_class, args) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Validates service arguments against the service's arguments schema.

Examples:

Validator.validate_arguments!(MyService, { user_id: 123 })

Parameters:

  • service_class (Class)

    the service class being validated

  • args (Hash)

    keyword arguments passed to the service

Returns:

  • (Boolean)

    true if validation passes

Raises:



50
51
52
53
54
55
56
57
58
# File 'lib/servus/support/validator.rb', line 50

def self.validate_arguments!(service_class, args)
  schema = load_schema(service_class, 'arguments')
  enforce_schema_presence!(schema, service_class, :require_service_arguments_schema)
  return true unless schema

  validate_data_against_schema!(args, schema, "Invalid arguments for #{service_class.name}")

  true
end

.validate_data_against_schema!(data, schema, message_prefix) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Serializes data and validates it against a JSON schema.

Parameters:

  • data (Object)

    the data to validate

  • schema (Hash)

    the JSON schema to validate against

  • message_prefix (String)

    prefix for the error message on failure

Raises:



188
189
190
191
192
193
# File 'lib/servus/support/validator.rb', line 188

def self.validate_data_against_schema!(data, schema, message_prefix)
  errors = JSON::Validator.fully_validate(schema, data.as_json)
  return if errors.empty?

  raise Servus::Base::ValidationError, "#{message_prefix}: #{errors.join(', ')}"
end

.validate_event_payload!(event_class, payload) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Validates an event payload against the event's payload schema.

Examples:

Validator.validate_event_payload!(UserCreated, { user_id: 123 })

Parameters:

  • event_class (Class)

    the Event subclass

  • payload (Hash)

    the event payload to validate

Returns:

  • (Boolean)

    true if validation passes

Raises:



116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/servus/support/validator.rb', line 116

def self.validate_event_payload!(event_class, payload)
  schema = load_schema(event_class, 'payload')
  enforce_schema_presence!(schema, event_class, :require_event_payload_schema)
  return true unless schema

  validate_data_against_schema!(
    payload,
    schema,
    "Invalid payload for event :#{event_class.event_name}"
  )

  true
end

.validate_result!(service_class, result) ⇒ Servus::Support::Response

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Validates service result data against the appropriate schema.

For successful responses, validates against the result schema. For failure responses with data, validates against the failure schema. Failure responses without data are skipped.

Examples:

Validator.validate_result!(MyService, response)

Parameters:

  • service_class (Class)

    the service class being validated

  • result (Servus::Support::Response)

    the response object to validate

Returns:

Raises:



75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/servus/support/validator.rb', line 75

def self.validate_result!(service_class, result)
  schema, schema_type = result_schema_for(service_class, result)
  return result unless schema

  validate_data_against_schema!(
    result.data,
    schema,
    "Invalid #{schema_type} structure from #{service_class.name}"
  )

  result
end