Skip to content

Commit a290097

Browse files
committed
Add initial library code
1 parent 6d15c32 commit a290097

File tree

1 file changed

+189
-0
lines changed

1 file changed

+189
-0
lines changed

TryLibrary.lua

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
function Try(Function, ...)
2+
3+
-- Capture function execution response
4+
local Data = { pcall(Function, ...) };
5+
6+
-- Determine whether execution succeeded or failed
7+
local Success = Data[1];
8+
9+
-- Gather arguments to return from data
10+
local Arguments = { unpack(Data, 2) };
11+
12+
-- Return attempt for chaining
13+
return setmetatable({
14+
_IsAttempt = true,
15+
Then = Then,
16+
Catch = Catch,
17+
Retry = Retry,
18+
Success = Success,
19+
Arguments = Arguments,
20+
Stack = debug.traceback(),
21+
LastArguments = { ... },
22+
Hops = (not Success) and { Function } or nil,
23+
RetryCount = 0,
24+
Start = true
25+
26+
-- Indicate type when converted to string (to aid in debugging)
27+
}, { __tostring = function () return 'Attempt' end })
28+
29+
end;
30+
31+
function Then(Attempt, Callback)
32+
33+
-- Update attempt state
34+
Attempt.Start = false;
35+
36+
-- Enter new attempt contexts if received
37+
local FirstArgument = Attempt.Arguments[1];
38+
if Attempt.Success and type(FirstArgument) == 'table' and FirstArgument._IsAttempt then
39+
Attempt = FirstArgument;
40+
end;
41+
42+
-- Skip processing if attempt failed
43+
if not Attempt.Success then
44+
table.insert(Attempt.Hops, Callback);
45+
return Attempt;
46+
end;
47+
48+
-- Capture callback execution response
49+
local Data = { pcall(Callback, unpack(Attempt.Arguments)) };
50+
local Success = Data[1];
51+
local Arguments = { unpack(Data, 2) };
52+
53+
-- Replace attempt state
54+
Attempt.Success = Success;
55+
Attempt.LastArguments = Attempt.Arguments;
56+
Attempt.Arguments = Arguments;
57+
Attempt.Stack = debug.traceback();
58+
59+
-- Track hops on failure
60+
if not Success then
61+
Attempt.Hops = { Callback };
62+
end
63+
64+
-- Return attempt for chaining
65+
return Attempt;
66+
67+
end;
68+
69+
function Catch(Attempt, ...)
70+
71+
-- Capture all arguments
72+
local Arguments = { ... };
73+
74+
-- Get target errors and the callback
75+
local TargetErrors = { unpack(Arguments, 1, #Arguments - 1) };
76+
local Callback = unpack(Arguments, #Arguments);
77+
78+
-- Enter new attempt contexts if received
79+
local FirstArgument = Attempt.Arguments[1];
80+
if type(FirstArgument) == 'table' and FirstArgument._IsAttempt then
81+
Attempt = FirstArgument;
82+
end;
83+
84+
-- Proceed upon unhandled failure
85+
if not Attempt.Success and not Attempt.Handled then
86+
87+
-- Track hops
88+
table.insert(Attempt.Hops, Arguments);
89+
90+
-- Get error from failed attempt
91+
local Error = Attempt.Arguments[1];
92+
93+
-- Filter errors if target errors were specified
94+
if (#TargetErrors > 0) then
95+
for _, TargetError in pairs(TargetErrors) do
96+
if type(Error) == 'string' and Error:match(TargetError) then
97+
Attempt.Handled = true;
98+
return Try(Callback, Error, Attempt.Stack, Attempt);
99+
end;
100+
end;
101+
102+
-- Pass any error if no target errors were specified
103+
elseif #TargetErrors == 0 then
104+
Attempt.Handled = true;
105+
return Try(Callback, Error, Attempt.Stack, Attempt);
106+
end;
107+
108+
end;
109+
110+
-- Return attempt for chaining
111+
return Attempt;
112+
113+
end;
114+
115+
function Retry(Attempt)
116+
117+
-- Ensure attempt failed
118+
if Attempt.Success then
119+
return;
120+
end;
121+
122+
-- Get hops and arguments
123+
local Hops = Attempt.Hops;
124+
local Arguments = Attempt.LastArguments;
125+
126+
-- Reset attempt state
127+
Attempt.Hops = nil;
128+
Attempt.Success = true;
129+
Attempt.Arguments = Arguments;
130+
Attempt.Handled = nil;
131+
Attempt.RetryCount = Attempt.RetryCount and (Attempt.RetryCount + 1) or 1;
132+
133+
-- Restart attempts that failed from the start
134+
if Attempt.Start then
135+
local NewAttempt = Try(Hops[1], Arguments);
136+
137+
-- Reset retry counter if reattempt succeeds
138+
if NewAttempt.Success then
139+
NewAttempt.RetryCount = 0;
140+
else
141+
NewAttempt.RetryCount = Attempt.RetryCount;
142+
end;
143+
144+
-- Apply each hop
145+
for HopIndex, Hop in ipairs(Hops) do
146+
if HopIndex > 1 then
147+
148+
-- Apply `then` hops
149+
if type(Hop) == 'function' then
150+
NewAttempt:Then(Hop);
151+
152+
-- Apply `catch` hops
153+
elseif type(Hop) == 'table' then
154+
NewAttempt:Catch(unpack(Hop));
155+
end;
156+
157+
end;
158+
end;
159+
160+
-- Return the new attempt
161+
return NewAttempt;
162+
163+
-- Continue attempts that failed after the start
164+
else
165+
for HopIndex, Hop in ipairs(Hops) do
166+
167+
-- Apply `then` hoops
168+
if type(Hop) == 'function' then
169+
Attempt:Then(Hop);
170+
171+
-- Apply `catch` hops
172+
elseif type(Hop) == 'table' then
173+
Attempt:Catch(unpack(Hop));
174+
end;
175+
176+
-- Reset retry counter if reattempt succeeds
177+
if HopIndex == 1 and Attempt.Success then
178+
Attempt.RetryCount = 0;
179+
end;
180+
181+
end;
182+
183+
-- Return the attempt
184+
return Attempt;
185+
end;
186+
187+
end;
188+
189+
return Try;

0 commit comments

Comments
 (0)