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)() 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)() 48 { 49 enum constructorFuncs = { 50 ConstructValueRef[] ret; 51 foreach (ref field; DormFields!This) 52 { 53 foreach (ref annotation; field.internalAnnotations) 54 { 55 annotation.match!( 56 (ConstructValueRef ctor) { 57 ret ~= ctor; 58 }, 59 (_) {} 60 ); 61 } 62 } 63 return ret; 64 }(); 65 static if (constructorFuncs.length) 66 { 67 auto t = cast(This)this; 68 static foreach (fn; constructorFuncs) 69 runValueConstructorImpl!(fn.rid)(t); 70 } 71 } 72 73 /// Runs the defined `@validator` functions on fields, returns a list of 74 /// failed fields. 75 ModelFormat.Field[] runValidators(this This)() pure @safe nothrow 76 { 77 static struct ValidatorInfo { 78 int type; 79 ValidatorRef validator; 80 Choices choices; 81 ModelFormat.Field field; 82 } 83 ModelFormat.Field[] failedFields; 84 enum validatorFuncs = { 85 ValidatorInfo[] ret; 86 foreach (ref field; DormFields!This) 87 { 88 foreach (ref annotation; field.internalAnnotations) 89 { 90 annotation.match!( 91 (ValidatorRef validator) { 92 ret ~= ValidatorInfo(0, validator, Choices.init, field); 93 }, 94 (_) {} 95 ); 96 } 97 foreach (ref annotation; field.annotations) 98 { 99 annotation.value.match!( 100 (Choices choices) { 101 ret ~= ValidatorInfo(1, ValidatorRef.init, choices, field); 102 }, 103 (_) {} 104 ); 105 } 106 } 107 return ret; 108 }(); 109 static if (validatorFuncs.length) 110 { 111 auto t = cast(This)this; 112 static foreach (func; validatorFuncs) 113 {{ 114 static if (func.type == 0) 115 { 116 // validator 117 if (!runValidatorImpl!(func.validator.rid)(t)) 118 failedFields ~= func.field; 119 } 120 else static if (func.type == 1) 121 { 122 // choices 123 alias fieldRef = __traits(getMember, cast(This)this, func.field.sourceColumn); 124 alias FieldT = typeof(fieldRef); 125 126 static if (is(FieldT == enum)) 127 { 128 // we assume that the enum value is simply valid for now. 129 } 130 else static if (is(FieldT : string)) 131 { 132 import std.algorithm : canFind; 133 134 if (!func.choices.choices.canFind(__traits(getMember, cast(This)this, func.field.sourceColumn))) 135 failedFields ~= func.field; 136 } 137 else static assert(false, 138 "Missing DORM implementation: Cannot validate inferred @choices from " 139 ~ This.stringof ~ " -> " ~ func.field.sourceColumn ~ " of type " 140 ~ FieldT.stringof 141 ~ " (choices should only apply to string and enums, don't know what to do with this type)"); 142 } 143 else static assert(false); 144 }} 145 } 146 return failedFields; 147 } 148 } 149 150 private static bool runValidatorImpl(string field, T)(T t) 151 { 152 alias fieldAlias = mixin("t." ~ field); 153 alias attributes = __traits(getAttributes, fieldAlias); 154 155 static foreach (attribute; attributes) 156 { 157 static if (is(attribute == validator!fn, alias fn)) 158 { 159 return fn(mixin("t." ~ field)); 160 } 161 } 162 } 163 164 private static bool runValueConstructorImpl(string field, T)(T t) 165 { 166 alias fieldAlias = mixin("t." ~ field); 167 alias attributes = __traits(getAttributes, fieldAlias); 168 169 static foreach (attribute; attributes) 170 { 171 static if (is(attribute == constructValue!fn, alias fn)) 172 { 173 mixin("t." ~ field) = fn(); 174 return true; // dummy return value 175 } 176 } 177 }