use feltdb::application::{
ApplicationDraft, ApplicationManifest, ApplicationRevision, ValidationReport,
};
use serde::Deserialize;
use serde_json::{json, Value};
use crate::{encode, Client, Method, Result};
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Health {
pub status: String,
pub version: String,
#[serde(default)]
pub git_commit: Option<String>,
#[serde(default)]
pub release_id: Option<String>,
#[serde(default)]
pub public_contract: Option<String>,
#[serde(default)]
pub capability_fingerprint: Option<String>,
#[serde(default)]
pub storage: Option<String>,
#[serde(default)]
pub membership: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Tenant {
pub id: String,
pub name: String,
#[serde(default)]
pub slug: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct ApplicationRecord {
pub id: String,
pub tenant_id: String,
pub name: String,
#[serde(default)]
pub slug: Option<String>,
#[serde(default)]
pub status: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Promotion {
environment: String,
reason: String,
expected_current_revision: Option<String>,
approve_destructive_changes: bool,
}
impl Promotion {
pub fn new(environment: impl Into<String>) -> Self {
Self {
environment: environment.into(),
reason: String::new(),
expected_current_revision: None,
approve_destructive_changes: false,
}
}
pub fn reason(mut self, reason: impl Into<String>) -> Self {
self.reason = reason.into();
self
}
pub fn expect_current(mut self, revision_id: Option<String>) -> Self {
self.expected_current_revision = revision_id;
self
}
pub fn approve_destructive_changes(mut self) -> Self {
self.approve_destructive_changes = true;
self
}
}
#[derive(Debug, Clone)]
pub struct Control {
client: Client,
}
impl Control {
fn application_path(application_id: &str, rest: &str) -> String {
format!("/api/applications/{}{rest}", encode(application_id))
}
fn draft_path(application_id: &str, draft_id: &str, rest: &str) -> String {
Self::application_path(
application_id,
&format!("/drafts/{}{rest}", encode(draft_id)),
)
}
pub async fn tenants(&self) -> Result<Vec<Tenant>> {
self.client
.send::<_, ()>(Method::GET, "/api/tenants", None)
.await
}
pub async fn create_tenant(&self, name: &str) -> Result<Tenant> {
self.client
.send(Method::POST, "/api/tenants", Some(&json!({ "name": name })))
.await
}
pub async fn applications(&self, tenant_id: &str) -> Result<Vec<ApplicationRecord>> {
let path = format!("/api/tenants/{}/applications", encode(tenant_id));
self.client.send::<_, ()>(Method::GET, &path, None).await
}
pub async fn create_application(
&self,
tenant_id: &str,
name: &str,
) -> Result<ApplicationRecord> {
let path = format!("/api/tenants/{}/applications", encode(tenant_id));
self.client
.send(Method::POST, &path, Some(&json!({ "name": name })))
.await
}
pub async fn application(&self, application_id: &str) -> Result<ApplicationRecord> {
self.client
.send::<_, ()>(
Method::GET,
&Self::application_path(application_id, ""),
None,
)
.await
}
pub async fn create_draft(
&self,
application_id: &str,
base_revision_id: Option<&str>,
) -> Result<ApplicationDraft> {
self.client
.send(
Method::POST,
&Self::application_path(application_id, "/drafts"),
Some(&json!({ "base_revision_id": base_revision_id })),
)
.await
}
pub async fn draft(&self, application_id: &str, draft_id: &str) -> Result<ApplicationDraft> {
self.client
.send::<_, ()>(
Method::GET,
&Self::draft_path(application_id, draft_id, ""),
None,
)
.await
}
pub async fn update_draft(
&self,
application_id: &str,
draft_id: &str,
version: u64,
manifest: &ApplicationManifest,
) -> Result<ApplicationDraft> {
self.client
.send_with_headers(
Method::PUT,
&Self::draft_path(application_id, draft_id, ""),
&[("if-version", &version.to_string())],
Some(&json!({ "manifest": manifest })),
)
.await
}
pub async fn validate_draft(
&self,
application_id: &str,
draft_id: &str,
) -> Result<ValidationReport> {
self.client
.send(
Method::POST,
&Self::draft_path(application_id, draft_id, "/validate"),
Some(&json!({})),
)
.await
}
pub async fn commit_draft(
&self,
application_id: &str,
draft_id: &str,
) -> Result<ApplicationRevision> {
self.client
.send(
Method::POST,
&Self::draft_path(application_id, draft_id, "/commit"),
Some(&json!({})),
)
.await
}
pub async fn revision(
&self,
application_id: &str,
revision_id: &str,
) -> Result<ApplicationRevision> {
let path = Self::application_path(
application_id,
&format!("/revisions/{}", encode(revision_id)),
);
self.client.send::<_, ()>(Method::GET, &path, None).await
}
pub async fn promote(
&self,
application_id: &str,
revision_id: &str,
promotion: &Promotion,
) -> Result<Value> {
let path = Self::application_path(
application_id,
&format!("/revisions/{}/promote", encode(revision_id)),
);
self.client
.send(
Method::POST,
&path,
Some(&json!({
"environment": promotion.environment,
"reason": promotion.reason,
"expected_current_revision": promotion.expected_current_revision,
"approve_destructive_changes": promotion.approve_destructive_changes,
})),
)
.await
}
pub async fn backup(&self, output: &str) -> Result<Value> {
self.client
.send(
Method::POST,
"/admin/backups",
Some(&json!({ "output": output })),
)
.await
}
}
impl Client {
pub async fn health(&self) -> Result<Health> {
self.send::<_, ()>(Method::GET, "/health", None).await
}
pub fn control(&self) -> Control {
Control {
client: self.clone(),
}
}
}