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 }