1 module jcli.core.result;
2 
3 struct ResultOf(alias T)
4 {
5     import std.typecons : Nullable;
6 
7     enum IsVoid = is(T == void);
8     alias This = typeof(this);
9 
10     private
11     {
12         string _error = "I've not been initialised.";
13         int _errorCode;
14         static if(!IsVoid)
15             T _value;
16     }
17 
18     static if(IsVoid)
19     {
20         static This ok()()
21         {
22             This t;
23             t._error = null;
24             return t;
25         }
26     }
27     else
28     {
29         static This ok()(T value)
30         {
31             This t;
32             t._error = null;
33             t._value = value;
34             return t;
35         }
36     }
37 
38     static This fail()(string error, int errorCode = -1)
39     {
40         This t;
41         t._error = error;
42         t._errorCode = errorCode;
43         return t;
44     }
45 
46     static if(!is(T == void) && is(T : Nullable!DataT, DataT))
47     void opAssign(inout ResultOf!DataT notNullResult)
48     {
49         this._error = notNullResult._error;
50         this._errorCode = notNullResult._errorCode;
51         this._value = notNullResult._value;
52     }
53 
54     inout:
55 
56     bool isOk()()
57     {
58         return this._error is null;
59     }
60 
61     bool isError()()
62     {
63         return this._error !is null;
64     }
65 
66     string error()()
67     {
68         assert(!this.isOk, "Cannot call .error on an ok result. Please use .isOk to check.");
69         return this._error;
70     }
71 
72     int errorCode()()
73     {
74         assert(!this.isOk, "Cannot call .errorCode on an ok result.");
75         return this._errorCode;
76     }
77 
78     static if(!IsVoid)
79     inout(T) value()() inout
80     {
81         assert(this.isOk, "Cannot call .value on a failed result. Please use .isOk to check.");
82         return this._value;
83     }
84 
85     void enforceOk()()
86     {
87         if(!this.isOk)
88             throw new ResultException(this.error, this.errorCode);
89     }
90 }
91 ///
92 unittest
93 {
94     auto ok     = ResultOf!int.ok(1);
95     auto fail   = ResultOf!int.fail("Bad", 200);
96     auto init   = ResultOf!int.init;
97     auto void_  = Result.ok();
98 
99     assert(ok.isOk);
100     assert(ok.value == 1);
101 
102     assert(!fail.isOk);
103     assert(fail.error == "Bad");
104     assert(fail.errorCode == 200);
105 
106     assert(!init.isOk);
107     assert(init.error);
108 
109     assert(void_.isOk);
110 }
111 
112 alias Result = ResultOf!void;
113 
114 auto ok(T)(T value)
115 {
116     return ResultOf!T.ok(value);
117 }
118 
119 auto ok()()
120 {
121     return Result.ok();
122 }
123 
124 auto fail(T)(string error, int errorCode = -1)
125 {
126     return ResultOf!T.fail(error, errorCode);
127 }
128 
129 class ResultException : Exception
130 {
131     const(int) errorCode;
132 
133     @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null)
134     {
135         this.errorCode = -1;
136         super(msg, file, line, nextInChain);
137     }
138 
139     @nogc @safe pure nothrow this(string msg, int errorCode, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null)
140     {
141         this.errorCode = errorCode;
142         super(msg, file, line, nextInChain);
143     }
144 
145     @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__)
146     {
147         this.errorCode = -1;
148         super(msg, file, line, nextInChain);
149     }
150 }