g1t/crates/contracts/src/lib.rs

151 lines5,347 bytesCodeBlame
1//! Types and service interfaces shared by every g1t service.
2//!
3//! Each service has a module here holding the data it exchanges and the
4//! arguments of each of its methods. Services and their callers depend on
5//! this crate, never on each other's code.
6
7pub mod access;
8pub mod accounts;
9pub mod actions;
10pub mod agents;
11pub mod audit;
12pub mod billing;
13pub mod capture;
14pub mod credentials;
15pub mod events;
16pub mod github;
17pub mod guardrails;
18pub mod identity;
19pub mod integrations;
20mod ids;
21mod names;
22mod outcome;
23pub mod projects;
24pub mod repos;
25pub mod runners;
26pub mod scopes;
27pub mod search;
28pub mod security;
29pub mod time;
30pub mod webhooks;
31pub mod work;
32
33pub use ids::new_id;
34pub use names::{is_valid_namespace, is_valid_repo_name};
35pub use outcome::{Failure, FailureCode, Outcome};
36
37use serde::{Deserialize, Serialize};
38
39/// What a member may do in a workspace.
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "lowercase")]
42pub enum Role {
43 /// Everything a member can, plus managing members.
44 Owner,
45 /// Create repositories, push, manage issues and merge pull requests.
46 Member,
47}
48
49/// One workspace a user belongs to.
50#[derive(Clone, Debug, Serialize, Deserialize)]
51pub struct Membership {
52 /// The workspace's name in URLs: `g1t.sh/<slug>`.
53 pub slug: String,
54 pub role: Role,
55 /// The workspace's display name, for showing it to people. Set when a
56 /// user is resolved from credentials; absent on principals made up by
57 /// a service.
58 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub name: Option<String>,
60 /// The workspace's uploaded icon: the SHA-256 of its bytes, served at
61 /// `/avatars/<avatar>`. Absent means the generated letter avatar.
62 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub avatar: Option<String>,
64 /// What a member gets on each of the workspace's repositories: the
65 /// workspace's base permission. Set when a user is resolved from
66 /// credentials; absent means the default, Write. Owners have Admin
67 /// whatever it says. See [`access`].
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub base_permission: Option<access::BasePermission>,
70}
71
72impl Membership {
73 /// A plain member of `slug`, as services act inside one workspace.
74 pub fn member(slug: impl Into<String>) -> Self {
75 Membership {
76 slug: slug.into(),
77 role: Role::Member,
78 name: None,
79 avatar: None,
80 base_permission: None,
81 }
82 }
83}
84
85/// What a set of credentials resolved to.
86#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
87#[serde(rename_all = "lowercase")]
88pub enum PrincipalKind {
89 /// A person's account.
90 #[default]
91 User,
92 /// A workspace, acting through one of its own access tokens. Its `id`
93 /// is the workspace's, its `username` the workspace's slug, and it is a
94 /// member of that workspace and no other.
95 Workspace,
96 /// A g1t agent at work in a sandbox, acting through a token that lives
97 /// as long as its run and can do only what that token's scope lists, in
98 /// one repository. Its `username` is `g1t-agent`.
99 Agent,
100}
101
102#[derive(Clone, Debug, Default, Serialize, Deserialize)]
103pub struct User {
104 pub id: String,
105 pub username: String,
106 #[serde(default)]
107 pub kind: PrincipalKind,
108 /// Whether the account's email address has been confirmed. Unverified
109 /// accounts can sign in but cannot create or change anything.
110 #[serde(default)]
111 pub verified: bool,
112 /// The workspaces this user belongs to. Filled in when a user is
113 /// resolved from credentials, so any service can authorize from it.
114 #[serde(default)]
115 pub workspaces: Vec<Membership>,
116 /// The person's uploaded avatar: the SHA-256 of its bytes, served at
117 /// `/avatars/<avatar>`. Absent means the generated letter avatar.
118 #[serde(default, skip_serializing_if = "Option::is_none")]
119 pub avatar: Option<String>,
120 /// Set on an agent resolved from its token: who it acts for, with which
121 /// credential, and what it may do. See [`credentials`].
122 #[serde(default, skip_serializing_if = "Option::is_none")]
123 pub acting: Option<Box<credentials::Acting>>,
124 /// The repositories this user has been given a role on directly,
125 /// whether or not they belong to its workspace. Filled in with
126 /// `workspaces`; see [`access`].
127 #[serde(default, skip_serializing_if = "Vec::is_empty")]
128 pub grants: Vec<access::RepoGrant>,
129 /// Set on a user resolved from an access token: its scopes and the
130 /// workspaces or repositories it is limited to. Absent on a signed-in
131 /// session and on an agent (whose `acting` scope applies instead).
132 /// See [`scopes`].
133 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub token: Option<Box<scopes::TokenAccess>>,
135}
136
137impl User {
138 pub fn role_in(&self, slug: &str) -> Option<Role> {
139 self.workspaces
140 .iter()
141 .find(|membership| membership.slug == slug)
142 .map(|membership| membership.role)
143 }
144
145 pub fn is_member(&self, slug: &str) -> bool {
146 self.role_in(slug).is_some()
147 }
148}
149
150/// Who is asking. Every read and write in every service takes one.
151pub type Viewer = Option<User>;