module biotronic.validation; import std.conv : to; import biotronic.utils; version (unittest) { import std.exception : assertThrown, assertNotThrown; } version (D_Ddoc) { /** Encapsulates a validated value, the validation of which is enforced through $(LREF validated). $(BR) The unadorned value is available through $(LREF value), and through alias this. $(BR) The constraints can either throw on their own, or return a bool value of true if the constraint passed, false if it didn't. $(BR) Example: ---- bool isPositive(int value) { return value >= 0; } void checkLessThan42(int value) { enforce(value < 42); } void foo(Validated!(int, isPositive) value) { } foo(13); // Refuses to compile. Validated!(int, isPositive, checkLessThan42) x = validated!(isPositive, checkLessThan42)(14); // Would throw on invalid input foo(x); // It works! ---- A validated value A whose constraints are a superset of those of another validated type B may be implicitly converted. The opposite is not possible. Example: ---- alias A = Validated!(int, isPositive, checkLessThan42); alias B = Validated!(int, isPostive); A a = 13; B b = a; a = b; // Error ---- If the wrapped type is convertible, and the constraints match, a type conversion is performed. Example: ---- Validated!(int, isPositive) a = validated!isPositive(4); Validated!(long, isPositive) b = a; ---- **/ struct Validated(T, Constraints) if (Constraints.length > 0 && hasNoDuplicates!Constraints) { /// The wrapped value. @property public T value() { return T.init; } } } template Validated(T, _Constraints...) if (_Constraints.length > 0 && !isSorted!_Constraints && hasNoDuplicates!_Constraints) { alias Validated!(T, StaticSort!(sortPred, _Constraints)) Validated; } struct Validated(T, _Constraints...) if (_Constraints.length > 0 && isSorted!_Constraints && hasNoDuplicates!_Constraints) { alias _Constraints constraints; private T _value; @property inout public T value() { return _value; } alias value this; @disable this(); /+ debug { this(int line = __LINE__, string file = __FILE__)(T other) { alias create = validated!constraints; this = create!(T, line, file)(other); } } else { this(T other) { this = validated!constraints(other); } } +/ this(U)(U other) if (isValidated!U && TypeSet!(U.constraints).superSetOf!(constraints) ) { _value = other._value; } typeof(this) opAssign(U)(U other) if (isValidated!U && TypeSet!(U.constraints).superSetOf!(constraints) && is(typeof(_value = other._value))) { _value = other._value; return this; } inout(U) opCast(U)() inout if (isValidated!U && TypeSet!(constraints).superSetOf!(U.constraints) ) { U result = void; result._value = _value; return result; } inout(U) opCast(U)() inout if (is(T : U)) { return value; } } template isValidated(T...) if (T.length == 1) { static if (is(typeof(T))) { enum isValidated = isValidated!(typeof(T)); } else { enum isValidated = is(T[0] == Validated!U, U...); } } unittest { assert(isValidated!(Validated!(int, isPositive))); assert(isValidated!(validated!(isPositive)(4))); assert(!isValidated!string); assert(!isValidated!"foo"); } /** validated checks that the value passes all constraints, and returns a $(LREF Validated). Example: ---- void foo(Validated!(int, isPositive) value) { } auto a = validated!isPositive(4); foo(a); ---- Multiple constraints may be passed to validated. Example: ---- auto b = validated!(isPositive, checkLessThan42)(54); // Will throw at runtime. ---- **/ template validated(Constraints...) if (Constraints.length > 0) { auto validatedImpl(string loc, T)(T value) { import std.exception : enforce; import std.typetuple : TypeTuple; foreach (fn; Constraints) { staticEnforce!(is(typeof(fn(value))), loc ~ "Invalid constraint " ~ TypeTuple!(fn).stringof[6..$-1] ~ " for value of type " ~ T.stringof); static if (is(typeof(fn(value)) == bool)) { enforce(fn(value), loc ~ "Validation failed for value (" ~ value.to!string ~ "). Constraint: " ~ TypeTuple!(fn).stringof[6..$-1]); } fn(value); } static if (isValidated!T) { Validated!(typeof(T._value), NoDuplicates!(Constraints, T.constraints)) result = void; } else { Validated!(T, Constraints) result = void; } result._value = value; return result; } debug { auto validated(T, int line = __LINE__, string file = __FILE__)(T value) { return validatedImpl!(file ~ "(" ~ line.to!string ~ "): ")(value); } } else { auto validated(T)(T value) { return validatedImpl!""(value); } } } unittest { assertNotThrown(validated!(isPositive)(3)); assertThrown(validated!(isPositive)(-4)); } /** assumeValidated does not run any checks on the passed value, and assumes that the programmer has done so himself. This is useful when checks may be prohibitively expensive or in inner loops where maximum speed is required. Example: ---- auto a = assumeValidated!isPositive(-4); ---- **/ template assumeValidated(Constraints...) if (Constraints.length > 0) { auto assumeValidated(T)(T value) { Validated!(T, Constraints) result = void; result._value = value; return result; } } unittest { assertNotThrown(assumeValidated!isPositive(-2)); } version (unittest) { import std.exception : enforce; bool isPositive(int value) { return value >= 0; } void checkLessThan42(int value) { enforce(value < 42); } void checkString(string value) { } } unittest { void test1(int a) {} void test2(Validated!(int, isPositive)) {} void test3(Validated!(int, isPositive, checkLessThan42)) {} Validated!(int, isPositive, checkLessThan42) a = void; Validated!(int, isPositive) b = void; Validated!(long, isPositive) r = a; Validated!(int, checkString) s = validated!checkString(3); a = validated!(checkLessThan42, isPositive)(3); b = a; a = validated!(checkLessThan42)(b); test1(b); test1(a); test3(a); assert(!__traits(compiles, test2(3))); assert(!__traits(compiles, test3(b))); } void main() { }