Membrane provides an easy to use DSL for specifying validators declaratively. It's intended to be used to validate data received from external sources, such as API endpoints or config files. Use it at the edges of your process to decide what data to let in and what to keep out.
The core concept behind Membrane is the schema
. A schema
represents an invariant about a piece of data (similar to a type) and is
capable of verifying whether or not a supplied datum satisfies the
invariant. Schemas may be composed together to produce more expressive
constructs.
Membrane provides a handful of useful schemas out of the box. You should be able to construct the majority of your schemas using only what is provided by default. The provided schemas are:
- Any - Accepts all values. Use it sparingly.
- Bool - Accepts
true
andfalse
. - Class - Accepts instances of a supplied class.
- Dictionary - Accepts hashes whose keys and values validate against their respective schemas.
- Enum - Accepts values that validate against any of the supplied schemas. Similar to a sum type.
- List - Accepts arrays where each element of the array validates against a supplied schema.
- Record - Accepts hashes with known keys. Each key has a supplied schema, against which its value must validate.
- Regexp - Accepts strings that match a supplied regular expression.
- Value - Accepts values using
==
.
Membrane schemas are typically created using a concise DSL. For example, the following creates a schema that will validate a hash where the key "ints" maps to a list of integers and the key "string" maps to a string.
schema = Membrane::SchemaParser.parse do
{ "ints" => [Integer],
"string" => String,
}
end
# Validates successfully
schema.validate({
"ints" => [1],
"string" => "hi",
})
# Fails validation. The key "string" is missing and the value for "ints"
# isn't the correct type.
schema.validate({
"ints" => "invalid",
})
This is a more complicated example that illustrate the entire DSL. It should be self-explanatory.
Membrane::SchemaParser.parse do
{ "ints" => [Integer]
"true_or_false" => bool,
"anything" => any,
optional("_") => any,
"one_or_two" => enum(1, 2),
"strs_to_ints" => dict(String, Integer),
"foo_prefix" => /^foo/,
}
end
Adding a new schema is trivial. Any class implementing the following "interface" can be used as a schema:
# @param [Object] The object being validated.
#
# @raise [Membrane::SchemaValidationError] Raised when a supplied object is
# invalid.
#
# @return [nil]
def validate(object)
If you wish to include your new schema as part of the DSL, you'll need to
modify membrane/schema_parser.rb
and have your class inherit from
Membrane::Schema::Base