1use feltdb::application::{
5 ApplicationDraft, ApplicationManifest, ApplicationRevision, ValidationReport,
6};
7use serde::Deserialize;
8use serde_json::{json, Value};
9
10use crate::{encode, Client, Method, Result};
11
12#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
14pub struct Health {
15 pub status: String,
17 pub version: String,
19 #[serde(default)]
20 pub git_commit: Option<String>,
21 #[serde(default)]
22 pub release_id: Option<String>,
23 #[serde(default)]
24 pub public_contract: Option<String>,
25 #[serde(default)]
26 pub capability_fingerprint: Option<String>,
27 #[serde(default)]
28 pub storage: Option<String>,
29 #[serde(default)]
30 pub membership: Option<String>,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
35pub struct Tenant {
36 pub id: String,
37 pub name: String,
38 #[serde(default)]
39 pub slug: Option<String>,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
45pub struct ApplicationRecord {
46 pub id: String,
47 pub tenant_id: String,
48 pub name: String,
49 #[serde(default)]
50 pub slug: Option<String>,
51 #[serde(default)]
52 pub status: Option<String>,
53}
54
55#[derive(Debug, Clone)]
57pub struct Promotion {
58 environment: String,
59 reason: String,
60 expected_current_revision: Option<String>,
61 approve_destructive_changes: bool,
62}
63
64impl Promotion {
65 pub fn new(environment: impl Into<String>) -> Self {
66 Self {
67 environment: environment.into(),
68 reason: String::new(),
69 expected_current_revision: None,
70 approve_destructive_changes: false,
71 }
72 }
73
74 pub fn reason(mut self, reason: impl Into<String>) -> Self {
76 self.reason = reason.into();
77 self
78 }
79
80 pub fn expect_current(mut self, revision_id: Option<String>) -> Self {
83 self.expected_current_revision = revision_id;
84 self
85 }
86
87 pub fn approve_destructive_changes(mut self) -> Self {
89 self.approve_destructive_changes = true;
90 self
91 }
92}
93
94#[derive(Debug, Clone)]
96pub struct Control {
97 client: Client,
98}
99
100impl Control {
101 fn application_path(application_id: &str, rest: &str) -> String {
102 format!("/api/applications/{}{rest}", encode(application_id))
103 }
104
105 fn draft_path(application_id: &str, draft_id: &str, rest: &str) -> String {
106 Self::application_path(
107 application_id,
108 &format!("/drafts/{}{rest}", encode(draft_id)),
109 )
110 }
111
112 pub async fn tenants(&self) -> Result<Vec<Tenant>> {
114 self.client
115 .send::<_, ()>(Method::GET, "/api/tenants", None)
116 .await
117 }
118
119 pub async fn create_tenant(&self, name: &str) -> Result<Tenant> {
120 self.client
121 .send(Method::POST, "/api/tenants", Some(&json!({ "name": name })))
122 .await
123 }
124
125 pub async fn applications(&self, tenant_id: &str) -> Result<Vec<ApplicationRecord>> {
127 let path = format!("/api/tenants/{}/applications", encode(tenant_id));
128 self.client.send::<_, ()>(Method::GET, &path, None).await
129 }
130
131 pub async fn create_application(
132 &self,
133 tenant_id: &str,
134 name: &str,
135 ) -> Result<ApplicationRecord> {
136 let path = format!("/api/tenants/{}/applications", encode(tenant_id));
137 self.client
138 .send(Method::POST, &path, Some(&json!({ "name": name })))
139 .await
140 }
141
142 pub async fn application(&self, application_id: &str) -> Result<ApplicationRecord> {
143 self.client
144 .send::<_, ()>(
145 Method::GET,
146 &Self::application_path(application_id, ""),
147 None,
148 )
149 .await
150 }
151
152 pub async fn create_draft(
155 &self,
156 application_id: &str,
157 base_revision_id: Option<&str>,
158 ) -> Result<ApplicationDraft> {
159 self.client
160 .send(
161 Method::POST,
162 &Self::application_path(application_id, "/drafts"),
163 Some(&json!({ "base_revision_id": base_revision_id })),
164 )
165 .await
166 }
167
168 pub async fn draft(&self, application_id: &str, draft_id: &str) -> Result<ApplicationDraft> {
169 self.client
170 .send::<_, ()>(
171 Method::GET,
172 &Self::draft_path(application_id, draft_id, ""),
173 None,
174 )
175 .await
176 }
177
178 pub async fn update_draft(
181 &self,
182 application_id: &str,
183 draft_id: &str,
184 version: u64,
185 manifest: &ApplicationManifest,
186 ) -> Result<ApplicationDraft> {
187 self.client
188 .send_with_headers(
189 Method::PUT,
190 &Self::draft_path(application_id, draft_id, ""),
191 &[("if-version", &version.to_string())],
192 Some(&json!({ "manifest": manifest })),
193 )
194 .await
195 }
196
197 pub async fn validate_draft(
200 &self,
201 application_id: &str,
202 draft_id: &str,
203 ) -> Result<ValidationReport> {
204 self.client
205 .send(
206 Method::POST,
207 &Self::draft_path(application_id, draft_id, "/validate"),
208 Some(&json!({})),
209 )
210 .await
211 }
212
213 pub async fn commit_draft(
215 &self,
216 application_id: &str,
217 draft_id: &str,
218 ) -> Result<ApplicationRevision> {
219 self.client
220 .send(
221 Method::POST,
222 &Self::draft_path(application_id, draft_id, "/commit"),
223 Some(&json!({})),
224 )
225 .await
226 }
227
228 pub async fn revision(
229 &self,
230 application_id: &str,
231 revision_id: &str,
232 ) -> Result<ApplicationRevision> {
233 let path = Self::application_path(
234 application_id,
235 &format!("/revisions/{}", encode(revision_id)),
236 );
237 self.client.send::<_, ()>(Method::GET, &path, None).await
238 }
239
240 pub async fn promote(
243 &self,
244 application_id: &str,
245 revision_id: &str,
246 promotion: &Promotion,
247 ) -> Result<Value> {
248 let path = Self::application_path(
249 application_id,
250 &format!("/revisions/{}/promote", encode(revision_id)),
251 );
252 self.client
253 .send(
254 Method::POST,
255 &path,
256 Some(&json!({
257 "environment": promotion.environment,
258 "reason": promotion.reason,
259 "expected_current_revision": promotion.expected_current_revision,
260 "approve_destructive_changes": promotion.approve_destructive_changes,
261 })),
262 )
263 .await
264 }
265
266 pub async fn backup(&self, output: &str) -> Result<Value> {
270 self.client
271 .send(
272 Method::POST,
273 "/admin/backups",
274 Some(&json!({ "output": output })),
275 )
276 .await
277 }
278}
279
280impl Client {
281 pub async fn health(&self) -> Result<Health> {
283 self.send::<_, ()>(Method::GET, "/health", None).await
284 }
285
286 pub fn control(&self) -> Control {
288 Control {
289 client: self.clone(),
290 }
291 }
292}