Class: Servus::Support::Validator

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

Overview

Handles JSON Schema validation for service arguments and results.

The Validator class provides automatic validation of service inputs and outputs against JSON Schema definitions. Schemas can be defined as inline constants (ARGUMENTS_SCHEMA, RESULT_SCHEMA) or as external JSON files.

Examples:

Inline schema validation

class MyService < Servus::Base
  ARGUMENTS_SCHEMA = {
    type: "object",
    required: ["user_id"],
    properties: {
      user_id: { type: "integer" }
    }
  }
end

File-based schema validation

# app/schemas/services/my_service/arguments.json
# { "type": "object", "required": ["user_id"], ... }

See Also:

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 schema paths to loaded schemas



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

def self.cache
  @schema_cache
end

.clear_cache!Hash

Clears the schema cache.

Useful in development when schema files are modified, or in tests to ensure fresh schema loading between test cases.

Examples:

In a test suite

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

Returns:

  • (Hash)

    empty hash



182
183
184
# File 'lib/servus/support/validator.rb', line 182

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.

Returns the schema if present. Raises if absent and the config flag is enabled.

Parameters:

  • schema (Hash, nil)

    the loaded schema

  • klass (Class)

    the service or handler class

  • config_flag (Symbol)

    the config method to check

Returns:

  • (Hash, nil)

    the schema

Raises:



219
220
221
222
223
224
225
226
# File 'lib/servus/support/validator.rb', line 219

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

.fetch_schema_from_sources(dsl_schema, inline_schema_constant, schema_path) ⇒ 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.

Fetches schema from DSL, inline constant, or file.

Implements the schema resolution precedence:

  1. DSL-defined schema (if provided)

  2. Inline constant (if provided)

  3. File at schema_path (if exists)

  4. nil (no schema found)

Parameters:

  • dsl_schema (Hash, nil)

    schema from DSL method (e.g., schema arguments: Hash)

  • inline_schema_constant (Hash, nil)

    inline schema constant (e.g., ARGUMENTS_SCHEMA)

  • schema_path (String)

    file path to external schema JSON

Returns:

  • (Hash, nil)

    schema with indifferent access, or nil if not found



242
243
244
245
246
247
248
249
250
# File 'lib/servus/support/validator.rb', line 242

def self.fetch_schema_from_sources(dsl_schema, inline_schema_constant, schema_path)
  if dsl_schema
    dsl_schema.with_indifferent_access
  elsif inline_schema_constant
    inline_schema_constant.with_indifferent_access
  elsif File.exist?(schema_path)
    JSON.load_file(schema_path).with_indifferent_access
  end
end

.load_schema(service_class, 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.

Loads and caches a schema for a service.

Implements a three-tier lookup strategy:

  1. Check for schema defined via DSL method (service_class.arguments_schema/result_schema)

  2. Check for inline constant (ARGUMENTS_SCHEMA or RESULT_SCHEMA)

  3. Fall back to JSON file in app/schemas/services/namespace/type.json

Schemas are cached after first load for performance.

rubocop:disable Metrics/MethodLength

Parameters:

  • service_class (Class)

    the service class

  • type (String)

    schema type (“arguments”, “result”, or “failure”)

Returns:

  • (Hash, nil)

    the schema hash, or nil if no schema found



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/servus/support/validator.rb', line 146

def self.load_schema(service_class, type)
  # Get service path based on class name (e.g., "process_payment" from "Servus::ProcessPayment::Service")
  service_namespace = parse_service_namespace(service_class)
  schema_path = Servus.config.schema_path_for(service_namespace, type)

  # Return from cache if available
  return @schema_cache[schema_path] if @schema_cache.key?(schema_path)

  # Check for DSL-defined schema first
  dsl_schema = case type
               when 'arguments' then service_class.arguments_schema
               when 'result'    then service_class.result_schema
               when 'failure'   then service_class.failure_schema
               end

  inline_schema_constant_name = "#{service_class}::#{type.upcase}_SCHEMA"
  inline_schema_constant = if Object.const_defined?(inline_schema_constant_name)
                             Object.const_get(inline_schema_constant_name)
                           end

  @schema_cache[schema_path] = fetch_schema_from_sources(dsl_schema, inline_schema_constant, schema_path)
  @schema_cache[schema_path]
end

.parse_service_namespace(service_class) ⇒ String

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.

Converts service class name to file path namespace.

Transforms a class name like “Services::ProcessPayment::Service” into “services/process_payment” for locating schema files.

Examples:

parse_service_namespace(Services::ProcessPayment::Service)
# => "services/process_payment"

Parameters:

  • service_class (Class)

    the service class

Returns:

  • (String)

    underscored namespace path



265
266
267
268
269
# File 'lib/servus/support/validator.rb', line 265

def self.parse_service_namespace(service_class)
  service_class.name.split('::')[..-2].map do |s|
    s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
  end.join('/')
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 ARGUMENTS_SCHEMA.

Checks arguments against either an inline ARGUMENTS_SCHEMA constant or a file-based schema at app/schemas/services/namespace/arguments.json. Validation is skipped if no schema is defined.

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:



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

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:



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

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!(handler_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 event payload against the handler’s payload schema.

Examples:

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

Parameters:

  • handler_class (Class)

    the event handler class

  • payload (Hash)

    the event payload to validate

Returns:

  • (Boolean)

    true if validation passes

Raises:



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

def self.validate_event_payload!(handler_class, payload)
  schema = handler_class.payload_schema
  enforce_schema_presence!(schema, handler_class, :require_event_payload_schema)
  return true unless schema

  validate_data_against_schema!(
    payload,
    schema,
    "Invalid payload for event :#{handler_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