MCRO
C++23 utilities for Unreal Engine.
Loading...
Searching...
No Matches
Error.h
Go to the documentation of this file.
1/** @noop License Comment
2 * @file
3 * @copyright
4 * This Source Code is subject to the terms of the Mozilla Public License, v2.0.
5 * If a copy of the MPL was not distributed with this file You can obtain one at
6 * https://mozilla.org/MPL/2.0/
7 *
8 * @author David Mórász
9 * @date 2025
10 */
11
12#pragma once
13
14#include "CoreMinimal.h"
15#include "Templates/ValueOrError.h"
16#include "Mcro/Error.Fwd.h"
17#include "Void.h"
18#include "Mcro/Types.h"
19#include "Mcro/Concepts.h"
20#include "Mcro/SharedObjects.h"
21#include "Mcro/Observable.Fwd.h"
22#include "Mcro/TextMacros.h"
23#include "Mcro/Text.h"
25
27#include "yaml-cpp/yaml.h"
29
30#include <source_location>
31
32/** Contains utilities for structured error handling */
33namespace Mcro::Error
34{
35 using namespace Mcro::Text;
36 using namespace Mcro::Types;
37 using namespace Mcro::FunctionTraits;
38 using namespace Mcro::SharedObjects;
39 using namespace Mcro::Delegates;
40
41 /**
42 * @brief A base class for a structured error handling and reporting with modular architecture and fluent API.
43 *
44 * @important
45 * Instantiate errors only with `IError::Make(new FMyError())` this ensures the minimal runtime reflection features.
46 *
47 * Many times runtime errors are unavoidable and if an API only gives indication of success or failure (let's say in
48 * the form of a boolean) that will be quite frustrating for the user to report, as it gives no direction of course
49 * what went wrong, how it went wrong, and when it went wrong. Slightly better when the API gives a list of things
50 * what can go wrong and return an item from that list when things go wrong. This of course still doesn't allow to
51 * provide much of a context for the user.
52 *
53 * An 'improvement' to that is using C++ exceptions, however it is not unanimously well received in the community
54 * because it can hide the fact that the API can bail on its caller. So when exceptions are enabled one may call
55 * every function of an API like if they were walking on a minefield. For this (and a couple more) reasons C++
56 * exceptions are disabled by default in Unreal projects and viewed as terrible practice to introduce it ourselves.
57 *
58 * Unreal does provide though the `TValueOrError` template which allows API's to indicate that they can fail in some
59 * ways without the need to consult an external documentation. It gives the developer total freedom however of what
60 * the error can be, so on its own it does not solve the questions of what/when/how.
61 *
62 * Using `TMaybe` with IError can be a powerful tool in the developer's arsenal when creating a library.
63 * IError can standardize a detailed and structured way of communicating errors without hindering call-site
64 * usage. It can also automate the method and the format of logging the (many times excessive amount of)
65 * information surrounding an error, or decide how it may be presented for the user.
66 */
67 class MCRO_API IError : public IHaveTypeShareable
68 {
69 protected:
70 TMap<FString, IErrorRef> InnerErrors;
71 TArray<std::source_location> ErrorPropagation;
72 EErrorSeverity Severity = EErrorSeverity::ErrorComponent;
73 FString Message;
74 FString Details;
75 FString CodeContext;
76 mutable bool bIsRoot = false;
77
78 /** @brief Override this method if inner errors needs custom way of serialization */
79 virtual void SerializeInnerErrors(YAML::Emitter& emitter) const;
80
81 /** @brief Override this method if error propagation history needs custom way of serialization */
82 virtual void SerializeErrorPropagation(YAML::Emitter& emitter) const;
83
84 /** @brief Override this method if inner errors added to current one needs special attention */
85 virtual void AddError(const FString& name, const TSharedRef<IError>& error, const FString& typeOverride = {});
86
87 /** @brief Add extra separate blocks of text in an ad-hoc fashion */
88 virtual void AddAppendix(const FString& name, const FString& text, const FString& type = TEXT_"Appendix");
89
90 void AddCppStackTrace(const FString& name, int32 numAdditionalStackFramesToIgnore, bool fastWalk);
91 void AddBlueprintStackTrace(const FString& name);
92
93 /**
94 * @brief
95 * Override this method if direct members should be serialized differently or extra members are added by
96 * derived errors.
97 */
98 virtual void SerializeMembers(YAML::Emitter& emitter) const;
99
101
102 public:
103
104 FORCEINLINE decltype(InnerErrors)::TRangedForIterator begin() { return InnerErrors.begin(); }
105 FORCEINLINE decltype(InnerErrors)::TRangedForConstIterator begin() const { return InnerErrors.begin(); }
106 FORCEINLINE decltype(InnerErrors)::TRangedForIterator end() { return InnerErrors.end(); }
107 FORCEINLINE decltype(InnerErrors)::TRangedForConstIterator end() const { return InnerErrors.end(); }
108
109 /**
110 * @brief
111 * This is an empty function so any IError can fulfill CSharedInitializeable without needing extra
112 * attention in derived classes. Simply hide this function with overloads in derived classes if they need
113 * to use TSharedFromThis for initialization
114 */
115 void Initialize() {};
116
117 /**
118 * @brief
119 * Override this function to change the method how this error is entirely serialized into a YAML format
120 *
121 * @param emitter the YAML node into which the data of this error needs to be appended to
122 */
123 virtual void SerializeYaml(YAML::Emitter& emitter) const;
124
125 /** @brief Overload append operator for YAML::Emitter */
126 friend auto operator << (YAML::Emitter& emitter, IErrorRef const& error) -> YAML::Emitter&;
127
128 /** @brief Render this error as a string using the YAML representation */
129 FString ToString() const;
130
131 /** @brief Render this error as a std::string using the YAML representation */
132 std::string ToStringUtf8() const;
133
134 /**
135 * @brief To ensure automatic type reflection use IError::Make instead of manually constructing error objects
136 *
137 * @code
138 * IError::Make(new FMyError(myConstructorArgs), myInitializerArgs);
139 * @endcode
140 *
141 * @tparam T Type of new error
142 * @tparam Args Arguments for the new error initializer.
143 * @param newError Pass the new object in as `new FMyError(...)`
144 * @param args Arguments for the new error initializer.
145 */
146 template <CError T, typename... Args>
147 requires CSharedInitializeable<T, Args...>
148 static TSharedRef<T> Make(T* newError, Args&&... args)
149 {
150 return MakeShareableInit(newError, FWD(args)...)->WithType();
151 }
152
153 /**
154 * @brief
155 * When `Report` is called on an error this event is triggered, allowing issue tracking systems to react on
156 * IError's which are deemed "ready".
157 *
158 * `ERROR_LOG`, `ERROR_CLOG` and `FErrorManager::DisplayError` automatically report their input error.
159 */
160 static auto OnErrorReported() -> TEventDelegate<void(IErrorRef)>&;
161
162 FORCEINLINE EErrorSeverity GetSeverity() const { return Severity; }
163 FORCEINLINE int32 GetSeverityInt() const { return static_cast<int32>(Severity); }
164 FORCEINLINE FString const& GetMessage() const { return Message; }
165 FORCEINLINE FString const& GetDetails() const { return Details; }
166 FORCEINLINE FString const& GetCodeContext() const { return CodeContext; }
167 FORCEINLINE TMap<FString, IErrorRef> const& GetInnerErrors() const { return InnerErrors; }
168 FORCEINLINE int32 GetInnerErrorCount() const { return InnerErrors.Num(); }
169
170 /**
171 * @brief
172 * Get a list of source locations where this error has been handled. This is not equivalent of stack-traces but
173 * rather a historical record of where this error was considered throughout the source code. Each item in this
174 * list is explicitly recorded via WithLocation. The first item is the earliest consideration of this error.
175 */
176 TArray<FString> GetErrorPropagation() const;
177
178 /** @brief Same as `GetErrorPropagation` but items are separated by new line. */
180
181 /** @brief Get the error severity as an unreal string. */
182 FStringView GetSeverityString() const;
183
184 /** @brief Override this function to customize how an error is displaxed for the end-user */
185 virtual TSharedRef<SErrorDisplay> CreateErrorWidget();
186
187 /**
188 * @brief Specify error message with a fluent API
189 * @tparam Self Deducing this
190 * @param self Deduced this (not present in calling arguments)
191 * @param input the message
192 * @param condition Only add message when this condition is satisfied
193 * @return Self for further fluent API setup
194 */
195 template <typename Self>
196 SelfRef<Self> WithMessage(this Self&& self, const FString& input, bool condition = true)
197 {
198 if (condition) self.Message = input;
199 return self.SharedThis(&self);
200 }
201
202 /**
203 * @brief Specify formatted error message with a fluent API
204 * @tparam Self Deducing this
205 * @param self Deduced this (not present in calling arguments)
206 * @param input the message format
207 * @param fmtArgs ordered format arguments
208 * @return Self for further fluent API setup
209 */
210 template <typename Self, CStringFormatArgument... FormatArgs>
211 SelfRef<Self> WithMessageF(this Self&& self, const TCHAR* input, FormatArgs&&... fmtArgs)
212 {
213 self.Message = FString::Format(input, OrderedArguments(FWD(fmtArgs)...));
214 return self.SharedThis(&self);
215 }
216
217 /**
218 * @brief Specify formatted error message with a fluent API
219 * @tparam Self Deducing this
220 * @param self Deduced this (not present in calling arguments)
221 * @param condition Only add message when this condition is satisfied
222 * @param input the message format
223 * @param fmtArgs format arguments
224 * @return Self for further fluent API setup
225 */
226 template <typename Self, typename... FormatArgs>
227 SelfRef<Self> WithMessageFC(this Self&& self, bool condition, const TCHAR* input, FormatArgs&&... fmtArgs)
228 {
229 if (condition) self.Message = FString::Format(input, OrderedArguments(FWD(fmtArgs)...));
230 return self.SharedThis(&self);
231 }
232
233 /**
234 * @brief Specify severity with a fluent API
235 * @tparam Self Deducing this
236 * @param self Deduced this (not present in calling arguments)
237 * @param input the severity
238 * @return Self for further fluent API setup
239 * @see EErrorSeverity
240 */
241 template <typename Self>
243 {
244 self.Severity = input;
245 return self.SharedThis(&self);
246 }
247
248 /** @brief Recoverable shorthand */
249 template <typename Self>
250 SelfRef<Self> AsRecoverable(this Self&& self)
251 {
252 self.Severity = EErrorSeverity::Recoverable;
253 return self.SharedThis(&self);
254 }
255
256 /** @brief Fatal shorthand */
257 template <typename Self>
258 SelfRef<Self> AsFatal(this Self&& self)
259 {
260 self.Severity = EErrorSeverity::Fatal;
261 return self.SharedThis(&self);
262 }
263
264 /** @brief Crashing shorthand */
265 template <typename Self>
266 SelfRef<Self> AsCrashing(this Self&& self)
267 {
268 self.Severity = EErrorSeverity::Crashing;
269 return self.SharedThis(&self);
270 }
271
272 /**
273 * @brief
274 * Specify details for the error which may provide further context for the user or provide them
275 * reminders/suggestions
276 *
277 * @tparam Self Deducing this
278 * @param self Deduced this (not present in calling arguments)
279 * @param input the details text
280 * @param condition Only add details when this condition is satisfied
281 * @return Self for further fluent API setup
282 */
283 template <typename Self>
284 SelfRef<Self> WithDetails(this Self&& self, const FString& input, bool condition = true)
285 {
286 if (condition) self.Details = input;
287 return self.SharedThis(&self);
288 }
289
290 /**
291 * @brief
292 * Specify formatted details for the error which may provide further context for the user or provide them
293 * reminders/suggestions
294 *
295 * @tparam Self Deducing this
296 * @param self Deduced this (not present in calling arguments)
297 * @param input the details text
298 * @param fmtArgs ordered format arguments
299 * @return Self for further fluent API setup
300 */
301 template <typename Self, CStringFormatArgument... FormatArgs>
302 SelfRef<Self> WithDetailsF(this Self&& self, const TCHAR* input, FormatArgs&&... fmtArgs)
303 {
304 self.Details = FString::Format(input, OrderedArguments(FWD(fmtArgs)...));
305 return self.SharedThis(&self);
306 }
307
308 /**
309 * @brief
310 * Specify formatted details for the error which may provide further context for the user or provide them
311 * reminders/suggestions
312 *
313 * @tparam Self Deducing this
314 * @param self Deduced this (not present in calling arguments)
315 * @param input the details text
316 * @param condition Only add details when this condition is satisfied
317 * @param fmtArgs ordered format arguments
318 * @return Self for further fluent API setup
319 */
320 template <typename Self, CStringFormatArgument... FormatArgs>
321 SelfRef<Self> WithDetailsFC(this Self&& self, bool condition, const TCHAR* input, FormatArgs&&... fmtArgs)
322 {
323 if (condition) self.Details = FString::Format(input, OrderedArguments(FWD(fmtArgs)...));
324 return self.SharedThis(&self);
325 }
326
327 /**
328 * @brief If available write a source code context into the error directly displaying where this error has occured
329 * @tparam Self Deducing this
330 * @param self Deduced this (not present in calling arguments)
331 * @param input the source code context
332 * @param condition Only add code context when this condition is satisfied
333 * @return Self for further fluent API setup
334 */
335 template <typename Self>
336 SelfRef<Self> WithCodeContext(this Self&& self, const FString& input, bool condition = true)
337 {
338 if (condition) self.CodeContext = input;
339 return self.SharedThis(&self);
340 }
341
342 /**
343 * @brief Add a uniquely typed inner error.
344 * @tparam Self Deducing this
345 * @tparam Error Deduced type of the error
346 * @param self Deduced this (not present in calling arguments)
347 * @param input Inner error
348 * @param condition Only add inner error when this condition is satisfied
349 * @return Self for further fluent API setup
350 */
351 template <typename Self, CError Error>
352 SelfRef<Self> WithError(this Self&& self, const TSharedRef<Error>& input, bool condition = true)
353 {
354 if (condition) self.AddError({}, input);
355 return self.SharedThis(&self);
356 }
357
358 /**
359 * @brief Add one inner error with specific name.
360 * @tparam Self Deducing this
361 * @tparam Error Deduced type of the error
362 * @param self Deduced this (not present in calling arguments)
363 * @param name Optional name of the error. If it's empty only the type of the error will be used for ID
364 * @param input Inner error
365 * @param condition Only add inner error when this condition is satisfied
366 * @return Self for further fluent API setup
367 */
368 template <typename Self, CError Error>
369 SelfRef<Self> WithError(this Self&& self, const FString& name, const TSharedRef<Error>& input, bool condition = true)
370 {
371 if (condition) self.AddError(name, input);
372 return self.SharedThis(&self);
373 }
374
375 /**
376 * @brief Add multiple errors at once with optional names
377 * @tparam Self Deducing this
378 * @param input An array of tuples with otional error name and the error itself
379 * @param condition Only add errors when this condition is satisfied
380 * @return Self for further fluent API setup
381 */
382 template <typename Self>
383 SelfRef<Self> WithErrors(this Self&& self, const TArray<TTuple<FString, IErrorRef>>& input, bool condition = true)
384 {
385 if (condition)
386 {
387 for (const auto& error : input)
388 self.AddError(error.Key, error.Value);
389 }
390 return self.SharedThis(&self);
391 }
392
393 /**
394 * @brief Add multiple errors at once
395 * @tparam Self Deducing this
396 * @tparam Errors Deduced type of the errors
397 * @param errors Errors to be added
398 * @return Self for further fluent API setup
399 */
400 template <typename Self, CError... Errors>
401 SelfRef<Self> WithErrors(this Self&& self, const TSharedRef<Errors>&... errors)
402 {
403 (self.AddError({}, errors), ...);
404 return self.SharedThis(&self);
405 }
406
407 /**
408 * @brief Add multiple errors at once
409 * @tparam Self Deducing this
410 * @tparam Errors Deduced type of the errors
411 * @param errors Errors to be added
412 * @param condition Only add errors when this condition is satisfied
413 * @return Self for further fluent API setup
414 */
415 template <typename Self, CError... Errors>
416 SelfRef<Self> WithErrors(this Self&& self, bool condition, const TSharedRef<Errors>&... errors)
417 {
418 if (condition) self.WithErrors(errors...);
419 return self.SharedThis(&self);
420 }
421
422 /**
423 * @brief Add an extra plain text block inside inner errors
424 * @tparam Self Deducing this
425 * @param name Name of the extra text block
426 * @param text Value of the extra text block
427 * @param condition Only add inner error when this condition is satisfied
428 * @return Self for further fluent API setup
429 */
430 template <typename Self>
431 SelfRef<Self> WithAppendix(this Self&& self, const FString& name, const FString& text, bool condition = true)
432 {
433 if (condition) self.AddAppendix(name, text);
434 return self.SharedThis(&self);
435 }
436
437 /**
438 * @brief Add an extra plain text block inside inner errors
439 * @tparam Self Deducing this
440 * @param name Name of the extra text block
441 * @param text Value of the extra text block
442 * @param fmtArgs ordered format arguments
443 * @return Self for further fluent API setup
444 */
445 template <typename Self, CStringFormatArgument... FormatArgs>
446 SelfRef<Self> WithAppendixF(this Self&& self, const FString& name, const TCHAR* text, FormatArgs&&... fmtArgs)
447 {
448 self.AddAppendix(name, FString::Format(text, OrderedArguments(FWD(fmtArgs)...)));
449 return self.SharedThis(&self);
450 }
451
452 /**
453 * @brief Add an extra plain text block inside inner errors
454 * @tparam Self Deducing this
455 * @param name Name of the extra text block
456 * @param text Value of the extra text block
457 * @param fmtArgs ordered format arguments
458 * @param condition Only add inner error when this condition is satisfied
459 * @return Self for further fluent API setup
460 */
461 template <typename Self, CStringFormatArgument... FormatArgs>
462 SelfRef<Self> WithAppendixFC(this Self&& self, bool condition, const FString& name, const TCHAR* text, FormatArgs&&... fmtArgs)
463 {
464 if (condition)
465 self.AddAppendix(name, FString::Format(text, OrderedArguments(FWD(fmtArgs)...)));
466 return self.SharedThis(&self);
467 }
468
469 /** @brief Notify an observable state about this error */
470 template <typename Self>
472 {
473 self.NotifyState(state);
474 return self.SharedThis(&self);
475 }
476
477 /** @brief Break if a debugger is attached when this error is created */
478 template <typename Self>
479 SelfRef<Self> BreakDebugger(this Self&& self)
480 {
481 UE_DEBUG_BREAK();
482 return self.SharedThis(&self);
483 }
484
485 /** @brief Shorthand for adding the current C++ stacktrace to this error */
486 template <typename Self>
487 SelfRef<Self> WithCppStackTrace(this Self&& self, const FString& name = {}, bool condition = true, int32 numAdditionalStackFramesToIgnore = 0, bool fastWalk = !UE_BUILD_DEBUG)
488 {
489#if !UE_BUILD_SHIPPING
490 if (condition)
491 self.AddCppStackTrace(name, numAdditionalStackFramesToIgnore + 1, fastWalk);
492#endif
493 return self.SharedThis(&self);
494 }
495
496 /** @brief Shorthand for adding the current Blueprint stacktrace to this error */
497 template <typename Self>
498 SelfRef<Self> WithBlueprintStackTrace(this Self&& self, const FString& name = {}, bool condition = true)
499 {
500#if !UE_BUILD_SHIPPING
501 if (condition)
502 self.AddBlueprintStackTrace(name);
503#endif
504 return self.SharedThis(&self);
505 }
506
507 /**
508 * @brief
509 * Allow the error to record the source locations it has been handled at compile time. For example this gives
510 * more information than stack-traces because it can also record where errors were handled between parallel
511 * threads.
512 *
513 * @tparam Self Deducing this
514 * @param location The location this error is handled at. In 99% of cases this should be left at the default
515 * @return Self for further fluent API setup
516 */
517 template <typename Self>
518 SelfRef<Self> WithLocation(this Self&& self, std::source_location const& location = std::source_location::current())
519 {
520 self.ErrorPropagation.Add(location);
521 return self.SharedThis(&self);
522 }
523
524 /**
525 * @brief
526 * Report this error to the global `IError::OnErrorReported` event once it is deemed "ready", so an issue
527 * tracking system can handle important error unintrusively. Make sure this is the last modification on the
528 * error. Only call it once no further information can be added and on the top-most main error in case of
529 * aggregate errors.
530 *
531 * @tparam Self Deducing this
532 * @param condition Only report errors when this condition is satisfied
533 * @return Self for further fluent API setup
534 */
535 template <typename Self>
536 SelfRef<Self> Report(this Self&& self, bool condition = true)
537 {
538 if (condition)
539 OnErrorReported().Broadcast(self.SharedThis(&self));
540
541 return self.SharedThis(&self);
542 }
543
544 /**
545 * @brief
546 * Call an arbitrary function with this error. Other than cases like invoking macros inline operating on
547 * errors (like the inline ERROR_LOG overload) this may have limited use.
548 *
549 * @tparam Self Deducing this.
550 * @tparam Function Function to call with this error.
551 * @param self Deducing this.
552 * @param function Function to call with this error.
553 * @return Self for further fluent API setup
554 */
555 template <
556 typename Self,
557 CFunctionCompatible_ArgumentsDecay<void(SelfRef<Self>)> Function
558 >
559 SelfRef<Self> AsOperandWith(this Self&& self, Function&& function)
560 {
561 auto sharedSelf = self.SharedThis(&self);
562 function(sharedSelf);
563 return sharedSelf;
564 }
565 };
566
567 /** @brief A simple error type for checking booleans. It adds no extra features to IError */
568 class MCRO_API FAssertion : public IError {};
569
570 /**
571 * @brief
572 * A simple error type denoting that whatever is being accessed is not available like attempting to access nullptr.
573 * It adds no extra features to IError
574 */
575 class MCRO_API FUnavailable : public IError
576 {
577 public:
579 };
580
581 /**
582 * @brief
583 * A `TValueOrError` alternative for IError which allows implicit conversion from values and errors (no need for
584 * `MakeError` or `MakeValue`) and is boolean testable. It also doesn't have ambiguous state such as
585 * `TValueOrError` has, so a TMaybe will always have either an error or a value, it will never have neither of
586 * them or both of them.
587 */
588 template <CNonVoid T>
589 struct TMaybe
590 {
591 using ValueType = T;
592
593 /**
594 * @brief
595 * Default initializing a TMaybe while its value is not default initializable, initializes the resulting
596 * TMaybe to an erroneous state.
597 */
598 template <typename = T>
599 requires (!CDefaultInitializable<T>)
601 ->WithMessageF(
602 TEXT_"TMaybe has been default initialized, but a Value of {0} cannot be default initialized",
604 )
605 ) {}
606
607 /** @brief If T is default initializable then the default state of TMaybe will be the default value of T, and not an error */
608 template <CDefaultInitializable = T>
609 TMaybe() : Value(T{}) {}
610
611 /** @brief Enable copy constructor for T only when T is copy constructable */
612 template <CConvertibleToDecayed<T> From, CCopyConstructible = T>
613 TMaybe(From const& value) : Value(value) {}
614
615 /** @brief Enable move constructor for T only when T is move constructable */
616 template <CConvertibleToDecayed<T> From, CMoveConstructible = T>
617 TMaybe(From&& value) : Value(FWD(value)) {}
618
619 /** @brief Enable copy constructor for TMaybe only when T is copy constructable */
620 template <CConvertibleToDecayed<T> From, CCopyConstructible = T>
621 TMaybe(TMaybe<From> const& other) : Value(other.Value) {}
622
623 /** @brief Enable move constructor for TMaybe only when T is move constructable */
624 template <CConvertibleToDecayed<T> From, CMoveConstructible = T>
625 TMaybe(TMaybe<From>&& other) : Value(MoveTemp(other.Value)) {}
626
627 /** @brief Set this TMaybe to an erroneous state */
628 template <CError ErrorType>
629 TMaybe(TSharedRef<ErrorType> const& error) : Error(error) {}
630
631 bool HasValue() const { return Value.IsSet(); }
632 bool HasError() const { return Error.IsValid(); }
633
634 auto TryGetValue() -> TOptional<T>& { return Value; }
635 auto TryGetValue() const -> TOptional<T> const& { return Value; }
636
637 auto GetValue() -> T& { return Value.GetValue(); }
638 auto GetValue() const -> T const& { return Value.GetValue(); }
639
640 T&& StealValue() && { return MoveTemp(Value.GetValue()); }
641
642 auto GetError() const -> IErrorPtr { return Error; }
643 auto GetErrorRef() const -> IErrorRef { return Error.ToSharedRef(); }
644
645 operator bool() const { return HasValue(); }
646
647 /**
648 * @brief Modify a potential error stored in this monad
649 * @tparam Self Deducing this
650 * @tparam Function Modifying function type
651 * @param self Deducing this
652 * @param mod Input function modifying a potential error
653 * @return Self, preserving qualifiers.
654 */
655 template <typename Self, CFunctionCompatible_ArgumentsDecay<void(IErrorRef)> Function>
656 Self&& ModifyError(this Self&& self, Function&& mod)
657 {
658 if (self.HasError()) mod(self.GetErrorRef());
659 return FWD(self);
660 }
661
662 operator TValueOrError<T, IErrorPtr>() const
663 {
664 if (HasValue())
665 return MakeValue(Value.GetValue());
666 return MakeError(Error);
667 }
668
669 private:
670 TOptional<T> Value;
671 IErrorPtr Error;
672 };
673
674 /** @brief Indicate that an otherwise void function that it may fail with an IError. */
676
677 /**
678 * @brief
679 * Syntactically same as `FCanFail` but for functions which is explicitly used to query some boolean decidable
680 * thing, and which can also provide a reason why the queried thing is false.
681 */
683
684 /** @brief Return an FCanFail or FTrueOrReason indicating a success or truthy output */
685 FORCEINLINE FCanFail Success() { return FVoid(); }
686}
687
688#define MCRO_ERROR_LOG_3(categoryName, verbosity, error) \
689 UE_LOG(categoryName, verbosity, TEXT_"%s", *((error) \
690 ->WithLocation() \
691 ->Report() \
692 ->ToString() \
693 )) //
694
695#define MCRO_ERROR_LOG_2(categoryName, verbosity) \
696 Report() \
697 ->AsOperandWith([](IErrorRef const& error) \
698 { \
699 UE_LOG(categoryName, verbosity, TEXT_"%s", *(error->ToString())); \
700 }) //
701
702/**
703 * @brief Convenience macro for logging an error with UE_LOG
704 *
705 * The following overloads are available:
706 * - `(categoryName, verbosity, error)` Simply use UE_LOG to log the error
707 * - `(categoryName, verbosity)` Log the error preceding this macro (use ERROR_LOG inline).
708 * `WithLocation` is omitted from this overload
709 */
710#define ERROR_LOG(...) MACRO_OVERLOAD(MCRO_ERROR_LOG_, __VA_ARGS__)
711
712#define ERROR_CLOG(condition, categoryName, verbosity, error) \
713 UE_CLOG(condition, categoryName, verbosity, TEXT_"%s", *((error) \
714 ->WithLocation() \
715 ->Report() \
716 ->ToString() \
717 )) //
718
719#define MCRO_ASSERT_RETURN_2(condition, error) \
720 if (UNLIKELY(!(condition))) \
721 return Mcro::Error::IError::Make(new error) \
722 ->WithLocation() \
723 ->AsRecoverable() \
724 ->WithCodeContext(PREPROCESSOR_TO_TEXT(condition)) //
725
726#define MCRO_ASSERT_RETURN_1(condition) MCRO_ASSERT_RETURN_2(condition, Mcro::Error::FAssertion())
727
728/**
729 * @brief Similar to check() macro, but return an error instead of crashing
730 *
731 * The following overloads are available:
732 * - `(condition, error)` Specify error type to return
733 * - `(condition)` Return `FAssertion` error
734 */
735#define ASSERT_RETURN(...) MACRO_OVERLOAD(MCRO_ASSERT_RETURN_, __VA_ARGS__)
736
737#define MCRO_UNAVAILABLE_1(error) \
738 return Mcro::Error::IError::Make(new DEFAULT_ON_EMPTY(error, Mcro::Error::FUnavailable())) \
739 ->WithLocation() \
740 ->AsRecoverable() //
741
742/**
743 * @brief Denote that a resource which is asked for doesn't exist
744 *
745 * The following overloads are available:
746 * - `(error)` Specify error type to return
747 * - `()` Return `FUnavailable` error
748 */
749#define UNAVAILABLE(error) MCRO_UNAVAILABLE_1(error)
750
751#define MCRO_PROPAGATE_FAIL_3(type, var, expression) \
752 type var = (expression); \
753 if (UNLIKELY(var.HasError())) return var.GetError() \
754 ->WithLocation() //
755
756#define MCRO_PROPAGATE_FAIL_2(var, expression) MCRO_PROPAGATE_FAIL_3(auto, var, expression)
757#define MCRO_PROPAGATE_FAIL_1(expression) MCRO_PROPAGATE_FAIL_2(PREPROCESSOR_JOIN(tempResult, __LINE__), expression)
758
759/**
760 * @brief
761 * If a function returns a TMaybe or an FCanFail inside another function which may also return another error use this
762 * convenience macro to propagate the failure.
763 *
764 * The following overloads are available:
765 * - `(expression)` Use this when the value of success is not needed further below. This will create a local variable
766 * with an automatically determined name and type
767 * - `(var, expression)` Use this when the value of success will be used further below. The local variable will be
768 * created with `auto`
769 * - `(type, var, expression)` Use this when the value of success will be used further below and its local variable
770 * should have an explicit type
771 */
772#define PROPAGATE_FAIL(...) MACRO_OVERLOAD(MCRO_PROPAGATE_FAIL_, __VA_ARGS__)
This header exists because STL headers in Android doesn't define STL concepts (other than same_as whi...
Use this header and Start.h in tandem to include third-party library headers which may not tolerate U...
#define FWD(...)
Shorten forwarding expression with this macro so one may not need to specify explicit type.
Definition Macros.h:100
Use this header and End.h in tandem to include third-party library headers which may not tolerate Unr...
Use leading TEXT_ without parenthesis for Unreal compatible text literals.
#define TEXT_
A convenience alternative to Unreal's own TEXT macro but this one doesn't require parenthesis around ...
Definition TextMacros.h:53
These two are the most useful types in the arsenal of the C++ developer. Use these for dummy types or...
"Extension" of a common TMulticastDelegate. It allows to define optional "flags" when adding a bindin...
A simple error type for checking booleans. It adds no extra features to IError.
Definition Error.h:568
A simple error type denoting that whatever is being accessed is not available like attempting to acce...
Definition Error.h:576
A base class for a structured error handling and reporting with modular architecture and fluent API.
Definition Error.h:68
virtual void SerializeYaml(YAML::Emitter &emitter) const
Override this function to change the method how this error is entirely serialized into a YAML format.
FORCEINLINE int32 GetInnerErrorCount() const
Definition Error.h:168
virtual TSharedRef< SErrorDisplay > CreateErrorWidget()
Override this function to customize how an error is displaxed for the end-user.
void Initialize()
This is an empty function so any IError can fulfill CSharedInitializeable without needing extra atten...
Definition Error.h:115
SelfRef< Self > WithError(this Self &&self, const FString &name, const TSharedRef< Error > &input, bool condition=true)
Add one inner error with specific name.
Definition Error.h:369
SelfRef< Self > WithMessage(this Self &&self, const FString &input, bool condition=true)
Specify error message with a fluent API.
Definition Error.h:196
SelfRef< Self > WithMessageFC(this Self &&self, bool condition, const TCHAR *input, FormatArgs &&... fmtArgs)
Specify formatted error message with a fluent API.
Definition Error.h:227
FString ToString() const
Render this error as a string using the YAML representation.
SelfRef< Self > WithCodeContext(this Self &&self, const FString &input, bool condition=true)
If available write a source code context into the error directly displaying where this error has occu...
Definition Error.h:336
SelfRef< Self > WithErrors(this Self &&self, const TSharedRef< Errors > &... errors)
Add multiple errors at once.
Definition Error.h:401
SelfRef< Self > WithAppendixF(this Self &&self, const FString &name, const TCHAR *text, FormatArgs &&... fmtArgs)
Add an extra plain text block inside inner errors.
Definition Error.h:446
FStringView GetSeverityString() const
Get the error severity as an unreal string.
SelfRef< Self > WithDetails(this Self &&self, const FString &input, bool condition=true)
Specify details for the error which may provide further context for the user or provide them reminder...
Definition Error.h:284
SelfRef< Self > Notify(this Self &&self, Observable::IState< IErrorPtr > &state)
Notify an observable state about this error.
Definition Error.h:471
SelfRef< Self > WithError(this Self &&self, const TSharedRef< Error > &input, bool condition=true)
Add a uniquely typed inner error.
Definition Error.h:352
std::string ToStringUtf8() const
Render this error as a std::string using the YAML representation.
FString Details
Definition Error.h:74
SelfRef< Self > WithMessageF(this Self &&self, const TCHAR *input, FormatArgs &&... fmtArgs)
Specify formatted error message with a fluent API.
Definition Error.h:211
SelfRef< Self > WithSeverity(this Self &&self, EErrorSeverity input)
Specify severity with a fluent API.
Definition Error.h:242
virtual void SerializeErrorPropagation(YAML::Emitter &emitter) const
Override this method if error propagation history needs custom way of serialization.
FString GetErrorPropagationJoined() const
Same as GetErrorPropagation but items are separated by new line.
FORCEINLINE decltype(InnerErrors) ::TRangedForConstIterator end() const
Definition Error.h:107
void AddCppStackTrace(const FString &name, int32 numAdditionalStackFramesToIgnore, bool fastWalk)
FORCEINLINE FString const & GetCodeContext() const
Definition Error.h:166
virtual void AddAppendix(const FString &name, const FString &text, const FString &type=TEXT_"Appendix")
Add extra separate blocks of text in an ad-hoc fashion.
FORCEINLINE decltype(InnerErrors) ::TRangedForConstIterator begin() const
Definition Error.h:105
SelfRef< Self > AsOperandWith(this Self &&self, Function &&function)
Call an arbitrary function with this error. Other than cases like invoking macros inline operating on...
Definition Error.h:559
SelfRef< Self > WithErrors(this Self &&self, bool condition, const TSharedRef< Errors > &... errors)
Add multiple errors at once.
Definition Error.h:416
SelfRef< Self > AsFatal(this Self &&self)
Fatal shorthand.
Definition Error.h:258
virtual void SerializeMembers(YAML::Emitter &emitter) const
Override this method if direct members should be serialized differently or extra members are added by...
FString CodeContext
Definition Error.h:75
SelfRef< Self > Report(this Self &&self, bool condition=true)
Report this error to the global IError::OnErrorReported event once it is deemed "ready",...
Definition Error.h:536
FORCEINLINE FString const & GetMessage() const
Definition Error.h:164
TArray< std::source_location > ErrorPropagation
Definition Error.h:71
virtual void SerializeInnerErrors(YAML::Emitter &emitter) const
Override this method if inner errors needs custom way of serialization.
static auto OnErrorReported() -> TEventDelegate< void(IErrorRef)> &
When Report is called on an error this event is triggered, allowing issue tracking systems to react o...
SelfRef< Self > AsRecoverable(this Self &&self)
Recoverable shorthand.
Definition Error.h:250
SelfRef< Self > WithBlueprintStackTrace(this Self &&self, const FString &name={}, bool condition=true)
Shorthand for adding the current Blueprint stacktrace to this error.
Definition Error.h:498
SelfRef< Self > WithAppendixFC(this Self &&self, bool condition, const FString &name, const TCHAR *text, FormatArgs &&... fmtArgs)
Add an extra plain text block inside inner errors.
Definition Error.h:462
SelfRef< Self > WithDetailsF(this Self &&self, const TCHAR *input, FormatArgs &&... fmtArgs)
Specify formatted details for the error which may provide further context for the user or provide the...
Definition Error.h:302
SelfRef< Self > BreakDebugger(this Self &&self)
Break if a debugger is attached when this error is created.
Definition Error.h:479
FORCEINLINE FString const & GetDetails() const
Definition Error.h:165
FORCEINLINE decltype(InnerErrors) ::TRangedForIterator begin()
Definition Error.h:104
FORCEINLINE TMap< FString, IErrorRef > const & GetInnerErrors() const
Definition Error.h:167
FORCEINLINE decltype(InnerErrors) ::TRangedForIterator end()
Definition Error.h:106
SelfRef< Self > WithLocation(this Self &&self, std::source_location const &location=std::source_location::current())
Allow the error to record the source locations it has been handled at compile time....
Definition Error.h:518
SelfRef< Self > WithCppStackTrace(this Self &&self, const FString &name={}, bool condition=true, int32 numAdditionalStackFramesToIgnore=0, bool fastWalk=!UE_BUILD_DEBUG)
Shorthand for adding the current C++ stacktrace to this error.
Definition Error.h:487
SelfRef< Self > WithAppendix(this Self &&self, const FString &name, const FString &text, bool condition=true)
Add an extra plain text block inside inner errors.
Definition Error.h:431
SelfRef< Self > WithErrors(this Self &&self, const TArray< TTuple< FString, IErrorRef > > &input, bool condition=true)
Add multiple errors at once with optional names.
Definition Error.h:383
FString Message
Definition Error.h:73
static TSharedRef< T > Make(T *newError, Args &&... args)
To ensure automatic type reflection use IError::Make instead of manually constructing error objects.
Definition Error.h:148
virtual void AddError(const FString &name, const TSharedRef< IError > &error, const FString &typeOverride={})
Override this method if inner errors added to current one needs special attention.
TMap< FString, IErrorRef > InnerErrors
Definition Error.h:70
SelfRef< Self > WithDetailsFC(this Self &&self, bool condition, const TCHAR *input, FormatArgs &&... fmtArgs)
Specify formatted details for the error which may provide further context for the user or provide the...
Definition Error.h:321
SelfRef< Self > AsCrashing(this Self &&self)
Crashing shorthand.
Definition Error.h:266
FORCEINLINE int32 GetSeverityInt() const
Definition Error.h:163
FORCEINLINE EErrorSeverity GetSeverity() const
Definition Error.h:162
void AddBlueprintStackTrace(const FString &name)
TArray< FString > GetErrorPropagation() const
Get a list of source locations where this error has been handled. This is not equivalent of stack-tra...
virtual void NotifyState(Observable::IState< IErrorPtr > &state)
Shorthand for combination of IHaveType and TSharedFromThis
Definition Types.h:153
TSharedRef< std::decay_t< Self > > SelfRef
Definition Types.h:68
Concept constraining input type argument T to an IError.
Definition Error.Fwd.h:33
Contains utilities for structured error handling.
Definition Error.Fwd.h:19
TSharedRef< IError > IErrorRef
Convenience alias for an instance of an error.
Definition Error.Fwd.h:25
FORCEINLINE FCanFail Success()
Return an FCanFail or FTrueOrReason indicating a success or truthy output.
Definition Error.h:685
TSharedPtr< IError > IErrorPtr
Convenience alias for an instance of an error.
Definition Error.Fwd.h:26
EErrorSeverity
Indicate the severity of an error and at what discretion the caller may treat it.
Definition Error.Fwd.h:53
Utilities for TSharedPtr/Ref and related.
TSharedRef< T, Mode > MakeShareableInit(T *newObject, Args &&... args)
A wrapper around MakeShareable that automatically calls an initializer method Initialize on the insta...
Mixed text utilities and type traits.
Definition Enums.h:51
FStringFormatOrderedArguments OrderedArguments(Args &&... args)
Create an ordered argument list for a string format from input arguments.
Definition Text.h:432
constexpr FStringView TTypeName
Get a friendly string of an input type without using typeid(T).name().
Definition TypeName.h:161
C++ native static reflection utilities, not to be confused with reflection of UObjects.
Definition Types.h:24
This struct may be used for situations where something needs to be returned but it's not meaningful t...
Definition Void.h:26
A TValueOrError alternative for IError which allows implicit conversion from values and errors (no ne...
Definition Error.h:590
TMaybe(From &&value)
Enable move constructor for T only when T is move constructable.
Definition Error.h:617
TMaybe(TMaybe< From > const &other)
Enable copy constructor for TMaybe only when T is copy constructable.
Definition Error.h:621
TMaybe()
Default initializing a TMaybe while its value is not default initializable, initializes the resulting...
Definition Error.h:600
auto TryGetValue() const -> TOptional< T > const &
Definition Error.h:635
TMaybe(TSharedRef< ErrorType > const &error)
Set this TMaybe to an erroneous state.
Definition Error.h:629
auto GetErrorRef() const -> IErrorRef
Definition Error.h:643
bool HasValue() const
Definition Error.h:631
auto GetValue() -> T &
Definition Error.h:637
TMaybe()
If T is default initializable then the default state of TMaybe will be the default value of T,...
Definition Error.h:609
TMaybe(TMaybe< From > &&other)
Enable move constructor for TMaybe only when T is move constructable.
Definition Error.h:625
T && StealValue() &&
Definition Error.h:640
Self && ModifyError(this Self &&self, Function &&mod)
Modify a potential error stored in this monad.
Definition Error.h:656
auto GetError() const -> IErrorPtr
Definition Error.h:642
bool HasError() const
Definition Error.h:632
TMaybe(From const &value)
Enable copy constructor for T only when T is copy constructable.
Definition Error.h:613
auto TryGetValue() -> TOptional< T > &
Definition Error.h:634
auto GetValue() const -> T const &
Definition Error.h:638
Public API and base class for TState which shouldn't concern with policy flags or thread safety.
Definition Observable.h:58