Using error codes effectively

In the previous posts we have seen what error codes and error conditions are. But the way we used them is far from optimum. In particular, the implementation of FailureSourceCategory::equivalent was huge and error category FailureSourceCategory was forced to be aware of all error codes from all sub-systems in the project. In this post we will see how we can do better, especially in more complex systems.

The problem

Let’s start with restating the problem from the previous posts and making it a bit more complicated.

We have a sub-system for finding flights, call it Flights, which documents the following error codes and their meaning.

Flights — error codes
10 non-existent locations in request
11 requested dates in the past
12 inverted date range in request
20 no flights found
30 protocol violation
31 connection error
32 resource error
33 timeout

This time we are not using an enum, but reflect the way two teams usually communicate: by documents. The only thing we are informed about is numeric values of error codes. They will be returned from subsystems as integer values, probably in XML files.

Similarly, we have a service for finding available seats on a given flight, call it Seats, which claims to be able to return the following error codes in case of failure:

Seats — error codes
1 invalid request
2 could not connect
3 internal error
4 no response
5 non-existent class
6 no seats avaialble

Now, as specified in the previous posts, I want to be able at some point to decide whether the error code returned from any of the sub-systems falls into one of the following categories:

  1. User sent us an illogical request.
  2. There is some problem with the system which the user will not understand but which prevents us from returning the requested answer.
  3. No airline we are aware of is able to offer a requested trip.

This is the same problem as in previous posts. Now, however, we will be also logging different conditions, and at some point we want to determine the severity of an error. It will be one of:

  1. A bug.
  2. An incorrect configuration.
  3. A (perhaps temporary) shortage of resources.
  4. Normal (like “no solutions found”) — user may be disappointed, but developers or the system did nothing wrong.

Thus we will be performing two different queries. Additiobally, to make the matter more complicated, sometimes we will not be using sub-system Flights, but instead we will be calling a competitive sub-system, called Flights-2, which does practically the same thing (but faster), but uses different values for errors:

Flights-2 — error codes
11 invalid request format
12 could not connect
13 database error
14 not enough memory
15 internal error
16 no such airport
17 journey in the past
18 dates not monotonic

Now you get the idea of the complexity of the problem. Obviously, the problem can grow in two directions:

  1. More sub-systems can be replaced, each sub-system providing a different set error code values.
  2. More queries on error codes may be needed.

The solution

In order to address this, we will introduce a yet another, artificial set of error conditions that will enumerate anything that can go wrong in any of the sub-systems: it will be a super-set of the error codes from any of the sub-systems. Error codes like 30 from Fights or 1 from Seats or 11 from Flights-2 will all be mapped onto one normalized value “protocol error”. Such “normalized” condition looses some information (like original numeric value, or from which system it originated) but retains sufficient granularity for high level queries (these for severity, or error source, or any future queries) to give correct answers.

Once we have this “normalized” error values, we will be able to provide:

  1. Mappings from sub-system error codes onto the normalized error conditions.
  2. Mappings from the normalized error conditions onto the high-level error queries.

This way, there is a one “centralized” source of information, but high-level queries need not be concerned with new sub-systems, and similarly, sub-systems need not be bothered with new high-level queries.

Let’s implement this then. First, we encode the list of numeric error values from sub-system Flights into a C++ enumeration, as we did in the previous post:

enum class FlightsErrc
{
  NonexistentLocations = 10,
  DatesInThePast       = 11,
  InvertedDates        = 12,
  NoFlightsFound       = 20,
  ProtocolViolation    = 30,
  ConnectionError      = 31,
  ResourceError        = 32,
  Timeout              = 33,
};

We have encoded the informal table into a piece of source code. We preserve the same numeric values, the description of what they encode, and we encode in the type that these numeric values describe error codes from sub-system Flights. This is important because sub-system Flights-2 uses same numeric values for representing different situations. If we also encode error codes from Flights-2:

enum class Flights2Errc
{
  ZeroFlightsFound     = 11,
  InvalidRequestFormat = 12,
  CouldNotConnect      = 13,
  DatabaseError        = 14,
  NotEnoughMemory      = 15,
  InternalError        = 16,
  NoSuchAirport        = 17,
  JourneyInThePast     = 18,
  DatesNotMonotonic    = 19,
};

While the error conditions are similar in both services they are assigned different numeric values. For instance number 12 indicates error in XML format in Fligts-2, but silly dates provided by the user in Flights. But we will never confuse them because they are encoded in different types.

We will need to do the same for service Seats. Next, we need similar enums for the error queries:

enum class FailureSource
{
  // no 0
  BadUserInput = 1,
  SystemError,
  NoSolution,
};

enum class Severity
{
  // no 0
  Bug = 1,
  Config,
  Resource,
  Normal,
};

Here the numeric values are not that important, so long as they are different within the enum, so we only make sure they are not 0. Finally, we provide an enumeration for our “normalized” list of error conditions:

enum class SubsystemError
{
  // no 0
  InputBadAirport = 1,
  InputPastDate,
  InputBadDateRange,
  InputBadClass,
  NoFlightFound,
  NoSeatFound,
  SubsysProtocolErr,
  SubsysInternal,
  SubsysResource,
  SubsysConfig,
  SubsysTimeout,
};

Now, we will plug SubsystemError into the error system as an error condition: it will not be used for storing numeric values of codes from sub-systems, but only for performing (fine-grained) queries. We already know how to do that: specialize trait std::is_error_condition_enum, provide factory function overload make_error_condition. I will not show it here. The interesting part is error category corresponding to SubsystemError. This is how we define it:

namespace /* anonymous */ {

class SubsystemErrorCategory : public std::error_category
{
public:
  const char* name() const noexcept override;
  std::string message(int ev) const override;
};

}

You already know this: name assigns short mnemonic name to our category, message assigns short text description to each enum value. What is interesting is that we override no other member function: we define no relation to any sub-system error or any high-level query. This category is not aware of either! It is the other categories — those for defining semantics of sub-system error codes and those for high-level query codes — that will be later defining the numeric value relations to this category.

So, let’s see how you tie an error code to this super-category. Let’s start with enum FlightsErrc. We will plug it as error code because its numeric values represent real error values from a sub-system. We want this numeric value to be transported, and preserved. Again, we already know how to do that from the previous posts: specialize trait std::is_error_code_enum, provide factory function overload make_error_code. But unlike in previous posts, we will define the corresponding error category somewhat different.

We have already indicated the difference between error codes and error conditions:

  • error_code is used for storing and transmitting error codes as they were produced by originating library, unchanged;
  • error_condition is used for performing queries on error_codes, for the purpose of grouping or classification or translation.

In this scheme, an error_category is used for defining the meaning of each value of an error code: how it is equivalent to different values of error categories. Let’s go:

namespace /* anonymous */ {
struct FlightsErrCategory : std::error_category
{
  const char* name() const noexcept override;
  std::string message(int ev) const override;
  std::error_condition default_error_condition(int ev)
    const noexcept override;
};

}

Observe: no function equivalent as in the previous posts. Instead we override a different member function: default_error_condition. You override it to indicate your preferred, default, error condition, that best describes semantics of the error code values from your enum in the context of your program/library. In our case, we already said we will be mapping error code values from any sub-system onto the normalized error condition, represented by enum SubsystemError and by category SubsystemErrorCategory.

Let’s define the function to better understand what it does:

std::error_condition 
FlightsErrCategory::default_error_condition(int ev)
  const noexcept
{
  switch (static_cast<FlightsErrc>(ev))
  {
  case FlightsErrc::NonexistentLocations:
    return SubsystemError::InputBadAirport;
  
  case FlightsErrc::DatesInThePast:
    return SubsystemError::InputPastDate;
      
  case FlightsErrc::InvertedDates:
    return SubsystemError::InputBadDateRange;
        
  case FlightsErrc::NoFlightsFound:
    return SubsystemError::NoFlightFound;
      
  case FlightsErrc::ProtocolViolation:
    return SubsystemError::SubsysProtocolErr;
      
  case FlightsErrc::ConnectionError:
    return SubsystemError::SubsysConfig;
       
  case FlightsErrc::ResourceError:
    return SubsystemError::SubsysResource;
      
  case FlightsErrc::Timeout:
    return SubsystemError::SubsysTimeout;
    
  default:
    assert (false);
    return {};
  }
}

The first case label should be read as follows, “if the error code currently inspected stores numeric value FlightsErrc::NonexistentLocations, consider it equivalent to error condition SubsystemError::InputBadAirport. At this point FlightsErrc::NonexistentLocations represents a numeric value, but SubsystemError::InputBadAirport is treated as (is converted to) an std::error_condition.

And we define a similar mapping for every numeric value from enum FlightsErrc. (If you are unhappy about the assert at the end, you can skip it, or throw an exception: I do not want to focus on such edge conditions in this post.)

With this in place, we can compare errors from Flights sub-system against our “default” error condition:

std::error_code ec = FlightsErrc::DatesInThePast;
assert (ec == SubsystemError::InputPastDate);
assert (ec != SubsystemError::InputBadDateRange);

In the above code snippet we are comparing an error code against an error condition. This error condition is so fine grained that it might appear as though we were comparing two error codes, but it is not the case. We are still preserving the distinction we presented earlier: error codes are for storing and transporting values; error conditions are for performing queries, including micro-queries. What we buy with this is separation. As the next step, we will need to define a similar mapping for enum Flights2Errc, and maybe in the future we will add a yet another set of codes Flights3Errc, but the other parts of the program, that only check for conditions using micro-queries like SubsystemError::InputPastDate will be unaffected.

Just to explain what happens in the above code. The comparison between an error code and an error condition is defined as:

bool operator==(const error_code&      lhs,
                const error_condition& rhs) noexcept
{
  return lhs.category().equivalent(lhs.value(), rhs)
      || rhs.category().equivalent(lhs, rhs.value());
}

So, this triggers a call to a virtual member function equivalent on the category in error code, which is of our type FlightsErrCategory. Because we did not override this function, the default implementation from the base class is used:

bool equivalent(int code,
                const error_condition& cond) const noexcept
{
  return default_error_condition(code) == cond;
}

So, down below we are comparing one condition against another. Comparing conditions works the same as comparing two codes: both int value and the category must match.

In a similar manner we have to define mapping for Flights2Errc and SeatsErrc, but we will not show it in this post. What we will show is how you map from a low-level query to a high-level query. We will see how to do it for query Severity. Again, we have to specialize the trait std::is_error_condition_enum and provide factory function make_error_condition this is uninteresting. What is interesting is our error category type:

namespace {
class SeverityCategory : public std::error_category
{
public:
  const char* name() const noexcept override;
  std::string message(int ev) const override;

  bool equivalent(
    const std::error_code& code,
    int condition) const noexcept override;
};
}

This time we are overriding function equivalent. The mapping in the function, from an error code to our category, is expressed by performing low-level queries. This looks like this:

bool SeverityCategory::equivalent(
      const std::error_code& ec,
      int cond) const noexcept
{     
  switch (static_cast<Severity>(cond))
  {
  case Severity::Bug:
    return ec == SubsystemError::SubsysProtocolErr
        || ec == SubsystemError::SubsysInternal;
        
  case Severity::Config:
    return ec == SubsystemError::SubsysConfig;

  case Severity::Resource:
    return ec == SubsystemError::SubsysResource        
        || ec == SubsystemError::SubsysTimeout;
        
  case Severity::Normal:
    return ec == SubsystemError::InputBadAirport
        || ec == SubsystemError::InputPastDate
        || ec == SubsystemError::InputBadDateRange
        || ec == SubsystemError::InputBadClass
        || ec == SubsystemError::NoFlightFound
        || ec == SubsystemError::NoSeatFound;
             
  default:
    return false;
  }
}

The first case label reads, “high-level condition Severity::Bug is a union of low-level conditions SubsystemError::SubsysProtocolErr and SubsystemError::SubsysInternal”. Thus, we are building high-level queries from low-level queries (error conditions) but not from error code values directly.

Similarly, we can define a mapping for FailureSource. Full compiling code illustrating all of the above can be found here.

One final thing. Sub-systems, like Flights, will be likely sending us error codes as raw ints. In order to turn a raw int into an error_code we will have to convert it to a value of a corresponding enum FlightsErrc. One way is to simply static_cast it:

int ret_value = 20; // it was returned from Flights
std::error_code ec2 = static_cast<FlightsErrc>(ret_value);

Another would be to provide a converting function (probably with a big switch-statement) that would handle the situation when the numeric value is for some reason different from those that sub-system Flights claims to return.

In closing

The above design is analogous to what has been done for the standard library. Enum std::errc corresponds to our enum SubsystemError: it is registered as error condition. It is a low-level query for determining failure condition in a platform-agnostic way. Its corresponding error category can be obtained by calling function std::generic_category(). Numeric values of error codes, on the other hand, depend on the platrorm, and may correspond to POSIX error codes on Unix systems and to HRESULT values on Windows. This is analogous to our enums FlightsErrc and Flights2Errc. Their corresponding to error category can be obtained by calling function std::system_category(). The platform-specific values are retained for debugging purposes, but in the program we are using the platform-agnostic low-lever queries from std::errc.

I am very grateful to Tomasz Kamiński for explaining to me the details of error code handling utility.

This entry was posted in programming and tagged , . Bookmark the permalink.

31 Responses to Using error codes effectively

  1. xABBAAA says:

    And we define a similar mapping for every numeric value from enum FlightsErrc. (If you are unhappy about the assert at the end, you can skip it, or throw an exception: I do not want to focus on such edge conditions in this post.)

    assert is usually good in the early phases of testing, ant it is more useful for C. However, that will be useful thing to know. For development phase will do it.
    It looks nice and very good article.
    I just don’t know, could you add some more stuff with specific errors that are standard and then mention the operating system specific.

    THX

  2. LikeRainToFire says:

    So I can plug-in an error_condition above an error_code by overriding
    · virtual bool equivalent(const error_code& code, int condition) const noexcept,
    and I can plug-in an error_code below an error_condition by overriding
    · virtual bool equivalent(int code, const error_condition& condition) const noexcept, or
    · virtual error_condition default_error_condition(int ev) const noexcept.

    Then I have some questions:
    · What is the rationale behind having both default_error_condition and equivalent virtuals available?
    · Explicitly, if the single description of default_error_condition purpose (found in [syserr.errcat.objects]) is to translate system values to POSIX values, why should I override it for a different purpose?
    · And the interesing one, how should I plug-in another layer of granularity above an error_condition or below an error_code, i.e. how should I achieve composability on ‘s planar design?

    • First, two general remarks. (1) While the error_code feature was added to handle POSIX/MFC/Other error codes uniformly, it has been designed as a general purpose tool, which can be used for other things, as described in this blog posts. (2) One practically cannot understand the idea behind the error_code feature only from reading the formal specification. Really, unless you have heard from the original designers, or someone who has invested quite a lot of time in research and asking other people who already know it, it is very difficult to grasp the idea behind it.

      Now to your questions.

      Re 1: I suppose you may need either depending on how you want to plug your error code and how error codes were being categorized so far. Int this post we said we are starting with one “central” condition and are building around it. So the natural choice is to use default_error_condition. But one can imagine a system where there is more than one “central” condition, and you want your error code to be recognized by both. Then you will have to use equivalent at least for one: for instance, if I wanted error code FlightsErrc::DatesInThePast to be recognized by condition std::errc.

      Re 2. Subclause [syserr.errcat.objects] defines only generic_category() and system_category(), and they indeed use for storing and translating operating system error codes. In contrast, default_error_condition is used for defining “central” error conditions, as shown in this blog post, and you decide what is “central” in your program: it might be the operating system error condition or it might be something else.

      Re 3. You can have more than one level of error conditions: Condition C1 defines 81 basic states; condition C2 defines 9 states, more coarse grained, by speaking only in terms of C1; condition C3 defines 3 states by speaking only in terms of C2. Does that make sense?

      • LikeRainToFire says:

        I’ll simplify Q3 so u get the point: u are given two error_condition sets (not error_code sets) from two different libraries, and u are told to unify them. U should not rely on the implementation details of the error_codes such libraries emit. How would u do it taking into account error_condition to error_condition comparisons are not overloadable? Peek
        http://open-std.org/JTC1/SC22/WG21/docs/lwg-closed.html#971
        for a hint.

        “One practically cannot understand the idea behind the error_code feature only from reading the formal specification” This is exactly why Im trying to place shining light above the weak spots of system_error. Mind this blog post series is becoming reference, just trying to feed it with useful thoughts.

        BTW actually working on a proposal to address:
        · N2422 Issue 5: Class error_code constructors should be constexpr
        · LWG DR 832. Applying constexpr to System error support
        · LWG DR 2992. system_category() and error_code::error_code() should be constexpr

        • Ok, so here is how I understand your question. You have one condition that discriminates two states:

          enum class P {
            P1 = 1,
            P2,
            P3,
          };
          

          You have another condition that discriminates three other states:

          enum class R {
            R1 = 1,
            R2,
            R3,
          };
          

          You probably assume that P1, P2, P3, R1, R2 and R3 are disjoint, right?

          What you want is a new condition with six states corresponding to P1, P2, P3, R1, R2 and R3. Right?

          If so, then what I think you need to do is to provide a new error_condition enum:

          enum class X {
            P1 = 1,
            P2,
            P3,
            R1,
            R2,
            R3
          };
          

          Add a corresponding error_category with the following implementation of function equivalent:

          bool X_category::equivalent(
                const std::error_code& ec,
                int cond) const noexcept
          {     
            switch (static_cast<X>(cond))
            {
            case X::P1: return ec == P::P1;
            case X::P2: return ec == P::P2;
            case X::P3: return ec == P::P3;
            case X::R1: return ec == R::R1;
            case X::R2: return ec == R::R2;
            case X::R3: return ec == R::R3;      
            default:    return false;
            }
          }
          

          Did I get your question correctly?

        • LikeRainToFire says:

          Now we are on track. Next u are told to develop another library Q and adapt the error codes it emits to that X error_condition enum, but u no longer have control of the implementation of X. How would u do it?

          While on it I have another bunch of questions:
          · Should we have comparisons involving both zero but on different domains return true or false? I.E. error_code(0, foo_category()) == error_condition(0, bar_category()) ?
          · How should I implement a many to one relationship performance-wise?
          · How should I implement a many to many relationship performance-wise?
          · Is a many to many relationship actually good practice or a design flaw? I.E. if I split an error domain into conditions should they ever overlap? Mind “virtual error_condition default_error_condition(int ev) const noexcept” just can’t support many to many relationships but “virtual bool equivalent(int code, const error_condition& condition) const noexcept” can.

          Not enemy but ally, bringing ideas for a 4th chapter in the series.

        • “Next u are told to develop another library Q and adapt the error codes it emits to that X error_condition enum, but u no longer have control of the implementation of X.”
          “Adapt” in the sense “map onto the six existing states”, or “add three additional states to the present six ones”?

        • LikeRainToFire says:

          I’m not specifying map interrelations, but since u have no control over X implementation u can’t add new states to it. The problem naturally involves a many to one map. As a bonus u could take into account error codes with no translation.

        • Maybe the following is the answer; if I got your question correctly:

          enum class Q {
            Qa = 1, Qb, Qc, Qd,
          };
          
          // plug as error_code
          
          std::error_condition 
          Q_category::default_error_condition(int ev)
            const noexcept
          {
            switch (static_cast<Q>(ev))
            {
            case Q::Qa: return X::R1;
            case Q::Qb: return X::P1;
            case Q::Qc: return X::P2; 
            case Q::Qd: return X::P3;  
            default:  assert (false); return {};
            }
          }
          
        • LikeRainToFire says:

          Other than polishing corner cases we’re good. Still seems simple, right? Now lets add std::errc support for X. Its up to u how to map across domains but already established maps must not be forgotten.

        • Did I get it right? You want the user of a third party library (the one that has control overX) and of a Standard Library to say how the former should understand the latter?

        • LikeRainToFire says:

          Yes X implementation has to compare against generic conditions in adition to already implemented P and R ones. Of course u have full control of X now, but not of generic_category(). As a bonus u could think what if u have control of none, and u would need a third translator domain.

        • I see what you are saying. I do not think the library has been designed to solve this kind of problems. You either adapt your error codes to somebody’s conditions or your conditions to somebody’s error codes, but I do not thin you can adopt somebody’s codes to somebody else’s conditions.

        • LikeRainToFire says:

          But X is yours by now, so u still have no trouble. If u had, u could use some extra translation helper along the lines of “std::error_code translate_error(const std::error_code& code)” transforming or forwarding by filters, nothing fancy.

        • LikeRainToFire says:

          BTW, if u really need to adopt somebody’s codes to somebody else’s conditions, then what u need is to adopt somebody’s codes and somebody else’s conditions to your own domain. Thats why both make_error_code and make_error_condition should be provided, u can make an error_condition out of an error_code and viceversa obeying a one to one map within the same domain. A domain is made of a category and error VALUES, else is just a design artifact. Therefore one must be aware even if your domain is described by an error_condition_enum, category could receive values of its same domain as codes.

          The fun is about to begin!

  3. LikeRainToFire says:

    Btw it seems reply box discards false tags while sanitizing input, “…composability on #system_error#‘s planar design?” had less_than and more_than symbols to reflect I was talking about the header.

  4. Sandeep says:

    Hi, thanks for the series of posts.
    1. If I have understood the hierarchy correctly, I think there is an error in the full code which you shared:

    std::string SubsystemErrorCategory::message(int ev) const
    {
      switch (static_cast<FailureSource>(ev))
      {
          default:
              return "(uncharted)";
      }
    }
    

    I think the `static_cast` should be to SubSystemErr instead of FailureSource.

  5. Sandeep says:

    One more question (I haven’t read the linked post yet from the boost asio author, so I apologise if the answer is already there).
    Consider the following code:

        // We queried the underlying systems, let's say it returned an error NonexistentLocations
        std::error_code ec = FlightsErrc::NonexistentLocations;
        
        // and we returned this ec to the user, and user can do the following
        assert(ec); // to verify that the error has occurred
        assert(ec == FlightsErrc::NonexistentLocations);  // to verify the underlying error - Should they?
        assert(ec == SubsystemError::InputBadAirport);   // to verify the normalized condition
        
        // print the messae
        std::cout << ec.message() << std::endl;
    

    My question is related to the “Should they?”. Should users be able to check against the error code of the underlying systems? If they are, then the library author is not fully free to change the underlying systems from Flights1 to Flights2.

    • I agree with what you seem to be suggesting: comparing to error code enums (or error codes) directly makes it easy to break things.

      But sometimes, it might be exactly what you want or need. In some sense an error code is the most fine grained condition.

      • LikeRainToFire says:

        Actually enum value is converted to error_code or error_condition before comparison happens, if explicitly enabled via is_error_code_enum or is_error_condition_enum, or else it just fails to compile. So yes, error_code to error_code_enum comparison is a valid one to one map, if library states it is. The point instead is to NEVER compare against ec.value(), which is the real portability breaker.

  6. adrian94a says:

    Hello,
    I have read your blog and you are doing a good job but i have a little question.
    Why we can’t use “name” function if we overload this method? Here is an example made by me (same as yours) here : https://pastebin.com/eGMeYhet .
    I can’t use in main method “name” because “name is not a member of std::error_code” but if we take a look in std::error_code there is a method “name” witch i override in my structure.
    I hope you understand what i am trying to say and thank you for your good work.

    • Your link does not work for me, so I am not sure I am answering your question. But error_code indeed does not have member function name. It is error_category that has it:

      #include <system_error>
      #include <iostream>
      
      int main()
      {
        std::error_code ec {};
        std::cout << ec.category().name() << std::endl;
      }
      
  7. dbjsystems says:

    What troubles me I can’t find any referent projects using this in an “optimal” way? there are several years since this is introduced and now we have a completely new concept `std::expected`, what should one do? Ignore the both?

  8. First, it looks like std::error_code is not fully baked. For instance, it is not clear if it is meant to represent only error conditions or if it is also meant to represent other statuses that are not errors, like partial success, partial result, success with warnings, etc.. This is confirmed by a number of proposals to fix it:

    If it was clear that std::error_code is used only to describe different error conditions, the guidance about std::error_code vs std::expected would be clear: std::expected<T, E> is meant to be returned from functions: it represents either a successfully computed T or a reason why the computation failed. This reason is encoded in E. Now std::error_code, you could say, represent only the reason for failure, but not the function result, so you either use it as E in std::expected<T, E> or put it inside an exception.

    In other words, std::expected is the means of propagating failures from functions (an alternative to exception handling), whereas std::error_code is a “number” that describes what went wrong, but is only a number: you still need to decide how you want to propagate this number: via exception, or returning by value, or returning in std::expected.

  9. Ted says:

    Hi Andrzej,
    Thanks a lot for your post.

    Can I ask how to handle the situation where, at the SubSystem level, the error code is not simply an aggregation from its modules, rather the sub system will introduce its own error code as well.

    I have not been able to extend your example to achieve this. An example will look like this:

    enum class SubsystemError
    {
      // no 0
      InputBadAirport = 1,
      InputPastDate,
      InputBadDateRange,
      InputBadClass,
      NoFlightFound,
      NoSeatFound,
      SubsysProtocolErr,
      SubsysInternal,
      SubsysResource,
      SubsysConfig,
      SubsysTimeout,
      SubsysNewError,
    };
    

    The sub system layer introduced a new error code called SubsysNewError in this example.

    Thanks a lot, Ted

    • Hi Ted. I am not sure if I understand the situation correctly. I believe that what you are saying is that apart from N sub-systems (Flights, Flights-2, Seats), we also have an “orchestrator” application where al these error mappings are performed. And this orchestrator can fail for its own reasons, and these reasons need to be encoded and reflected in SubsystemError.

      Is this the problem you want to solve?

  10. xABBAAA says:

    … yeah, I don’t get newsletter from you any more, and one can use try catch and there are already existing ones…
    However, for exercise it is, let’s say okay…

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.