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    pub expires_in: u32,
83}
84
85#[derive(Debug, Serialize)]
86pub struct ObjectError {
87    pub code: u16,
88    pub message: String,
89}
90
91impl ObjectSpec {
92    pub fn missing(id: ObjectId) -> Self {
93        Self {
94            id,
95            authenticated: None,
96            actions: None,
97            error: Some(ObjectError {
98                code: 404,
99                message: "object not found".into(),
100            }),
101        }
102    }
103
104    pub fn too_large(id: ObjectId, limit: u64) -> Self {
105        Self {
106            error: Some(ObjectError {
107                code: 413,
108                message: format!(
109                    "object is {} bytes, over the {limit} byte limit this server accepts",
110                    id.size
111                ),
112            }),
113            id,
114            authenticated: None,
115            actions: None,
116        }
117    }
118
119    pub fn over_quota(id: ObjectId, used: u64, limit: u64) -> Self {
120        Self {
121            id,
122            authenticated: None,
123            actions: None,
124            error: Some(ObjectError {
125                code: 507,
126                message: format!(
127                    "this repository holds {used} bytes of its {limit} byte budget — collect what \
128                     it no longer references, or ask for more room"
129                ),
130            }),
131        }
132    }
133}
134
135#[derive(Debug, Deserialize)]
136pub struct CreateLockRequest {
137    pub path: String,
138}
139
140#[derive(Debug, Deserialize)]
141pub struct ListLocksQuery {
142    pub path: Option<String>,
143    pub id: Option<String>,
144    pub cursor: Option<String>,
145    pub limit: Option<usize>,
146    // Accepted and not acted on: see `refs` in the README. Rejecting it would
147    // break a client that sends what the specification tells it to send.
148    #[serde(rename = "refspec")]
149    pub refspec: Option<String>,
150}
151
152#[derive(Debug, Default, Deserialize)]
153pub struct VerifyLocksRequest {
154    pub cursor: Option<String>,
155    pub limit: Option<usize>,
156    #[serde(rename = "ref")]
157    pub reference: Option<Reference>,
158}
159
160#[derive(Debug, Deserialize)]
161pub struct Reference {
162    pub name: String,
163}
164
165#[derive(Debug, Deserialize)]
166pub struct UnlockRequest {
167    #[serde(default)]
168    pub force: bool,
169}
170
171#[derive(Debug, Serialize)]
172pub struct LockResponse {
173    pub lock: Lock,
174}
175
176#[derive(Debug, Serialize)]
177pub struct ListLocksResponse {
178    pub locks: Vec<Lock>,
179    #[serde(skip_serializing_if = "String::is_empty")]
180    pub next_cursor: String,
181}
182
183#[derive(Debug, Serialize)]
184pub struct VerifyLocksResponse {
185    pub ours: Vec<Lock>,
186    pub theirs: Vec<Lock>,
187    #[serde(skip_serializing_if = "String::is_empty")]
188    pub next_cursor: String,
189}
190
191#[derive(Debug, Serialize)]
192pub struct StatsResponse {
193    pub objects: u64,
194    pub bytes: u64,
195    pub locks: usize,
196}