# frozen_string_literal: true module Deimos # Represents a field in the schema. class SchemaField # @return [String] attr_accessor :name # @return [String] attr_accessor :type # @return [Array] attr_accessor :enum_values # @return [Object] attr_accessor :default # @param name [String] # @param type [Object] # @param enum_values [Array] # @param default [Object] def initialize(name, type, enum_values=[], default=:no_default) @name = name @type = type @enum_values = enum_values @default = default end end module SchemaBackends # Base class for encoding / decoding. class Base # @return [String] attr_accessor :schema # @return [String] attr_accessor :namespace # @return [String] attr_accessor :key_schema # @param schema [String,Symbol] # @param namespace [String] def initialize(schema:, namespace: nil) @schema = schema @namespace = namespace end # Encode a payload with a schema. Public method. # @param payload [Hash] # @param schema [String,Symbol] # @param topic [String] # @return [String] def encode(payload, schema: nil, topic: nil) validate(payload, schema: schema || @schema) encode_payload(payload, schema: schema || @schema, topic: topic) end # Decode a payload with a schema. Public method. # @param payload [String] # @param schema [String,Symbol] # @return [Hash,nil] def decode(payload, schema: nil) return nil if payload.nil? decode_payload(payload, schema: schema || @schema) end # Given a hash, coerce its types to our schema. To be defined by subclass. # @param payload [Hash] # @return [Hash] def coerce(payload) result = {} self.schema_fields.each do |field| name = field.name next unless payload.key?(name) val = payload[name] result[name] = coerce_field(field, val) end result.with_indifferent_access end # Indicate a class which should act as a mocked version of this backend. # This class should perform all validations but not actually do any # encoding. # Note that the "mock" version (e.g. avro_validation) should return # its own symbol when this is called, since it may be called multiple # times depending on the order of RSpec helpers. # @return [Symbol] def self.mock_backend :mock end # The content type to use when encoding / decoding requests over HTTP via ActionController. # @return [String] def self.content_type raise NotImplementedError end # Converts your schema to String form for generated YARD docs. # To be defined by subclass. # @param schema [Object] # @return [String] A string representation of the Type def self.field_type(schema) raise NotImplementedError end # Encode a payload. To be defined by subclass. # @param payload [Hash] # @param schema [String,Symbol] # @param topic [String] # @return [String] def encode_payload(payload, schema:, topic: nil) raise NotImplementedError end # Decode a payload. To be defined by subclass. # @param payload [String] # @param schema [String,Symbol] # @return [Hash] def decode_payload(payload, schema:) raise NotImplementedError end # Validate that a payload matches the schema. To be defined by subclass. # @param payload [Hash] # @param schema [String,Symbol] # @return [void] def validate(payload, schema:) raise NotImplementedError end # List of field names belonging to the schema. To be defined by subclass. # @return [Array] def schema_fields raise NotImplementedError end # Given a value and a field definition (as defined by whatever the # underlying schema library is), coerce the given value to # the given field type. # @param field [SchemaField] # @param value [Object] # @return [Object] def coerce_field(field, value) raise NotImplementedError end # Given a field definition, return the SQL type that might be used in # ActiveRecord table creation - e.g. for Avro, a `long` type would # return `:bigint`. There are also special values that need to be returned: # `:array`, `:map` and `:record`, for types representing those structures. # `:enum` is also recognized. # @param field [SchemaField] # @return [Symbol] def sql_type(field) raise NotImplementedError end # Encode a message key. To be defined by subclass. # @param key [String,Hash] the value to use as the key. # @param key_id [String,Symbol] the field name of the key. # @param topic [String] # @return [String] def encode_key(key, key_id, topic: nil) raise NotImplementedError end # Decode a message key. To be defined by subclass. # @param payload [Hash] the message itself. # @param key_id [String,Symbol] the field in the message to decode. # @return [String] def decode_key(payload, key_id) raise NotImplementedError end # Forcefully loads the schema into memory. # @return [Object] The schema that is of use. def load_schema raise NotImplementedError end end end end