use serde::Serialize;
use serde::de::DeserializeOwned;
use trust_tasks_rs::Payload as _;
use trust_tasks_rs::specs::rooms::blobs::chunk::v0_1 as download_chunk;
use trust_tasks_rs::specs::rooms::blobs::get::v0_1 as blob_get;
use trust_tasks_rs::specs::rooms::blobs::upload::{
abort::v0_1 as upload_abort, begin::v0_1 as upload_begin, chunk::v0_1 as upload_chunk,
commit::v0_1 as upload_commit,
};
use trust_tasks_rs::specs::rooms::info::v0_1 as room_info;
use trust_tasks_rs::specs::rooms::records::get::v0_2 as get_v2;
use trust_tasks_rs::specs::rooms::records::put::v0_2 as put_v2;
use super::{RoomSession, SealedContent};
use crate::{VtcClient, VtcError};
pub use blob_get::Response as BlobGetResponse;
pub use download_chunk::Response as DownloadChunkResponse;
pub use get_v2::Response as GetRecordV2Response;
pub use put_v2::Response as PutRecordV2Response;
pub use room_info::Response as RoomInfoResponse;
pub use upload_begin::Response as BeginUploadResponse;
pub use upload_chunk::Response as UploadChunkResponse;
pub use upload_commit::Response as CommitUploadResponse;
fn payload<P: DeserializeOwned + Serialize>(
value: serde_json::Value,
) -> Result<serde_json::Value, VtcError> {
let typed: P = serde_json::from_value(value)
.map_err(|e| VtcError::Url(format!("payload refused by its schema: {e}")))?;
serde_json::to_value(typed).map_err(|e| VtcError::Url(e.to_string()))
}
fn response<R: DeserializeOwned>(type_uri: &str, value: serde_json::Value) -> Result<R, VtcError> {
serde_json::from_value(value).map_err(|e| VtcError::Http {
status: 200,
body: format!("{type_uri} response does not match its schema: {e}"),
})
}
fn b64url(bytes: &[u8]) -> String {
base64::Engine::encode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, bytes)
}
impl VtcClient {
pub async fn begin_upload<M: Serialize>(
&self,
session: &RoomSession,
manifest: &M,
signer_did: &str,
private_key_multibase: &str,
) -> Result<BeginUploadResponse, VtcError> {
let p = payload::<upload_begin::Payload>(serde_json::json!({
"roomId": session.room_id,
"manifest": manifest,
"presentation": session.presentation,
}))?;
let uri = upload_begin::Payload::TYPE_URI;
let v = self
.room_task(uri, p, signer_did, private_key_multibase)
.await?;
response(uri, v)
}
pub async fn upload_chunk(
&self,
upload_id: &str,
index: u64,
chunk: &[u8],
signer_did: &str,
private_key_multibase: &str,
) -> Result<UploadChunkResponse, VtcError> {
let p = payload::<upload_chunk::Payload>(serde_json::json!({
"uploadId": upload_id,
"index": index,
"data": b64url(chunk),
"digestMultibase": vti_rooms::files::digest_multibase(chunk),
}))?;
let uri = upload_chunk::Payload::TYPE_URI;
let v = self
.room_task(uri, p, signer_did, private_key_multibase)
.await?;
response(uri, v)
}
pub async fn commit_upload(
&self,
upload_id: &str,
signer_did: &str,
private_key_multibase: &str,
) -> Result<CommitUploadResponse, VtcError> {
let p = payload::<upload_commit::Payload>(serde_json::json!({ "uploadId": upload_id }))?;
let uri = upload_commit::Payload::TYPE_URI;
let v = self
.room_task(uri, p, signer_did, private_key_multibase)
.await?;
response(uri, v)
}
pub async fn abort_upload(
&self,
upload_id: &str,
signer_did: &str,
private_key_multibase: &str,
) -> Result<bool, VtcError> {
let p = payload::<upload_abort::Payload>(serde_json::json!({ "uploadId": upload_id }))?;
let uri = upload_abort::Payload::TYPE_URI;
let v = self
.room_task(uri, p, signer_did, private_key_multibase)
.await?;
let r: upload_abort::Response = response(uri, v)?;
Ok(r.aborted)
}
pub async fn get_blob(
&self,
session: &RoomSession,
blob_ref: &str,
signer_did: &str,
private_key_multibase: &str,
) -> Result<BlobGetResponse, VtcError> {
let p = payload::<blob_get::Payload>(serde_json::json!({
"roomId": session.room_id,
"blobRef": blob_ref,
"presentation": session.presentation,
}))?;
let uri = blob_get::Payload::TYPE_URI;
let v = self
.room_task(uri, p, signer_did, private_key_multibase)
.await?;
response(uri, v)
}
pub async fn download_chunk(
&self,
download_id: &str,
index: u64,
signer_did: &str,
private_key_multibase: &str,
) -> Result<Vec<u8>, VtcError> {
let p = payload::<download_chunk::Payload>(serde_json::json!({
"downloadId": download_id,
"index": index,
}))?;
let uri = download_chunk::Payload::TYPE_URI;
let v = self
.room_task(uri, p, signer_did, private_key_multibase)
.await?;
let r: DownloadChunkResponse = response(uri, v)?;
base64::Engine::decode(
&base64::engine::general_purpose::URL_SAFE_NO_PAD,
r.data.as_str(),
)
.map_err(|e| VtcError::Http {
status: 200,
body: format!("chunk {index} is not base64url: {e}"),
})
}
pub async fn room_info(
&self,
session: &RoomSession,
signer_did: &str,
private_key_multibase: &str,
) -> Result<RoomInfoResponse, VtcError> {
let p = payload::<room_info::Payload>(serde_json::json!({
"roomId": session.room_id,
"presentation": session.presentation,
}))?;
let uri = room_info::Payload::TYPE_URI;
let v = self
.room_task(uri, p, signer_did, private_key_multibase)
.await?;
response(uri, v)
}
pub async fn get_record_v2(
&self,
session: &RoomSession,
key: &str,
signer_did: &str,
private_key_multibase: &str,
) -> Result<GetRecordV2Response, VtcError> {
let p = payload::<get_v2::Payload>(serde_json::json!({
"roomId": session.room_id,
"key": key,
"presentation": session.presentation,
}))?;
let uri = get_v2::Payload::TYPE_URI;
let v = self
.room_task(uri, p, signer_did, private_key_multibase)
.await?;
response(uri, v)
}
#[allow(clippy::too_many_arguments)]
pub async fn put_sealed_record_with_blobs(
&self,
session: &RoomSession,
key: &str,
sealed: SealedContent,
blobs: &[String],
expected_version: Option<u64>,
signer_did: &str,
private_key_multibase: &str,
) -> Result<PutRecordV2Response, VtcError> {
let mut value = serde_json::json!({
"roomId": session.room_id,
"key": key,
"presentation": session.presentation,
"sealed": sealed,
});
if !blobs.is_empty() {
value["blobs"] = serde_json::json!(blobs);
}
if let Some(v) = expected_version {
value["expectedVersion"] = serde_json::json!(v);
}
let p = payload::<put_v2::Payload>(value)?;
let uri = put_v2::Payload::TYPE_URI;
let v = self
.room_task(uri, p, signer_did, private_key_multibase)
.await?;
response(uri, v)
}
}