Skip to main content

lfsx_server/
model.rs

1use serde::{Deserialize, Serialize};
2
3use crate::locks::Lock;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
6#[serde(rename_all = "lowercase")]
7pub enum Operation {
8    Upload,
9    Download,
10}
11
12#[derive(Debug, Clone, Deserialize, Serialize)]
13pub struct ObjectId {
14    pub oid: String,
15    pub size: u64,
16}
17
18#[derive(Debug, Deserialize)]
19pub struct BatchRequest {
20    pub operation: Operation,
21    #[serde(default)]
22    pub transfers: Vec<String>,
23    pub objects: Vec<ObjectId>,
24    #[serde(rename = "ref")]
25    pub reference: Option<Reference>,
26}
27
28#[derive(Debug, Deserialize)]
29pub struct RetainRequest {
30    pub oids: Vec<String>,
31    #[serde(default)]
32    pub dry_run: bool,
33}
34
35#[derive(Debug, Deserialize)]
36pub struct CompressRequest {
37    #[serde(default)]
38    pub dry_run: bool,
39}
40
41#[derive(Debug, Deserialize)]
42pub struct DedupeRequest {
43    #[serde(default)]
44    pub dry_run: bool,
45}
46
47#[derive(Debug, Serialize)]
48pub struct BatchResponse {
49    pub transfer: &'static str,
50    pub objects: Vec<ObjectSpec>,
51}
52
53#[derive(Debug, Serialize)]
54pub struct ObjectSpec {
55    #[serde(flatten)]
56    pub id: ObjectId,
57    // Set only when the href carries its own credentials, which is true of a
58    // pre-signed bucket URL and of nothing else this server hands out. Claiming
59    // it for a URL pointing back here makes the client transfer with no
60    // credentials at all, and loop on the 401 that follows.
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub authenticated: Option<bool>,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub actions: Option<Actions>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub error: Option<ObjectError>,
67}
68
69#[derive(Debug, Default, Serialize)]
70pub struct Actions {
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub upload: Option<Action>,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub download: Option<Action>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub verify: Option<Action>,
77}
78
79#[derive(Debug, Serialize)]
80pub struct Action {
81    pub href: String,
82    // Headers the client must send with the request. Not advice: for a
83    // pre-signed upload the digest is bound into the signature, so an altered or
84    // missing header is a refused request rather than a weaker one.
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub header: Option<std::collections::BTreeMap<String, String>>,
87    pub expires_in: u32,
88}
89
90#[derive(Debug, Serialize)]
91pub struct ObjectError {
92    pub code: u16,
93    pub message: String,
94}
95
96impl ObjectSpec {
97    pub fn missing(id: ObjectId) -> Self {
98        Self {
99            id,
100            authenticated: None,
101            actions: None,
102            error: Some(ObjectError {
103                code: 404,
104                message: "object not found".into(),
105            }),
106        }
107    }
108
109    // The oid never reaches storage: it failed to parse at the boundary, so
110    // nothing was looked up and 404 would be a lie. 422 is what the rest of
111    // the request's class gets, and the message is the parser's own.
112    pub fn malformed(id: ObjectId) -> Self {
113        Self {
114            id,
115            authenticated: None,
116            actions: None,
117            error: Some(ObjectError {
118                code: 422,
119                message: crate::error::Error::MalformedOid.to_string(),
120            }),
121        }
122    }
123
124    pub fn too_large(id: ObjectId, limit: u64) -> Self {
125        Self {
126            error: Some(ObjectError {
127                code: 413,
128                message: format!(
129                    "object is {} bytes, over the {limit} byte limit this server accepts",
130                    id.size
131                ),
132            }),
133            id,
134            authenticated: None,
135            actions: None,
136        }
137    }
138
139    pub fn over_quota(id: ObjectId, used: u64, limit: u64) -> Self {
140        Self {
141            id,
142            authenticated: None,
143            actions: None,
144            error: Some(ObjectError {
145                code: 507,
146                message: format!(
147                    "this repository holds {used} bytes of its {limit} byte budget: collect what \
148                     it no longer references, or ask for more room"
149                ),
150            }),
151        }
152    }
153}
154
155#[derive(Debug, Deserialize)]
156pub struct CreateLockRequest {
157    pub path: String,
158}
159
160#[derive(Debug, Deserialize)]
161pub struct ListLocksQuery {
162    pub path: Option<String>,
163    pub id: Option<String>,
164    pub cursor: Option<String>,
165    pub limit: Option<usize>,
166    // Accepted and not acted on: see `refs` in the README. Rejecting it would
167    // break a client that sends what the specification tells it to send.
168    #[serde(rename = "refspec")]
169    pub refspec: Option<String>,
170}
171
172#[derive(Debug, Default, Deserialize)]
173pub struct VerifyLocksRequest {
174    pub cursor: Option<String>,
175    pub limit: Option<usize>,
176    #[serde(rename = "ref")]
177    pub reference: Option<Reference>,
178}
179
180#[derive(Debug, Deserialize)]
181pub struct Reference {
182    pub name: String,
183}
184
185#[derive(Debug, Deserialize)]
186pub struct UnlockRequest {
187    #[serde(default)]
188    pub force: bool,
189}
190
191#[derive(Debug, Serialize)]
192pub struct LockResponse {
193    pub lock: Lock,
194}
195
196#[derive(Debug, Serialize)]
197pub struct ListLocksResponse {
198    pub locks: Vec<Lock>,
199    #[serde(skip_serializing_if = "String::is_empty")]
200    pub next_cursor: String,
201}
202
203#[derive(Debug, Serialize)]
204pub struct VerifyLocksResponse {
205    pub ours: Vec<Lock>,
206    pub theirs: Vec<Lock>,
207    #[serde(skip_serializing_if = "String::is_empty")]
208    pub next_cursor: String,
209}
210
211#[derive(Debug, Serialize)]
212pub struct StatsResponse {
213    pub objects: u64,
214    pub bytes: u64,
215    pub locks: usize,
216}