//! The Onset protocol's JSON shapes, shared by the CLI and the servers. //! //! Base URL: what `https:///.well-known/onset.json` (or `vcs.json`, on //! a host from before Onset had its name) names as `api`. //! Every request but discovery carries `Authorization: Bearer `. //! //! ```text //! GET /me → Me //! POST /projects {owner, name, public} → ProjectView //! GET /projects/{o}/{n} → ProjectView //! GET /projects/{o}/{n}/versions?since=N → {versions: [Version]} (n > N, ascending) //! POST /projects/{o}/{n}/objects/missing ids (32 bytes each) → the ones the host lacks //! POST /projects/{o}/{n}/objects bundle → {stored} //! GET /projects/{o}/{n}/trees/{tree}?have=T bundle: everything under tree, minus what T shares //! GET /projects/{o}/{n}/changes → {changes: [ChangeRecord]} (the ones you can see) //! GET /projects/{o}/{n}/changes/{prefix} → {change: ChangeRecord, saves: [SaveRecord]} //! PUT /projects/{o}/{n}/changes/{id} PutChange (with its newest save) → {change} (409 held_elsewhere) //! POST /projects/{o}/{n}/changes/{id}/hold Hold → {previous} //! POST /projects/{o}/{n}/changes/{id}/saves SaveRecord + workspace → {} (409 held_elsewhere) //! POST /projects/{o}/{n}/changes/{id}/access Share → {shared} //! POST /projects/{o}/{n}/land LandRequest (with the change's record) → Landed //! POST /projects/{o}/{n}/import {versions} → {main} (owners, empty projects) //! POST /projects/{o}/{n}/edits {base?, title, body?, files: {path: text|null}, files_base64?, executable?, allow_secrets?} → Landed + {change} //! GET /projects/{o}/{n}/file?path=&version= → {version, path, kind, base64, size} (up to 1 MB) //! GET /projects/{o}/{n}/folder?path=&version= → {version, path, entries: [[name, kind]]} //! POST /projects/{o}/{n}/changes/{id}/approve → Landed, or {outcome: "approved", approvals, required} //! GET /projects/{o}/{n}/settings → {approvals}; PUT (owners) {approvals} //! GET /projects/{o}/{n}/groups → {groups: {name: [handle]}}; PUT (owners) {name, members} //! POST /projects/{o}/{n}/git/ids {ids: {native: git}} (owners; after an import) //! POST /projects/{o}/{n}/git/resume → {adopted} (owners; restarts a paused git view) //! GET /projects/{o}/{n}/rules → {rules: [Rule]} //! PUT /projects/{o}/{n}/rules Rule → {rules} (owners) //! GET /projects/{o}/{n}/mine → {files: [MineFile]} (your own) //! PUT /projects/{o}/{n}/mine {files: [MineFile]} //! GET /projects/{o}/{n}/mine/bundle bundle of your own files //! ``` //! //! Errors: an HTTP status and `{"error": {"code", "message"}}`. use serde::{Deserialize, Serialize}; use onset_core::Id; use onset_core::version::Version; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Me { pub handle: String, pub name: String, pub email: String, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct Can { pub read: bool, pub land: bool, pub admin: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProjectView { pub owner: String, pub name: String, #[serde(default)] pub public: bool, pub main: Option, pub can: Can, /// Where git clients clone this project's git view, if it has one. #[serde(default)] pub git: Option, /// Why the git view stopped taking new versions, if it did. #[serde(default)] pub git_stopped: Option, /// This person's own git URL, with the private folders they can see /// (the git view leaves them out). #[serde(default)] pub git_mine: Option, /// Switched on in Upstream for a repository with git history that /// hasn't come in yet: the first on get brings it. #[serde(default)] pub import_pending: bool, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ChangeRecord { pub id: String, #[serde(default)] pub title: String, #[serde(default)] pub body: String, pub base_tree: Id, #[serde(default)] pub base_version: Option, #[serde(default)] pub on_change: Option, #[serde(default)] pub after_change: Option, #[serde(default)] pub head_tree: Option, #[serde(default)] pub saves: i64, #[serde(default = "open")] pub state: String, #[serde(default)] pub version: Option, #[serde(default)] pub author: String, /// The account that owns it (set by the host). #[serde(default)] pub owner: String, #[serde(default)] pub shared: Vec, /// Where it's being worked on ("laptop:~/shop"), set by the host. #[serde(default)] pub holder: Option, #[serde(default)] pub updated: i64, /// Its change request, while the host's rules have it in review. #[serde(default, skip_serializing_if = "Option::is_none")] pub review: Option, /// The release line it lands on (None: main). #[serde(default, skip_serializing_if = "Option::is_none")] pub line: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ReviewLink { pub number: u64, pub url: String, } fn open() -> String { "open".into() } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Share { pub who: String, #[serde(default)] pub edit: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PutChange { pub change: ChangeRecord, pub workspace: String, #[serde(default)] pub label: String, /// The change's newest save, recorded in the same call. #[serde(default, skip_serializing_if = "Option::is_none")] pub save: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Hold { pub workspace: String, #[serde(default)] pub label: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SaveRecord { pub n: i64, pub tree: Id, pub base_tree: Id, pub at: i64, #[serde(default)] pub workspace: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ShareRequest { pub who: Vec, #[serde(default)] pub edit: bool, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Rule { pub path: String, /// `@group`, `@person`, `team`, `everyone`. pub who: Vec, /// Encrypted on its readers' machines; the host can't read it (seal.rs). #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub sealed: bool, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct MineFile { pub path: String, pub file: Id, #[serde(default)] pub exec: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ErrorBody { pub error: ErrorInfo, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ErrorInfo { pub code: String, pub message: String, #[serde(default)] pub holder: Option, } /// Concatenated 32-byte IDs, the body of objects/missing. pub fn encode_ids(ids: &[Id]) -> Vec { ids.iter().flat_map(|i| i.0).collect() } pub fn decode_ids(bytes: &[u8]) -> Vec { bytes.chunks_exact(32).filter_map(Id::from_slice).collect() }