summaryrefslogtreecommitdiff
path: root/asl/types/status.hpp
blob: de95670b31e5a03701cd0aad5f971fab7c7220e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause

#pragma once

#include "asl/base/integers.hpp"
#include "asl/strings/string_view.hpp"
#include "asl/formatting/format.hpp"

namespace asl
{

class Formatter;

enum class status_code : uint8_t
{
    ok               = 0,
    unknown          = 1,
    internal         = 2,
    runtime          = 3,
    invalid_argument = 4,
};

struct StatusInternal;

class status
{
    StatusInternal* m_payload{};

    static constexpr StatusInternal* status_to_payload(status_code code)
    {
        return code == status_code::ok
            ? nullptr
            : bit_cast<StatusInternal*>((static_cast<uintptr_t>(code) << 1) | 1);
    }

    static constexpr status_code payload_to_status(void* payload)
    {
        return static_cast<status_code>(bit_cast<uintptr_t>(payload) >> 1);
    }

    [[nodiscard]] constexpr bool is_inline() const
    {
        return m_payload == nullptr || (bit_cast<uintptr_t>(m_payload) & 1) != 0;
    }

    [[nodiscard]] constexpr status_code code_inline() const
    {
        ASL_ASSERT(is_inline());
        if (m_payload == nullptr)
        {
            return status_code::ok;
        }
        return payload_to_status(m_payload);
    }

    [[nodiscard]] status_code code_internal() const;
    [[nodiscard]] string_view message_internal() const;

    void ref();
    void unref();

public:
    constexpr ~status()
    {
        if (!is_inline()) { unref(); }
    }

    explicit constexpr status(status_code code)
        : m_payload{status_to_payload(code)}
    {}

    status(status_code code, string_view msg);
    status(status_code code, string_view fmt, span<format_internals::type_erased_arg> args);

    constexpr status(const status& other)
        : m_payload{other.m_payload}
    {
        if (!is_inline()) { ref(); }
    }

    constexpr status(status&& other)
        : m_payload{exchange(other.m_payload, status_to_payload(other.code()))}
    {}

    constexpr status& operator=(const status& other)
    {
        if (&other != this)
        {
            if (!is_inline()) { unref(); }
            m_payload = other.m_payload;
            if (!is_inline()) { ref(); }
        }
        return *this;
    }

    constexpr status& operator=(status&& other)
    {
        if (&other != this)
        {
            if (!is_inline()) { unref(); }
            m_payload = exchange(other.m_payload, status_to_payload(other.code()));
        }
        return *this;
    }

    [[nodiscard]] constexpr bool ok() const
    {
        return m_payload == nullptr;
    }

    [[nodiscard]] constexpr status_code code() const
    {
        return is_inline() ? code_inline() : code_internal();
    }

    [[nodiscard]] constexpr string_view message() const
    {
        if (!is_inline())
        {
            return message_internal();
        }
        return {};
    }

    constexpr status&& throw_status() && { return std::move(*this); }

    friend void AslFormat(Formatter& f, const status&);

    template<typename H>
    friend H AslHashValue(H h, const status& s)
    {
        if (s.is_inline())
        {
            return H::combine(std::move(h), s.code());
        }
        return H::combine(std::move(h), s.code(), s.message());
    }
};

static constexpr status ok() { return status{status_code::ok}; }

#define ASL_DEFINE_ERROR_(type) \
    static constexpr status type##_error() { return status{status_code::type}; }                \
    static inline status type##_error(string_view sv) { return status{status_code::type, sv}; } \
    template<formattable... Args>                                                               \
    [[maybe_unused]] static status type##_error(string_view fmt, const Args&... args)           \
    {                                                                                           \
        format_internals::type_erased_arg type_erased_args[] = {                                \
            format_internals::type_erased_arg(args)...                                          \
        };                                                                                      \
        return status{status_code::type, fmt, type_erased_args};                                \
    }

ASL_DEFINE_ERROR_(unknown)
ASL_DEFINE_ERROR_(internal)
ASL_DEFINE_ERROR_(runtime)
ASL_DEFINE_ERROR_(invalid_argument)

#define ASL_TRY(VALUE) if ((VALUE).ok()) {} else { return std::move(VALUE).throw_status(); }

} // namespace asl