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, EmptyOutputError, EnumerationContext, EnumerationOfType, ExhaustedOutputError, Links, MissingValidation, NotStrict, OutputEmptyGuard, OutputIteratorWithPredicate, OutputTypeGuard, OutputTypeMismatch, Rules, RulesExhaustedGuard, StrictValidationError, ValidationError, ValidationOptions

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

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

Creates a new scheme

See Also:



52
53
54
55
56
# File 'lib/media_types/scheme.rb', line 52

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

  instance_exec(&block) if block_given?
end

Class Method Details

.AllowNil(klazz) ⇒ CaseEqualityWithList

noinspection RubyClassMethodNamingConvention

Allows the wrapped klazz to be nil



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



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



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

See Also:



204
205
206
207
208
209
210
211
212
213
214
# File 'lib/media_types/scheme.rb', line 204

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



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

#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

See Also:



152
153
154
155
156
157
158
159
160
161
162
# File 'lib/media_types/scheme.rb', line 152

def attribute(key, type = ::Object, optional: false, **opts, &block)
  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

See Also:



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/media_types/scheme.rb', line 291

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

#inspect(indentation = 0) ⇒ Object



349
350
351
352
353
354
355
356
# File 'lib/media_types/scheme.rb', line 349

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:



341
342
343
344
345
346
347
# File 'lib/media_types/scheme.rb', line 341

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



221
222
223
224
# File 'lib/media_types/scheme.rb', line 221

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:



247
248
249
# File 'lib/media_types/scheme.rb', line 247

def not_strict
  rules.default = NotStrict.new
end

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

Checks if the output is valid



68
69
70
71
72
73
74
# File 'lib/media_types/scheme.rb', line 68

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

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

Raises:

  • ExhaustedOutputError

  • StrictValidationError

  • EmptyOutputError

  • CollectionTypeError

  • ValidationError

See Also:



94
95
96
97
98
99
100
# File 'lib/media_types/scheme.rb', line 94

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

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

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



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

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