//! The vcs protocol's JSON shapes, shared by the CLI and the servers. //! //! Base URL: what `https:///.well-known/vcs.json` 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}/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 vcs_core::Id; use vcs_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, } #[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, } 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, } #[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() }