g1t/crates/contracts/src/packages.rs

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