use super::*;
use async_trait::async_trait;
use std::time::Duration;
#[derive(Clone, Debug)]
pub(super) struct WorkspaceOperationPoller {
pub(super) transport: HttpTransport,
}
#[async_trait]
impl OperationPoller<WorkspaceOperation> for WorkspaceOperationPoller {
async fn poll(&self, operation_id: &str) -> Result<WorkspaceOperation, InfraClientError> {
self.transport
.get_json(&expand(WORKSPACE_OPERATIONS_PATH, &[operation_id]))
.await
}
async fn cancel(&self, operation_id: &str) -> Result<(), InfraClientError> {
let operation: WorkspaceOperation = self.poll(operation_id).await?;
let _: WorkspaceOperation = self
.transport
.post_json_with_options(
&expand(WORKSPACE_OPERATION_CANCEL_PATH, &[operation_id]),
&WorkspaceActionRequest {
expected_version: operation.version,
reason: None,
},
CallOptions::default(),
)
.await?;
Ok(())
}
fn observe(&self, operation: &WorkspaceOperation) -> OperationObservation {
let progress = match operation.state {
OperationState::Succeeded => OperationProgress::Succeeded,
OperationState::Failed => OperationProgress::Failed,
OperationState::Canceled => OperationProgress::Canceled,
OperationState::Queued | OperationState::Running | OperationState::Canceling => {
OperationProgress::Pending
}
};
OperationObservation {
progress,
next_poll_after: operation.next_poll_after_ms.map(Duration::from_millis),
error_code: operation.error_code.clone(),
}
}
}
pub(super) fn ensure_ack(response: AckResponse) -> Result<Option<String>, InfraClientError> {
if response.ok {
Ok(response.revision)
} else {
Err(InfraClientError::Protocol {
service: WORKSPACE_SERVICE_NAME,
message: "operation returned ok=false".into(),
})
}
}
pub(super) fn expand(template: &str, values: &[&str]) -> String {
let mut result = template.to_string();
for value in values {
let Some(start) = result.find('{') else {
break;
};
let Some(relative_end) = result[start..].find('}') else {
break;
};
result.replace_range(start..=start + relative_end, &encode_path_segment(value));
}
result
}
pub(super) fn resource_file_path(workspace_id: &str, path: &str) -> String {
let encoded_path = path
.trim_start_matches('/')
.split('/')
.map(encode_path_segment)
.collect::<Vec<_>>()
.join("/");
expand(WORKSPACE_RESOURCE_FILE_PATH, &[workspace_id]).replace("{*filePath}", &encoded_path)
}
pub(super) fn encode_path_segment(value: &str) -> String {
let mut encoded = String::with_capacity(value.len());
for byte in value.bytes() {
if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~') {
encoded.push(char::from(byte));
} else {
use std::fmt::Write as _;
write!(&mut encoded, "%{byte:02X}").expect("writing to String cannot fail");
}
}
encoded
}