g1t/crates/contracts/src/packages.rs

288 lines9,324 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.

Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member1//! The packages service: the registries a workspace publishes to and
2//! installs from, beside its code (docs/PACKAGES.md). Container images
3//! first, spoken over the OCI Distribution protocol on `g1t.sh/v2/`; npm,
4//! Composer, Cargo and Go after.
5//!
6//! The site reaches it over `POST /rpc/<method>` with the arguments below;
7//! the registries' own protocols are any other request. Mirrors
8//! `packages/contracts/src/packages.ts`.
9//!
10//! Who may do what: a package linked to a repository has that
11//! repository's visibility and roles (Read pulls, Write publishes, Admin
12//! deletes and changes settings). An unlinked one belongs to its workspace:
13//! members by the base permission, owners delete. Public packages pull
14//! anonymously.
15
16use serde::{Deserialize, Serialize};
17
18use crate::audit::Surface;
19use crate::{User, Viewer};
20
21/// Which registry a package is in.
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum Ecosystem {
25 Container,
26 Npm,
27 Composer,
28 Cargo,
29 Go,
30}
31
32impl Ecosystem {
33 pub const ALL: [Ecosystem; 5] = [
34 Ecosystem::Container,
35 Ecosystem::Npm,
36 Ecosystem::Composer,
37 Ecosystem::Cargo,
38 Ecosystem::Go,
39 ];
40
41 pub fn as_str(self) -> &'static str {
42 match self {
43 Ecosystem::Container => "container",
44 Ecosystem::Npm => "npm",
45 Ecosystem::Composer => "composer",
46 Ecosystem::Cargo => "cargo",
47 Ecosystem::Go => "go",
48 }
49 }
50
51 pub fn parse(text: &str) -> Option<Ecosystem> {
52 Ecosystem::ALL.into_iter().find(|e| e.as_str() == text)
53 }
54}
55
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "snake_case")]
58pub enum Visibility {
59 Public,
60 #[default]
61 Private,
62}
63
64impl Visibility {
65 pub fn as_str(self) -> &'static str {
66 match self {
67 Visibility::Public => "public",
68 Visibility::Private => "private",
69 }
70 }
71
72 pub fn parse(text: &str) -> Visibility {
73 if text == "public" { Visibility::Public } else { Visibility::Private }
74 }
75}
76
77/// The repository a package is linked to.
78#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
79pub struct LinkedRepo {
80 pub id: String,
81 pub namespace: String,
82 pub name: String,
83}
84
85/// A package as listings show it.
86#[derive(Clone, Debug, Serialize, Deserialize)]
87pub struct PackageSummary {
88 pub id: String,
89 pub workspace: String,
90 pub ecosystem: Ecosystem,
91 /// Without the workspace: `web` for `g1t.sh/acme/web`.
92 pub name: String,
93 /// What a client is given: `g1t.sh/acme/web` for a container image.
94 pub address: String,
95 /// Linked packages follow their repository's visibility.
96 pub visibility: Visibility,
97 pub repo: Option<LinkedRepo>,
98 pub description: Option<String>,
99 pub versions: u32,
100 /// The newest version's tag (for a container image, `latest` when it
101 /// has one) or version.
102 pub latest: Option<String>,
103 /// Bytes its versions hold, each file counted once.
104 pub size: u64,
105 /// Pulls and installs, counted approximately.
106 pub downloads: u64,
107 pub created_at: String,
108 pub updated_at: String,
109}
110
111/// One version: for a container image, one manifest, by digest.
112#[derive(Clone, Debug, Serialize, Deserialize)]
113pub struct PackageVersion {
114 pub id: String,
115 /// A tag, semver or (for container images) the manifest's digest.
116 pub version: String,
117 pub digest: String,
118 /// Bytes of its files: an image's layers, config and manifest.
119 pub size: u64,
120 pub media_type: Option<String>,
121 /// For an OCI artifact: what it is, such as a signature or an SBOM.
122 pub artifact_type: Option<String>,
123 /// For an artifact attached to another version: that version's digest.
124 pub subject: Option<String>,
125 /// For an image index: the platforms it holds, such as `linux/amd64`.
126 pub platforms: Vec<String>,
127 pub tags: Vec<String>,
128 /// The username that published it.
129 pub published_by: Option<String>,
130 pub published_at: String,
npm on g1t.sh: publish and install @<workspace>/<name> with the npm CLI and a g1t token131 /// npm: why the version should no longer be used, when it is deprecated.
132 #[serde(default)]
133 pub deprecated: Option<String>,
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member134}
135
136#[derive(Clone, Debug, Serialize, Deserialize)]
137pub struct PackageTag {
138 pub tag: String,
139 pub digest: String,
140 pub updated_at: String,
141}
142
143/// What the viewer may do with a package.
144#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
145pub struct PackagePermissions {
146 pub pull: bool,
147 pub push: bool,
148 pub delete: bool,
149 /// Change its visibility and link.
150 pub admin: bool,
151}
152
153/// `get_package`.
154#[derive(Clone, Debug, Serialize, Deserialize)]
155pub struct PackageDetail {
156 pub package: PackageSummary,
157 /// Newest first.
158 pub versions: Vec<PackageVersion>,
159 pub tags: Vec<PackageTag>,
160 pub permissions: PackagePermissions,
npm on g1t.sh: publish and install @<workspace>/<name> with the npm CLI and a g1t token161 /// The package's README, as markdown: npm's, from its latest version.
162 #[serde(default)]
163 pub readme: Option<String>,
Packages, with a container registry on g1t.sh; workspaces deleted whole and kept 30 days; Members for every member164}
165
166/// `list_packages`: the packages in a workspace the viewer may pull, newest
167/// first. Returns `Outcome<Vec<PackageSummary>>`.
168#[derive(Clone, Debug, Default, Serialize, Deserialize)]
169pub struct ListPackagesArgs {
170 pub workspace: String,
171 pub viewer: Viewer,
172 #[serde(default)]
173 pub ecosystem: Option<Ecosystem>,
174 /// Only those linked to this repository.
175 #[serde(default)]
176 pub repo_id: Option<String>,
177 /// Matched against names.
178 #[serde(default)]
179 pub query: Option<String>,
180}
181
182/// `get_package`. Returns `Outcome<PackageDetail>`; not found when the
183/// viewer may not pull it.
184#[derive(Clone, Debug, Serialize, Deserialize)]
185pub struct GetPackageArgs {
186 pub workspace: String,
187 pub ecosystem: Ecosystem,
188 pub name: String,
189 pub viewer: Viewer,
190}
191
192/// `delete_version`: a version, by its version or digest, or by a tag that
193/// points to it. Its tags go with it. Returns `Outcome<()>`.
194#[derive(Clone, Debug, Serialize, Deserialize)]
195pub struct DeleteVersionArgs {
196 pub actor: User,
197 pub workspace: String,
198 pub ecosystem: Ecosystem,
199 pub name: String,
200 pub version: String,
201 #[serde(default)]
202 pub surface: Option<Surface>,
203}
204
205/// `delete_package`: a package and every version. Returns `Outcome<()>`.
206#[derive(Clone, Debug, Serialize, Deserialize)]
207pub struct DeletePackageArgs {
208 pub actor: User,
209 pub workspace: String,
210 pub ecosystem: Ecosystem,
211 pub name: String,
212 #[serde(default)]
213 pub surface: Option<Surface>,
214}
215
216/// `set_package`: change a package's visibility, or the repository it is
217/// linked to. `link` names a repository of its workspace; `unlink` takes
218/// the link away (the package is then the workspace's, and private until
219/// someone makes it public). A linked package's visibility is its
220/// repository's, so `visibility` is refused for one. Needs Admin. Returns
221/// `Outcome<PackageSummary>`.
222#[derive(Clone, Debug, Serialize, Deserialize)]
223pub struct SetPackageArgs {
224 pub actor: User,
225 pub workspace: String,
226 pub ecosystem: Ecosystem,
227 pub name: String,
228 #[serde(default)]
229 pub visibility: Option<Visibility>,
230 #[serde(default)]
231 pub link: Option<String>,
232 #[serde(default)]
233 pub unlink: bool,
234 #[serde(default)]
235 pub surface: Option<Surface>,
236}
237
238/// `storage`: what a workspace's packages hold, each file counted once, as
239/// public when any public package uses it. For billing. Returns
240/// [`PackageStorage`].
241#[derive(Clone, Debug, Serialize, Deserialize)]
242pub struct StorageArgs {
243 pub workspace: String,
244}
245
246#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
247pub struct PackageStorage {
248 pub public_bytes: u64,
249 pub private_bytes: u64,
250}
251
252/// `storage_all`: [`PackageStorage`] for every workspace that has
253/// packages, from one query, for billing's daily measure. Takes `{}`;
254/// returns `Vec<WorkspacePackageStorage>`, by workspace.
255#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
256pub struct WorkspacePackageStorage {
257 pub workspace: String,
258 pub public_bytes: u64,
259 pub private_bytes: u64,
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn ecosystems_read_back() {
268 for ecosystem in Ecosystem::ALL {
269 assert_eq!(Ecosystem::parse(ecosystem.as_str()), Some(ecosystem));
270 assert_eq!(serde_json::to_value(ecosystem).unwrap(), ecosystem.as_str());
271 }
272 assert_eq!(Visibility::parse("public"), Visibility::Public);
273 assert_eq!(Visibility::parse("anything"), Visibility::Private);
274 }
275
276 /// The site's copy, `packages/contracts/src/packages.ts`, names the
277 /// same ecosystems and methods.
278 #[test]
279 fn the_typescript_mirror_names_the_same_ecosystems_and_methods() {
280 let ts = include_str!("../../../packages/contracts/src/packages.ts");
281 for ecosystem in Ecosystem::ALL {
282 assert!(ts.contains(&format!("\"{}\"", ecosystem.as_str())), "{}", ecosystem.as_str());
283 }
284 for method in ["list_packages", "get_package", "delete_version", "delete_package", "set_package", "storage", "storage_all"] {
285 assert!(ts.contains(&format!("\"{method}\"")), "{method}");
286 }
287 }
288}