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
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282

media / base / status.md [blame]

# TypedStatus<T>

The purpose of TypedStatus is to provide a thin wrapper around return-value
enums that support causality tracking, data attachment, and general assistance
with debugging, without adding slowdowns due to returning large structs,
pointers, or more complicated types.

A use-every-feature example:
```c++
struct MyExampleStatusTraits {
  // [REQUIRED] Declare your enum
  enum class Codes : StatusCodeType {
    kSomething = 9090,
    kAnotherThing = 92,
    kAThirdThing = 458,
    kAFinalThing = 438,
  };

  // [REQUIRED] Declare your group name
  static constexpr StatusGroupType Group() { return "MyExampleStatus"; }

  // [OPTIONAL] Declare your "default" code. If this method is defined,
  // then the function OkStatus() can be used to return a status with this
  // code. Statuses created with this default code can not have any data,
  // causes, or a message attached.
  static constexpr Codes OkEnumValue() { return Codes::kSomething; }

  // [OPTIONAL] If |OnCreateFrom| is declared, then TypedStatus<T> can be
  // created with {T::Codes, SomeOtherType} or {T::Codes, string, SomeOtherType}
  // The pre-created TypedStatus is passed into this method for additional
  // manipulation.
  static void OnCreateFrom(TypedStatus<MyExampleStatusTraits>* impl,
                           const SomeOtherType& t) {
    impl->WithData("key", SomeOtherTypeToString(t));
  }

  // [OPTIONAL] If you'd like to be able to send your status to UKM, declare
  // this method in your traits. This allows you to pack any part of the
  // status internal data into a single ukm-ready uint32.
  static uint32_t PackExtraData(const internal::StatusData& data) {
    return 0;
  }

  // [OPTIONAL] When a status doesn't include a message, the only source of
  // information about the failure reason is the numeric code. This can be
  // somewhat annoying to look up in the correct enum, so creating this method
  // allows a default message with a string representation of the code.
  static constexpr std::string ReadableCodeName(Codes code) {
    switch(code) {
      case Codes::kSomething: return "Something";
      ...
    }
  }
};

// Typically, you'd want to redefine your template instantiation, like this.
using MyExampleStatus = TypedStatus<MyExampleStatusTraits>;

```


## Using an existing `TypedStatus<T>`

All TypedStatus specializations have the following common API:
```c++
// The underlying code value.
T::Codes code() const;

// The underlying message.
std::string& message() const;

// Adds the current file & line number to the trace.
TypedStatus<T>&& AddHere() &&;

// Adds some named data to the status, such as a platform
// specific error value, ie: HRESULT. This data is for human consumption only
// in a developer setting, and can't be extracted from the TypedStatus
// normally. The code value should be sufficiently informative between sender
// and receiver of the TypedStatus.
template<typename D>
TypedStatus<T>&& WithData(const char *key, const D& value) &&;
template<typename D>
void WithData(const char *key, const D& value) &;

// Adds a "causal" status to this one.
// The type `R` will not be retained, and similarly with the data methods,
// `cause` will only be used for human consumption, and cannot be extracted
// under normal circumstances.
template<typename R>
TypedStatus<T>&& AddCause(TypedStatus<R>&& cause) &&;
template<typename R>
void AddCause(TypedStatus<R>&& cause) &;
```


## Quick usage guide

If you have an existing enum, and would like to wrap it:
```c++
enum class MyExampleEnum : StatusCodeType {
  kDefaultValue = 1,
  kThisIsAnExample = 2,
  kDontArgueInTheCommentSection = 3,
};
```

Define an |TypedStatusTraits|, picking a name for the group of codes:
(copying the descriptive comments is not suggested)

```c++
struct MyExampleStatusTraits {
  using Codes = MyExampleEnum;
  static constexpr StatusGroupType Group() { return "MyExampleStatus"; }
  static constexpr Codes OkEnumValue() { return Codes::kDefaultValue; }
}
```

Bind your typename:
```c++
using MyExampleStatus = media::TypedStatus<MyExampleStatusTraits>;
```

Use your new type:
```c++
MyExampleStatus Foo() {
  return MyExampleStatus::Codes::kThisIsAnExample;
}

int main() {
  auto result = Foo();
  switch(result.code()) {
    case MyExampleStatus::Codes::...:
      break;
    ...
  }
}
```

## Constructing a TypedStatus<T>
There are several ways to create a typed status, depending on what data you'd
like to encapsulate:

```
// To create an status with the default OK type, there's a helper function that
// creates any type you want, so long as it actually has a kOk value or
|OkEnumValue| implementation.
TypedStatus<MyType> ok = OkStatus();

// A status can be implicitly created from a code
TypedStatus<MyType> status = MyType::Codes::kMyCode;

// A status can be explicitly created from a code and message, or implicitly
// created from a brace initializer list of code and message
TypedStatus<MyType> status(MyType::Codes::kMyCode, "MyMessage");
TypedStatus<MyType> status = {MyType::Codes::kMyCode, "MyMessage"};

// If |MyType::OnCreateFrom<T>| is implemented, then a status can be created
// from a {code, T} pack, or a {code, message, T} pack:
TypedStatus<MyType> status = {MyType::Codes::kMyCode, 667};
TypedStatus<MyType> status = {MyType::Codes::kMyCode, "MyMessage", 667};

// A status can be created from packs of either {code, TypedStatus<Any>} or
// {code, message, TypedStatus<Any>} where TypedStatus<Any> will become the
// status that causes the return. Note that in this example,
// OtherType::Codes::kOther is itself being implicitly converted from a code
// to a TypedStatus<OtherType>.
TypedStatus<MyType> status = {MyType::Codes::kCode, OtherType::Codes::kOther};
TypedStatus<MyType> status = {MyType::Codes::kCode, "M", OtherType::Codes::kOther};
```



## TypedStatus<T>::Or<D>

For the common case where you'd like to return some constructed thing OR
an error type, we've also created `TypedStatus<T>::Or<D>`.

The `TypedStatus<T>::Or<D>` type can be constructed implicitly with either
a `TypedStatus<T>`, a `T`, or a `D`.

This type has methods:
```c++
bool has_value() const;

// Return the error, if we have one.
// Callers should ensure that this `!has_value()`.
TypedStatus<T> error() &&;

// Return the value, if we have one.
// Callers should ensure that this `has_value()`.
OtherType value() &&;

// It is invalid to call `code()` on an `Or<D>` type when
// has_value() is true and TypedStatusTraits<T>::OkEnumValue is nullopt.
T::Codes code();
```

Example usage:
```c++
MyExampleStatus::Or<std::unique_ptr<VideoDecoder>> CreateAndInitializeDecoder() {
  std::unique_ptr<VideoDecoder> decoder = decoder_factory_->GiveMeYourBestDecoder();
  auto init_status = decoder->Initialize(init_args_);
  // If the decoder initialized successfully, then just return it.
  if (init_status == InitStatusCodes::kOk)
    return std::move(decoder);
  // Otherwise, return a MediaExampleStatus caused by the init status.
  return MyExampleStatus(MyExampleEnum::kDontArgueInTheCommentSection).AddCause(
    std::move(init_status));
}

int main() {
  auto result = CreateAndInitializeDecoder();
  if (result.has_value())
    decoder_loop_->SetDecoder(std::move(result).value());
  else
    logger_->SendError(std::move(result).error());
}

```


## Testing
There are some helper matchers defined in test_helpers.h that can help convert
some of the trickier method expectations. For example, this:
```
EXPECT_CALL(object_, Foo(kExpectedCode));
```
becomes:
```
EXPECT_CALL(object_, Foo(HasStatusCode(kExpectedCode)));
```
The EXPECT_CALL macro won't test for overloaded operator== equality here, so
|HasStatusCode| is a matcher macro that allows checking if the expected status
has the matching error code.


## Additional setup for mojo

If you want to send a specialization of TypedStatus over mojo,
add the following to media_types.mojom:

```
struct MyExampleEnum {
  StatusBase? internal;
};
```

And add the following to media/mojo/mojom/BUILD.gn near the `StatusData` type
binding.

```
{
  mojom = "media.mojom.MyExampleEnum",
  cpp = "::media::MyExampleEnum"
},
```


## UKM & data-recording
TypedStatus is designed to be easily reported to UKM. A status is represented
by 16-bit hash of the group name, the 16-bit code, and 32 bits of extra data.
Any implementation of TypedStatus can define a |PackExtraData| method in the
traits struct which can operate on internal data and pack it into 32 bits.
For example, a TypedStatus which might often have wrapped HRESULTs might look
like this:
```c++
struct MyExampleStatusTraits {
  // If you do not have an existing enum, you can `enum class Codes { ... };`
  // here, instead of `using`.
  using Codes = MyExampleEnum;
  static constexpr StatusGroupType Group() { return "MyExampleStatus"; }
  static constexpr Codes OkEnumValue() { return Codes::kDefaultValue; }
  static uint32_t PackExtraData(const StatusData& info) {
    std::optional<int> hresult = info.data.GetIntValue("HRESULT");
    return static_cast<uint32_t>(hresult.has_value() ? *hresult : 0);
  }
}
```


## Design decisions
See go/typedstatus for design decisions.