1 module dorm.model; 2 3 import dorm.annotations; 4 import dorm.api.db; 5 import dorm.declarative; 6 import dorm.declarative.conversion; 7 import dorm.types; 8 9 public import dorm.types : DormPatch; 10 11 import std.algorithm; 12 import std.string; 13 import std.sumtype; 14 import std.traits; 15 16 /** 17 * Base Model class for all user-defined DORM Models. This implements running 18 * value constructors and validators. 19 * 20 * This is only checked at compile time using the `this This` template type, so 21 * only blocking invalid usage when calling it on an actual instance of the 22 * user-defined Model type. When casting to a base-class that uses the built-in 23 * generated ID field, it may be possible to circumvent this safety check. 24 */ 25 abstract class Model 26 { 27 /// Default constructor. Runs value constructors. (`@constructValue` UDAs) 28 this(this This)() @safe 29 { 30 applyConstructValue!This(); 31 } 32 33 /// Sets all fields on `this` (with the compile-time class as context) to 34 /// the values in the given Patch struct. 35 void applyPatch(Patch, this This)(Patch patch) 36 { 37 mixin ValidatePatch!(Patch, This); 38 39 auto t = cast(This)this; 40 foreach (i, ref field; patch.tupleof) 41 __traits(getMember, t, Patch.tupleof[i].stringof) = field; 42 } 43 44 /// Explicitly calls value constructors. (`@constructValue` UDAs) 45 /// This is already implicitly called by the default constructor and is 46 /// probably not needed to be called manually. 47 void applyConstructValue(this This)() @safe 48 { 49 enum constructorFuncs = DormModelConstructors!This; 50 static if (constructorFuncs.length) 51 { 52 This t = cast(This)this; 53 static foreach (fn; constructorFuncs) 54 runValueConstructorImpl!fn(t); 55 } 56 } 57 58 /// Runs the defined `@validator` functions on fields, returns a list of 59 /// failed fields. 60 ModelFormat.Field[] runValidators(this This)() pure @safe nothrow 61 { 62 static struct ValidatorInfo { 63 int type; 64 ValidatorRef validator; 65 Choices choices; 66 ModelFormat.Field field; 67 } 68 ModelFormat.Field[] failedFields; 69 enum validatorFuncs = { 70 ValidatorInfo[] ret; 71 foreach (ref field; DormFields!This) 72 { 73 foreach (ref annotation; field.internalAnnotations) 74 { 75 annotation.match!( 76 (ValidatorRef validator) { 77 ret ~= ValidatorInfo(0, validator, Choices.init, field); 78 }, 79 (_) {} 80 ); 81 } 82 foreach (ref annotation; field.annotations) 83 { 84 annotation.value.match!( 85 (Choices choices) { 86 ret ~= ValidatorInfo(1, ValidatorRef.init, choices, field); 87 }, 88 (_) {} 89 ); 90 } 91 } 92 return ret; 93 }(); 94 static if (validatorFuncs.length) 95 { 96 auto t = cast(This)this; 97 static foreach (func; validatorFuncs) 98 {{ 99 static if (func.type == 0) 100 { 101 // validator 102 if (!runValidatorImpl!(func.validator.rid)(t)) 103 failedFields ~= func.field; 104 } 105 else static if (func.type == 1) 106 { 107 // choices 108 alias fieldRef = __traits(getMember, cast(This)this, func.field.sourceColumn); 109 alias FieldT = typeof(fieldRef); 110 111 static if (is(FieldT == enum)) 112 { 113 // we assume that the enum value is simply valid for now. 114 } 115 else static if (is(FieldT : string)) 116 { 117 import std.algorithm : canFind; 118 119 if (!func.choices.choices.canFind(__traits(getMember, cast(This)this, func.field.sourceColumn))) 120 failedFields ~= func.field; 121 } 122 else static assert(false, 123 "Missing DORM implementation: Cannot validate inferred @choices from " 124 ~ This.stringof ~ " -> " ~ func.field.sourceColumn ~ " of type " 125 ~ FieldT.stringof 126 ~ " (choices should only apply to string and enums, don't know what to do with this type)"); 127 } 128 else static assert(false); 129 }} 130 } 131 return failedFields; 132 } 133 } 134 135 private static bool runValidatorImpl(string field, T)(T t) 136 { 137 alias fieldAlias = mixin("t." ~ field); 138 alias attributes = __traits(getAttributes, fieldAlias); 139 140 static foreach (attribute; attributes) 141 { 142 static if (is(attribute == validator!fn, alias fn)) 143 { 144 return fn(mixin("t." ~ field)); 145 } 146 } 147 } 148 149 private static bool runValueConstructorImpl(string field, T)(T t) @safe 150 { 151 alias fieldAlias = mixin("t." ~ field); 152 alias attributes = __traits(getAttributes, fieldAlias); 153 154 static foreach (attribute; attributes) 155 { 156 static if (is(attribute == constructValue!fn, alias fn)) 157 { 158 mixin("t." ~ field) = fn(); 159 return true; // dummy return value 160 } 161 } 162 }