flagon-io/g1t

public

Git for AI scale: a forge for thousands of agents working on the same code at once.

g1t/crates/contracts/src/outcome.rs

120 lines3,840 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

API and MCP server, Rust identity service, registration, site redesign1use serde::de::DeserializeOwned;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "snake_case")]
6pub enum FailureCode {
7 NotFound,
8 Forbidden,
9 Unauthenticated,
10 Conflict,
11 Invalid,
Agents as a team: lifecycle, merge queue, billing and a new shell12 /// The workspace has to pay before this can happen.
13 PaymentRequired,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look14 // 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,
API and MCP server, Rust identity service, registration, site redesign30}
31
32impl 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,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look40 FailureCode::PaymentRequired
41 | FailureCode::NotPaid
42 | FailureCode::TrialUsed
43 | FailureCode::Limit
44 | FailureCode::OssPoolEmpty => 402,
45 FailureCode::Paused => 409,
46 FailureCode::ReauthRequired => 403,
API and MCP server, Rust identity service, registration, site redesign47 }
48 }
49}
50
51#[derive(Clone, Debug, Serialize, Deserialize)]
52pub 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)]
62pub enum Outcome<T> {
63 Ok(T),
64 Fail(Failure),
65}
66
67impl<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"))]
85struct 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
93impl<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
111impl<'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}