Class: MediaTypes::Scheme

Inherits:
Object
  • Object
show all
Defined in:
lib/media_types/scheme.rb,
lib/media_types/scheme/links.rb,
lib/media_types/scheme/rules.rb,
lib/media_types/scheme/any_of.rb,
lib/media_types/scheme/errors.rb,
lib/media_types/scheme/allow_nil.rb,
lib/media_types/scheme/attribute.rb,
lib/media_types/scheme/not_strict.rb,
lib/media_types/scheme/output_type_guard.rb,
lib/media_types/scheme/missing_validation.rb,
lib/media_types/scheme/output_empty_guard.rb,
lib/media_types/scheme/validation_options.rb,
lib/media_types/scheme/enumeration_context.rb,
lib/media_types/scheme/enumeration_of_type.rb,
lib/media_types/scheme/rules_exhausted_guard.rb,
lib/media_types/scheme/output_iterator_with_predicate.rb

Overview

Media Type Schemes can validate content to a media type, by itself. Used by the ‘validations` dsl.

Examples:

A scheme to test against


class MyMedia
  include MediaTypes::Dsl

  validations do
    attribute :foo do
      collection :bar, String
    end
    attribute :number, Numeric
  end
end

MyMedia.valid?({ foo: { bar: ['test'] }, number: 42 })
#=> true

See Also:

Defined Under Namespace

Classes: Attribute, CaseEqualityWithList, DuplicateKeyError, EmptyOutputError, EnumerationContext, EnumerationOfType, ExhaustedOutputError, KeyTypeError, Links, MissingValidation, NotStrict, OutputEmptyGuard, OutputIteratorWithPredicate, OutputTypeGuard, OutputTypeMismatch, Rules, RulesExhaustedGuard, StrictValidationError, ValidationError, ValidationOptions

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(allow_empty: false, expected_type: ::Object, &block) ⇒ Scheme

Creates a new scheme

Parameters:

  • allow_empty (TrueClass, FalseClass) (defaults to: false)

    if true allows to be empty, if false raises EmptyOutputError if empty

  • expected_type (NilClass, Class) (defaults to: ::Object)

    forces the type to be this type, if given

See Also:



57
58
59
60
61
62
# File 'lib/media_types/scheme.rb', line 57

def initialize(allow_empty: false, expected_type: ::Object, &block)
  self.rules = Rules.new(allow_empty: allow_empty, expected_type: expected_type)
  self.type_attributes = {}

  instance_exec(&block) if block_given?
end

Instance Attribute Details

#type_attributesObject

Returns the value of attribute type_attributes.



64
65
66
# File 'lib/media_types/scheme.rb', line 64

def type_attributes
  @type_attributes
end

Class Method Details

.AllowNil(klazz) ⇒ CaseEqualityWithList

noinspection RubyClassMethodNamingConvention

Allows the wrapped klazz to be nil

Parameters:

  • klazz (Class)

    the class that it must be the if it is not NilClass

Returns:



15
16
17
# File 'lib/media_types/scheme/allow_nil.rb', line 15

def AllowNil(klazz) # rubocop:disable Naming/MethodName
  AnyOf(NilClass, klazz)
end

.AnyOf(*klazzes) ⇒ CaseEqualityWithList

noinspection RubyClassMethodNamingConvention

Allows it to be any of the wrapped klazzes

Parameters:

  • klazzes (Array<Class>)

    the classes that are valid for it

Returns:



26
27
28
# File 'lib/media_types/scheme/any_of.rb', line 26

def AnyOf(*klazzes) # rubocop:disable Naming/MethodName
  CaseEqualityWithList.new(klazzes)
end

Instance Method Details

#AllowNil(klazz) ⇒ CaseEqualityWithList

noinspection RubyInstanceMethodNamingConvention

Allows the wrapped klazz to be nil

Parameters:

  • klazz (Class)

    the class that it must be the if it is not NilClass

Returns:



26
27
28
# File 'lib/media_types/scheme/allow_nil.rb', line 26

def AllowNil(klazz) # rubocop:disable Naming/MethodName
  self.class.AllowNil(klazz)
end

#any(scheme = nil, expected_type: ::Hash, allow_empty: false, &block) ⇒ Object

Allow for any key.

The +&block+ defines the Schema for each value.

Examples:

Add a collection named foo, expecting any key with a defined value


class MyMedia
  include MediaTypes::Dsl

  validations do
    collection :foo do
      any do
        attribute :bar, String
      end
    end
  end
end

MyMedia.valid?({ foo: [{ anything: { bar: 'my-string' }, other_thing: { bar: 'other-string' } }] })
# => true

Any key, but all of them String or Numeric


class MyMedia
  include MediaTypes::Dsl

  validations do
    any AnyOf(String, Numeric)
  end
end

MyMedia.valid?({ foo: 'my-string', bar: 42 })
# => true

Parameters:

  • scheme (Scheme, NilClass) (defaults to: nil)

    scheme to use if no &block is given

  • allow_empty (TrueClass, FalseClass) (defaults to: false)

    if true, empty (no key/value present) is allowed

  • expected_type (Class) (defaults to: ::Hash)

    forces the validated object to have this type

See Also:



218
219
220
221
222
223
224
225
226
227
228
# File 'lib/media_types/scheme.rb', line 218

def any(scheme = nil, expected_type: ::Hash, allow_empty: false, &block)
  unless block_given?
    if scheme.is_a?(Scheme)
      return rules.default = scheme
    end

    return rules.default = Attribute.new(scheme)
  end

  rules.default = Scheme.new(allow_empty: allow_empty, expected_type: expected_type, &block)
end

#AnyOf(*klazzes) ⇒ CaseEqualityWithList

noinspection RubyInstanceMethodNamingConvention

Allows it to be any of the wrapped klazzes

Parameters:

  • klazzes (Array<Class>)

    the classes that are valid for it

Returns:



37
38
39
# File 'lib/media_types/scheme/any_of.rb', line 37

def AnyOf(*klazzes) # rubocop:disable Naming/MethodName
  self.class.AnyOf(*klazzes)
end

#assert_fail(fixture) ⇒ Object

Raises:



393
394
395
396
397
398
399
400
401
402
# File 'lib/media_types/scheme.rb', line 393

def assert_fail(fixture)
  json = JSON.parse(fixture, { symbolize_names: true })

  begin
    validate(json)
  rescue MediaTypes::Scheme::ValidationError
    return
  end
  raise AssertionError
end

#assert_pass(fixture) ⇒ Object



387
388
389
390
391
# File 'lib/media_types/scheme.rb', line 387

def assert_pass(fixture)
  json = JSON.parse(fixture, { symbolize_names: true })

  validate(json)
end

#attribute(key, type = ::Object, optional: false, **opts, &block) ⇒ Object

Adds an attribute to the schema

If a +block+ is given, uses that to test against instead of +type+

Examples:

Add an attribute named foo, expecting a string


class MyMedia
  include MediaTypes::Dsl

  validations do
    attribute :foo, String
  end
end

MyMedia.valid?({ foo: 'my-string' })
# => true

Add an attribute named foo, expecting nested scheme


class MyMedia
  include MediaTypes::Dsl

  validations do
    attribute :foo do
      attribute :bar, String
    end
  end
end

MyMedia.valid?({ foo: { bar: 'my-string' }})
# => true

Parameters:

  • key (Symbol)

    the attribute name

  • opts (Hash)

    options to pass to Scheme or Attribute

  • type (Class, #===, Scheme) (defaults to: ::Object)

    The type of the value, can be anything that responds to #===, or scheme to use if no &block is given. Defaults to Object without a &block and to Hash with a &block. or scheme to use if no &block is given. Defaults to Object without a &block and to Hash with a &block.

Raises:

See Also:



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/media_types/scheme.rb', line 161

def attribute(key, type = ::Object, optional: false, **opts, &block)
  raise KeyTypeError, "Unexpected key type #{key.class.name}, please use either a symbol or string." unless key.is_a?(String) || key.is_a?(Symbol)
  raise DuplicateKeyError, "An attribute with key #{key.to_s} has already been defined. Please remove one of the two." if rules.has_key?(key)
  raise DuplicateKeyError, "A string attribute with the same string representation as the symbol :#{key.to_s} already exists. Please remove one of the two." if key.is_a?(Symbol)&& rules.has_key?(key.to_s)
  raise DuplicateKeyError, "A symbol attribute with the same string representation as the string '#{key}' already exists. Please remove one of the two." if key.is_a?(String) && rules.has_key?(key.to_sym)

  if block_given?
    return collection(key, expected_type: ::Hash, optional: optional, **opts, &block)
  end

  if type.is_a?(Scheme)
    return rules.add(key, type, optional: optional)
  end

  rules.add(key, Attribute.new(type, **opts, &block), optional: optional)
end

#collection(key, scheme = nil, allow_empty: false, expected_type: ::Array, optional: false, &block) ⇒ Object

Expect a collection such as an array or hash.

The +block+ defines the Schema for each item in that collection.

Examples:

Collection with an array of string


class MyMedia
  include MediaTypes::Dsl

  validations do
    collection :foo, String
  end
end

MyMedia.valid?({ collection: ['foo', 'bar'] })
# => true

Collection with defined scheme


class MyMedia
  include MediaTypes::Dsl

  validations do
    collection :foo do
      attribute :required, String
      attribute :number, Numeric
    end
  end
end

MyMedia.valid?({ foo: [{ required: 'test', number: 42 }, { required: 'other', number: 0 }] })
# => true

Parameters:

  • key (Symbol)

    key of the collection (same as #attribute)

  • scheme (NilClass, Scheme, Class) (defaults to: nil)

    scheme to use if no &block is given, or type of each item in collection

  • allow_empty (TrueClass, FalseClass) (defaults to: false)

    if true accepts 0 items in an enumerable

  • expected_type (Class) (defaults to: ::Array)

    forces the value of this collection to be this type, defaults to Array.

See Also:



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/media_types/scheme.rb', line 305

def collection(key, scheme = nil, allow_empty: false, expected_type: ::Array, optional: false, &block)
  unless block_given?
    return rules.add(
      key,
      EnumerationOfType.new(
        scheme,
        enumeration_type: expected_type,
        allow_empty: allow_empty
      ),
      optional: optional
    )
  end

  rules.add(key, Scheme.new(allow_empty: allow_empty, expected_type: expected_type, &block), optional: optional)
end

#emptyObject

Mark object as a valid empty object

Examples:

Empty object


class MyMedia
  include MediaTypes::Dsl

  validations do
    empty
  end
end


375
376
# File 'lib/media_types/scheme.rb', line 375

def empty
end

#inspect(indentation = 0) ⇒ Object



378
379
380
381
382
383
384
385
# File 'lib/media_types/scheme.rb', line 378

def inspect(indentation = 0)
  tabs = '  ' * indentation
  [
    "#{tabs}[Scheme]",
    rules.inspect(indentation + 1),
    "#{tabs}[/Scheme]"
  ].join("\n")
end

Expect a link

Examples:

Links as defined in HAL, JSON-Links and other specs


class MyMedia
  include MediaTypes::Dsl

  validations do
    link :_self
    link :image
  end
end

MyMedia.valid?({ _links: { self: { href: 'https://example.org/s' }, image: { href: 'https://image.org/i' }} })
# => true

Link with extra attributes


class MyMedia
  include MediaTypes::Dsl

  validations do
    link :image do
      attribute :templated, TrueClass
    end
  end
end

MyMedia.valid?({ _links: { image: { href: 'https://image.org/{md5}', templated: true }} })
# => true

See Also:



355
356
357
358
359
360
361
# File 'lib/media_types/scheme.rb', line 355

def link(*args, **opts, &block)
  rules.fetch(:_links) do
    Links.new.tap do |links|
      rules.add(:_links, links)
    end
  end.link(*args, **opts, &block)
end

#merge(scheme, &block) ⇒ Object

Merges a scheme into this scheme without changing the incoming scheme

Parameters:

  • scheme (Scheme)

    the scheme to merge into this



235
236
237
238
# File 'lib/media_types/scheme.rb', line 235

def merge(scheme, &block)
  self.rules = rules.merge(scheme.send(:rules))
  instance_exec(&block) if block_given?
end

#not_strictObject

Allow for extra keys in the schema/collection even when passing strict: true to #validate!

Examples:

Allow for extra keys in collection


class MyMedia
  include MediaTypes::Dsl

  validations do
    collection :foo do
      attribute :required, String
      not_strict
    end
  end
end

MyMedia.valid?({ foo: [{ required: 'test', bar: 42 }] })
# => true

See Also:



261
262
263
# File 'lib/media_types/scheme.rb', line 261

def not_strict
  rules.default = NotStrict.new
end

#valid?(output, **opts) ⇒ TrueClass, FalseClass

Checks if the output is valid

Parameters:

  • output (#each)

    the output to test against

  • opts (Hash)

    the options as defined below

  • exhaustive (Hash)

    a customizable set of options

  • strict (Hash)

    a customizable set of options

Returns:

  • (TrueClass, FalseClass)

    true if valid, false otherwise



76
77
78
79
80
81
82
# File 'lib/media_types/scheme.rb', line 76

def valid?(output, **opts)
  validate(output, **opts)
rescue ExhaustedOutputError
  !opts.fetch(:exhaustive) { true }
rescue ValidationError
  false
end

#validate(output, options = nil, **opts) ⇒ TrueClass

Validates the output and raises on certain validation errors

Parameters:

  • output (#each)

    output to validate

  • opts (Hash)

    a customizable set of options

  • opts[Array<String>] (Hash)

    a customizable set of options

Options Hash (**opts):

  • exhaustive (TrueClass, FalseClass)

    if true, the entire schema needs to be consumed

  • strict (TrueClass, FalseClass)

    if true, no extra keys may be present in output

Returns:

  • (TrueClass)

Raises:

  • ExhaustedOutputError

  • StrictValidationError

  • EmptyOutputError

  • CollectionTypeError

  • ValidationError

See Also:



102
103
104
105
106
107
108
109
# File 'lib/media_types/scheme.rb', line 102

def validate(output, options = nil, **opts)
  options ||= ValidationOptions.new(**opts)
  options.context = output

  catch(:end) do
    validate!(output, options, context: nil)
  end
end

#validate!(output, call_options, **_opts) ⇒ Object



114
115
116
117
118
# File 'lib/media_types/scheme.rb', line 114

def validate!(output, call_options, **_opts)
  OutputTypeGuard.call(output, call_options, rules: rules)
  OutputEmptyGuard.call(output, call_options, rules: rules)
  RulesExhaustedGuard.call(output, call_options, rules: rules)
end