use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json::Value;
use crate::{HolderKey, VtcClient, VtcError};
pub use trust_tasks_rs::specs::git_ns as specs;
use specs::account::{link::v0_1 as link, link_status::v0_1 as link_status};
use specs::drift::resolve::v0_1 as drift_resolve;
use specs::namespace::{bind::v0_1 as bind, reseat::v0_2 as reseat, unbind::v0_1 as unbind};
pub const RESEAT_TYPE_URI: &str = "https://trusttasks.org/spec/git-ns/namespace/reseat/0.3";
use specs::repo::{
adopt::v0_1 as adopt, archive::v0_1 as archive, create::v0_1 as create,
transfer::v0_1 as transfer,
};
use specs::right::{grant::v0_1 as grant, revoke::v0_1 as revoke};
use specs::view::{v0_1 as view, v0_2 as view2};
pub const GIT_NS_VIEW_TYPE: &str = <view::Payload as trust_tasks_rs::Payload>::TYPE_URI;
impl VtcClient {
pub async fn git_ns_task<P: Serialize, R: DeserializeOwned>(
&self,
type_uri: &str,
payload: &P,
key: &HolderKey,
) -> Result<R, VtcError> {
let payload = serde_json::to_value(payload)
.map_err(|e| VtcError::Url(format!("serialise {type_uri} payload: {e}")))?;
let doc =
vta_sdk::trust_task_sign::build_signed_with(type_uri, payload, key, &self.vtc_did)
.await
.map_err(|e| VtcError::Signing(e.to_string()))?;
if self.base_url.is_empty() {
return Err(VtcError::NoRestTransport("a git-ns task"));
}
let resp = self
.http
.post(format!("{}/trust-tasks", self.base_url))
.header("content-type", "application/json")
.body(doc)
.send()
.await?;
let status = resp.status().as_u16();
let text = resp.text().await?;
let doc: trust_tasks_rs::TrustTask<Value> =
serde_json::from_str(&text).map_err(|e| VtcError::Http {
status,
body: format!("not a Trust Task document ({e}): {text}"),
})?;
if doc.type_uri.slug() == "trust-task-error" || !(200..300).contains(&status) {
return Err(VtcError::Http { status, body: text });
}
serde_json::from_value(doc.payload).map_err(|e| VtcError::Http {
status,
body: format!("{type_uri} response does not fit its schema: {e}"),
})
}
pub async fn git_ns_bind(
&self,
forge: &str,
owner: &str,
mode: &str,
key: &HolderKey,
) -> Result<bind::Response, VtcError> {
let payload = serde_json::json!({ "forge": forge, "owner": owner, "mode": mode });
self.git_ns_task(
<bind::Payload as trust_tasks_rs::Payload>::TYPE_URI,
&payload,
key,
)
.await
}
pub async fn git_ns_unbind(
&self,
namespace: &str,
key: &HolderKey,
) -> Result<unbind::Response, VtcError> {
let payload = serde_json::json!({ "namespace": namespace });
self.git_ns_task(
<unbind::Payload as trust_tasks_rs::Payload>::TYPE_URI,
&payload,
key,
)
.await
}
pub async fn git_ns_create_repo(
&self,
payload: &create::Payload,
key: &HolderKey,
) -> Result<create::Response, VtcError> {
self.git_ns_task(
<create::Payload as trust_tasks_rs::Payload>::TYPE_URI,
payload,
key,
)
.await
}
pub async fn git_ns_adopt(
&self,
resource: &str,
owners: &[String],
key: &HolderKey,
) -> Result<adopt::Response, VtcError> {
let payload = serde_json::json!({ "resource": resource, "owners": owners });
self.git_ns_task(
<adopt::Payload as trust_tasks_rs::Payload>::TYPE_URI,
&payload,
key,
)
.await
}
pub async fn git_ns_transfer(
&self,
resource: &str,
to: &str,
key: &HolderKey,
) -> Result<transfer::Response, VtcError> {
let payload = serde_json::json!({ "resource": resource, "to": to });
self.git_ns_task(
<transfer::Payload as trust_tasks_rs::Payload>::TYPE_URI,
&payload,
key,
)
.await
}
pub async fn git_ns_archive(
&self,
resource: &str,
key: &HolderKey,
) -> Result<archive::Response, VtcError> {
let payload = serde_json::json!({ "resource": resource });
self.git_ns_task(
<archive::Payload as trust_tasks_rs::Payload>::TYPE_URI,
&payload,
key,
)
.await
}
pub async fn git_ns_grant(
&self,
payload: &grant::Payload,
key: &HolderKey,
) -> Result<grant::Response, VtcError> {
self.git_ns_task(
<grant::Payload as trust_tasks_rs::Payload>::TYPE_URI,
payload,
key,
)
.await
}
pub async fn git_ns_revoke(
&self,
payload: &revoke::Payload,
key: &HolderKey,
) -> Result<revoke::Response, VtcError> {
self.git_ns_task(
<revoke::Payload as trust_tasks_rs::Payload>::TYPE_URI,
payload,
key,
)
.await
}
pub async fn git_ns_view(
&self,
resource: Option<&str>,
key: &HolderKey,
) -> Result<view::Response, VtcError> {
let payload = match resource {
Some(r) => serde_json::json!({ "resource": r }),
None => serde_json::json!({}),
};
self.git_ns_task(GIT_NS_VIEW_TYPE, &payload, key).await
}
pub async fn git_ns_view_v2(
&self,
resource: Option<&str>,
key: &HolderKey,
) -> Result<view2::Response, VtcError> {
let payload = match resource {
Some(r) => serde_json::json!({ "resource": r }),
None => serde_json::json!({}),
};
self.git_ns_task(
<view2::Payload as trust_tasks_rs::Payload>::TYPE_URI,
&payload,
key,
)
.await
}
pub async fn git_ns_drift_resolve(
&self,
payload: &drift_resolve::Payload,
key: &HolderKey,
) -> Result<drift_resolve::Response, VtcError> {
self.git_ns_task(
<drift_resolve::Payload as trust_tasks_rs::Payload>::TYPE_URI,
payload,
key,
)
.await
}
pub async fn git_ns_reseat(
&self,
namespace: &str,
subject: &str,
statement: &str,
key: &HolderKey,
) -> Result<reseat::Response, VtcError> {
let payload = serde_json::json!({
"namespace": namespace,
"subject": subject,
"statement": statement,
});
self.git_ns_task(RESEAT_TYPE_URI, &payload, key).await
}
pub async fn git_ns_link_account(
&self,
forge: &str,
key: &HolderKey,
) -> Result<link::Response, VtcError> {
let payload = serde_json::json!({ "forge": forge });
self.git_ns_task(
<link::Payload as trust_tasks_rs::Payload>::TYPE_URI,
&payload,
key,
)
.await
}
pub async fn git_ns_link_status(
&self,
link_id: &str,
key: &HolderKey,
) -> Result<link_status::Response, VtcError> {
let payload = serde_json::json!({ "linkId": link_id });
self.git_ns_task(
<link_status::Payload as trust_tasks_rs::Payload>::TYPE_URI,
&payload,
key,
)
.await
}
pub async fn git_ns_namespaces(&self) -> Result<Value, VtcError> {
self.git_ns_get(&["git-ns", "namespaces"], &[]).await
}
pub async fn git_ns_repos(&self, namespace: Option<&str>) -> Result<Value, VtcError> {
let query: Vec<(&str, &str)> = namespace.map(|n| ("namespace", n)).into_iter().collect();
self.git_ns_get(&["git-ns", "repos"], &query).await
}
pub async fn git_ns_admin_view(&self, resource: Option<&str>) -> Result<Value, VtcError> {
let query: Vec<(&str, &str)> = resource.map(|r| ("resource", r)).into_iter().collect();
self.git_ns_get(&["git-ns", "view"], &query).await
}
async fn git_ns_get(
&self,
segments: &[&str],
query: &[(&str, &str)],
) -> Result<Value, VtcError> {
let mut url = self.api_url(segments)?;
for (k, v) in query {
url.query_pairs_mut().append_pair(k, v);
}
let resp = self
.tt(reqwest::Method::GET, url, GIT_NS_VIEW_TYPE)?
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body = resp.text().await.unwrap_or_default();
return Err(VtcError::Http { status, body });
}
Ok(resp.json().await?)
}
}
pub fn task_error(err: &VtcError) -> Option<(String, String)> {
let VtcError::Http { body, .. } = err else {
return None;
};
let doc: Value = serde_json::from_str(body).ok()?;
let code = doc.pointer("/payload/code")?.as_str()?.to_string();
let message = doc
.pointer("/payload/message")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string();
Some((code, message))
}