use super::helpers::TrustTaskOutcome;
use serde_json::Value;
use trust_tasks_rs::TrustTask;
use vta_sdk::protocols::context_management::create::CreateContextBody;
use vta_sdk::protocols::context_management::delete::{DeleteContextBody, DeleteContextPreviewBody};
use vta_sdk::protocols::context_management::get::GetContextBody;
use vta_sdk::protocols::context_management::list::ListContextsBody;
use vta_sdk::protocols::context_management::update::UpdateContextBody;
use vta_sdk::protocols::context_management::update_did::UpdateContextDidBody;
use crate::auth::AuthClaims;
use crate::operations;
use crate::server::AppState;
use super::helpers::{
TRANSPORT_TRUST_TASK, app_error_to_reject, parse_payload, reject_with_code, success_response,
};
fn slug_from_doc(doc: &TrustTask<Value>) -> String {
doc.type_uri
.to_string()
.strip_prefix("https://trusttasks.org/spec/")
.and_then(|rest| rest.rsplit_once('/'))
.map(|(slug, _ver)| slug.to_string())
.unwrap_or_else(|| "vta/contexts/delete".to_string())
}
fn ext(slug: &str, local: &str) -> trust_tasks_rs::TrustTaskCode {
trust_tasks_rs::TrustTaskCode::new_extended(slug, local)
.expect("contexts extended code is grammar-valid")
}
fn reject_context_error(
doc: &TrustTask<Value>,
e: operations::contexts::ContextError,
) -> TrustTaskOutcome {
use operations::contexts::ContextError;
let slug = slug_from_doc(doc);
match e {
ContextError::Unreachable => reject_with_code(
doc,
ext(&slug, "notFound"),
"no context with that id is reachable by this caller",
None,
),
ContextError::ParentUnreachable => reject_with_code(
doc,
ext(&slug, "parentNotFound"),
"no context with that parent id is reachable by this caller",
None,
),
ContextError::NotEmpty(holds) => reject_with_code(
doc,
ext(&slug, "notEmpty"),
format!(
"context holds {}; retry with force to delete the whole subtree, or preview it \
first",
holds.summary()
),
Some(serde_json::json!({
"subContexts": holds.sub_contexts,
"keys": holds.keys,
"webvhDids": holds.webvh_dids,
"aclEntries": holds.acl_entries,
"didTemplates": holds.did_templates,
})),
),
ContextError::Other(e) => app_error_to_reject(doc, e),
}
}
pub(super) async fn handle_list(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
let _req: ListContextsBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
match operations::contexts::list_contexts(&state.contexts_ks, auth, TRANSPORT_TRUST_TASK).await
{
Ok(body) => success_response(&doc, body),
Err(e) => app_error_to_reject(&doc, e),
}
}
pub(super) async fn handle_create(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
if let Err(e) = auth.require_admin() {
return app_error_to_reject(&doc, e);
}
let req: CreateContextBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
let audit_id = req.id.clone();
match operations::contexts::create_context(
&state.contexts_ks,
auth,
&req.id,
req.name,
req.description,
req.parent,
TRANSPORT_TRUST_TASK,
)
.await
{
Ok(body) => {
if let Err(e) = crate::audit::record_with_detail(
&state.audit_sink,
"contexts.create",
&auth.did,
Some(&audit_id),
"success",
Some(TRANSPORT_TRUST_TASK),
Some(&audit_id),
None,
)
.await
{
tracing::warn!(error = %e, "audit record failed for contexts.create");
}
success_response(&doc, body)
}
Err(e) => reject_context_error(&doc, e),
}
}
pub(super) async fn handle_get(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
let req: GetContextBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
match operations::contexts::get_context_op(
&state.contexts_ks,
auth,
&req.id,
TRANSPORT_TRUST_TASK,
)
.await
{
Ok(body) => success_response(&doc, body),
Err(e) => reject_context_error(&doc, e),
}
}
pub(super) async fn handle_secrets(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
let req: vta_sdk::protocols::context_management::secrets::GetContextSecretsBody =
match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
let deps = operations::export::ExportDeps {
keys_ks: &state.keys_ks,
contexts_ks: &state.contexts_ks,
imported_ks: &state.imported_ks,
audit: &state.audit_sink,
acl_ks: &state.acl_ks,
#[cfg(feature = "webvh")]
webvh_ks: &state.webvh_ks,
seed_store: &state.seed_store,
};
match operations::export::get_context_secrets(&deps, auth, &req.id, TRANSPORT_TRUST_TASK).await
{
Ok(bundle) => success_response(
&doc,
vta_sdk::protocols::context_management::secrets::ContextSecretsResultBody::from(bundle),
),
Err(e) => app_error_to_reject(&doc, e),
}
}
pub(super) async fn handle_update(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
if let Err(e) = auth.require_super_admin() {
return app_error_to_reject(&doc, e);
}
let req: UpdateContextBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
match operations::contexts::update_context(
&state.contexts_ks,
auth,
&req.id,
operations::contexts::UpdateContextParams {
name: req.name,
did: req.did,
description: req.description,
context_policy: req.context_policy,
},
TRANSPORT_TRUST_TASK,
)
.await
{
Ok(body) => success_response(&doc, body),
Err(e) => reject_context_error(&doc, e),
}
}
pub(super) async fn handle_update_did(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
if let Err(e) = auth.require_admin() {
return app_error_to_reject(&doc, e);
}
let req: UpdateContextDidBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
match operations::contexts::update_context_did(
&state.contexts_ks,
auth,
&req.id,
req.did,
TRANSPORT_TRUST_TASK,
)
.await
{
Ok(body) => success_response(&doc, body),
Err(e) => reject_context_error(&doc, e),
}
}
pub(super) async fn handle_preview_delete(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
if let Err(e) = auth.require_admin() {
return app_error_to_reject(&doc, e);
}
let req: DeleteContextPreviewBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
match operations::contexts::preview_delete_context(
&state.contexts_ks,
&state.keys_ks,
&state.acl_ks,
&state.did_templates_ks,
#[cfg(feature = "webvh")]
&state.webvh_ks,
auth,
&req.id,
TRANSPORT_TRUST_TASK,
)
.await
{
Ok(body) => success_response(&doc, body),
Err(e) => reject_context_error(&doc, e),
}
}
pub(super) async fn handle_delete(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
if let Err(e) = auth.require_admin() {
return app_error_to_reject(&doc, e);
}
let req: DeleteContextBody = match parse_payload(&doc) {
Ok(r) => r,
Err(resp) => return resp,
};
let ks = operations::keyspaces_from_app_state(state);
#[cfg(feature = "webvh")]
let outcome = {
let vta_did = state.config.read().await.vta_did.clone();
match state.did_resolver.as_ref() {
Some(did_resolver) => {
let deps = operations::did_webvh::WebvhDeps::from_app_state(state, did_resolver);
let cleanup = operations::contexts::ContextDidCleanup {
deps: &deps,
vta_did: vta_did.as_deref(),
};
operations::contexts::delete_context(
&ks,
auth,
&req.id,
req.force,
TRANSPORT_TRUST_TASK,
Some(&cleanup),
)
.await
}
None => {
operations::contexts::delete_context(
&ks,
auth,
&req.id,
req.force,
TRANSPORT_TRUST_TASK,
None,
)
.await
}
}
};
#[cfg(not(feature = "webvh"))]
let outcome =
operations::contexts::delete_context(&ks, auth, &req.id, req.force, TRANSPORT_TRUST_TASK)
.await;
match outcome {
Ok(body) => success_response(&doc, body),
Err(e) => reject_context_error(&doc, e),
}
}