pr_01m47d15m3e54sn21z27rpy5n9/crates/contracts/src/outcome.rs
| 1 | use serde::de::DeserializeOwned; |
| 2 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; |
| 3 | |
| 4 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] |
| 5 | #[serde(rename_all = "snake_case")] |
| 6 | pub enum FailureCode { |
| 7 | NotFound, |
| 8 | Forbidden, |
| 9 | Unauthenticated, |
| 10 | Conflict, |
| 11 | Invalid, |
| 12 | /// The workspace has to pay before this can happen. |
| 13 | PaymentRequired, |
| 14 | // Billing's refusals to start compute (`reserve`), each with a message |
| 15 | // that says what to do. See `g1t_contracts::billing::ReserveArgs`. |
| 16 | /// No plan, and no trial or pool that pays for this kind of work: a |
| 17 | /// card check or the g1t plan is needed. |
| 18 | NotPaid, |
| 19 | /// The workspace's one trial is spent. |
| 20 | TrialUsed, |
| 21 | /// The workspace reached its spend limit or g1t's ceiling. |
| 22 | Limit, |
| 23 | /// Compute is paused: a spend spike waiting for an owner, or a hold. |
| 24 | Paused, |
| 25 | /// This month's open-source pool, or the repository's share, is spent. |
| 26 | OssPoolEmpty, |
| 27 | /// A sensitive change needs the person to prove it is them again: a |
| 28 | /// recent sign-in, or their password. See `identity::Reauth`. |
| 29 | ReauthRequired, |
| 30 | } |
| 31 | |
| 32 | impl FailureCode { |
| 33 | pub fn http_status(self) -> u16 { |
| 34 | match self { |
| 35 | FailureCode::NotFound => 404, |
| 36 | FailureCode::Forbidden => 403, |
| 37 | FailureCode::Unauthenticated => 401, |
| 38 | FailureCode::Conflict => 409, |
| 39 | FailureCode::Invalid => 422, |
| 40 | FailureCode::PaymentRequired |
| 41 | | FailureCode::NotPaid |
| 42 | | FailureCode::TrialUsed |
| 43 | | FailureCode::Limit |
| 44 | | FailureCode::OssPoolEmpty => 402, |
| 45 | FailureCode::Paused => 409, |
| 46 | FailureCode::ReauthRequired => 403, |
| 47 | } |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | #[derive(Clone, Debug, Serialize, Deserialize)] |
| 52 | pub struct Failure { |
| 53 | pub code: FailureCode, |
| 54 | pub message: String, |
| 55 | } |
| 56 | |
| 57 | /// The result of a service call. Expected failures cross service boundaries |
| 58 | /// as values, so callers have to handle them. |
| 59 | /// |
| 60 | /// On the wire: `{"ok": true, "value": …}` or `{"ok": false, "error": …}`. |
| 61 | #[derive(Clone, Debug)] |
| 62 | pub enum Outcome<T> { |
| 63 | Ok(T), |
| 64 | Fail(Failure), |
| 65 | } |
| 66 | |
| 67 | impl<T> Outcome<T> { |
| 68 | pub fn fail(code: FailureCode, message: impl Into<String>) -> Self { |
| 69 | Outcome::Fail(Failure { |
| 70 | code, |
| 71 | message: message.into(), |
| 72 | }) |
| 73 | } |
| 74 | |
| 75 | pub fn into_result(self) -> Result<T, Failure> { |
| 76 | match self { |
| 77 | Outcome::Ok(value) => Ok(value), |
| 78 | Outcome::Fail(failure) => Err(failure), |
| 79 | } |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | #[derive(Serialize, Deserialize)] |
| 84 | #[serde(bound(serialize = "T: Serialize", deserialize = "T: DeserializeOwned"))] |
| 85 | struct Wire<T> { |
| 86 | ok: bool, |
| 87 | #[serde(default, skip_serializing_if = "Option::is_none")] |
| 88 | value: Option<T>, |
| 89 | #[serde(default, skip_serializing_if = "Option::is_none")] |
| 90 | error: Option<Failure>, |
| 91 | } |
| 92 | |
| 93 | impl<T: Serialize> Serialize for Outcome<T> { |
| 94 | fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { |
| 95 | match self { |
| 96 | Outcome::Ok(value) => Wire { |
| 97 | ok: true, |
| 98 | value: Some(value), |
| 99 | error: None, |
| 100 | }, |
| 101 | Outcome::Fail(failure) => Wire { |
| 102 | ok: false, |
| 103 | value: None, |
| 104 | error: Some(failure.clone()), |
| 105 | }, |
| 106 | } |
| 107 | .serialize(serializer) |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | impl<'de, T: DeserializeOwned> Deserialize<'de> for Outcome<T> { |
| 112 | fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { |
| 113 | let wire = Wire::<T>::deserialize(deserializer)?; |
| 114 | match (wire.ok, wire.value, wire.error) { |
| 115 | (true, Some(value), _) => Ok(Outcome::Ok(value)), |
| 116 | (false, _, Some(error)) => Ok(Outcome::Fail(error)), |
| 117 | _ => Err(serde::de::Error::custom("malformed outcome")), |
| 118 | } |
| 119 | } |
| 120 | } |