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 }