use super::*;
use crate::CommandSpec;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub enum EnvironmentIdempotencyState {
Pending,
Completed,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct EnvironmentIdempotencyRecord {
pub id: String,
pub scope: ResourceScope,
pub action: String,
pub key_hash: String,
pub request_hash: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource_id: Option<String>,
pub state: EnvironmentIdempotencyState,
pub claim_owner: String,
pub claim_expires_at_ms: u64,
pub attempt: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub result_json: Option<String>,
pub started_at_ms: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub completed_at_ms: Option<u64>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct EnvironmentMetadataSnapshot {
pub version: u64,
#[serde(default)]
pub computers: Vec<ComputerRecord>,
#[serde(default)]
pub spaces: Vec<SpaceRecord>,
#[serde(default)]
pub sandboxes: Vec<SandboxRecord>,
#[serde(default)]
pub snapshots: Vec<SnapshotRecord>,
#[serde(default)]
pub templates: Vec<TemplateVersionRecord>,
#[serde(default)]
pub grants: Vec<SpaceGrant>,
#[serde(default)]
pub sessions: Vec<SpaceCollaborationSession>,
#[serde(default)]
pub tickets: Vec<SpaceAccessTicketRecord>,
#[serde(default)]
pub leases: Vec<SpaceWriteLease>,
#[serde(default)]
pub fencing_tokens: BTreeMap<String, u64>,
#[serde(default)]
pub delegations: Vec<DelegationRecord>,
#[serde(default)]
pub change_sets: Vec<SpaceChangeSetRecord>,
#[serde(default)]
pub idempotency: Vec<EnvironmentIdempotencyRecord>,
}
#[async_trait]
pub trait EnvironmentMetadataRepository: Send + Sync + Debug {
async fn load(&self, scope: &ResourceScope) -> Result<EnvironmentMetadataSnapshot>;
async fn list_scopes(
&self,
after: Option<&ResourceScope>,
limit: usize,
) -> Result<Vec<ResourceScope>>;
async fn compare_and_swap(
&self,
scope: &ResourceScope,
expected_version: u64,
snapshot: &EnvironmentMetadataSnapshot,
) -> Result<bool>;
}
#[async_trait]
pub trait ComputerProvider: Send + Sync + Debug {
fn kind(&self) -> &str;
fn capabilities(&self) -> ProviderCapabilities;
fn validate_create(&self, _request: &ProviderComputerRequest) -> Result<()> {
Ok(())
}
async fn create_computer(
&self,
scope: &ResourceScope,
request: &ProviderComputerRequest,
idempotency_key: &str,
) -> Result<ProviderResourceRef>;
async fn delete_computer(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
async fn create_space_directory(
&self,
computer_external_id: &str,
space_directory: &str,
request: &ProviderSpaceRequest,
idempotency_key: &str,
) -> Result<()> {
let _ = (
computer_external_id,
space_directory,
request,
idempotency_key,
);
Err(EnvironmentDomainError::new(
EnvironmentErrorCode::CapabilityUnsupported,
"computer does not support Space directories",
)
.into())
}
async fn delete_space_directory(
&self,
computer_external_id: &str,
space_directory: &str,
idempotency_key: &str,
) -> Result<()> {
let _ = (computer_external_id, space_directory, idempotency_key);
Err(EnvironmentDomainError::new(
EnvironmentErrorCode::CapabilityUnsupported,
"computer does not support Space directories",
)
.into())
}
async fn read_file(
&self,
computer_external_id: &str,
space_directory: &str,
path: &str,
) -> Result<ProviderFileContent> {
let _ = (computer_external_id, space_directory, path);
Err(EnvironmentDomainError::new(
EnvironmentErrorCode::CapabilityUnsupported,
"computer does not support file reads",
)
.into())
}
async fn write_file(
&self,
computer_external_id: &str,
space_directory: &str,
path: &str,
content: &[u8],
expected_provider_revision: Option<&str>,
idempotency_key: &str,
) -> Result<ProviderWriteResult> {
let _ = (
computer_external_id,
space_directory,
path,
content,
expected_provider_revision,
idempotency_key,
);
Err(EnvironmentDomainError::new(
EnvironmentErrorCode::CapabilityUnsupported,
"computer does not support file writes",
)
.into())
}
async fn create_change_set(
&self,
computer_external_id: &str,
source_space_directory: &str,
target_space_directory: &str,
idempotency_key: &str,
) -> Result<String> {
let _ = (
computer_external_id,
source_space_directory,
target_space_directory,
idempotency_key,
);
Err(EnvironmentDomainError::new(
EnvironmentErrorCode::CapabilityUnsupported,
"computer does not support change sets",
)
.into())
}
async fn apply_change_set(
&self,
computer_external_id: &str,
source_space_directory: &str,
target_space_directory: &str,
provider_change_ref: &str,
idempotency_key: &str,
) -> Result<ProviderWriteResult> {
let _ = (
computer_external_id,
source_space_directory,
target_space_directory,
provider_change_ref,
idempotency_key,
);
Err(EnvironmentDomainError::new(
EnvironmentErrorCode::CapabilityUnsupported,
"computer does not support change sets",
)
.into())
}
async fn exec_command(
&self,
computer_external_id: &str,
space_directory: &str,
command: &CommandSpec,
) -> Result<ProviderCommandOutput> {
let _ = (computer_external_id, space_directory, command);
Err(EnvironmentDomainError::new(
EnvironmentErrorCode::CapabilityUnsupported,
"computer does not support managed commands",
)
.into())
}
}
#[async_trait]
pub trait SandboxProvider: Send + Sync + Debug {
fn kind(&self) -> &str;
fn capabilities(&self) -> ProviderCapabilities;
fn validate_create(&self, _request: &ProviderSandboxRequest) -> Result<()> {
Ok(())
}
async fn create_sandbox(
&self,
scope: &ResourceScope,
request: &ProviderSandboxRequest,
idempotency_key: &str,
) -> Result<ProviderResourceRef>;
async fn stop_sandbox(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
async fn write_file(
&self,
external_id: &str,
path: &str,
content: &[u8],
expected_provider_revision: Option<&str>,
idempotency_key: &str,
) -> Result<ProviderWriteResult> {
let _ = (
external_id,
path,
content,
expected_provider_revision,
idempotency_key,
);
Err(EnvironmentDomainError::new(
EnvironmentErrorCode::CapabilityUnsupported,
"sandbox does not support file writes",
)
.into())
}
async fn exec_command(
&self,
external_id: &str,
command: &CommandSpec,
) -> Result<ProviderCommandOutput> {
let _ = (external_id, command);
Err(EnvironmentDomainError::new(
EnvironmentErrorCode::CapabilityUnsupported,
"sandbox does not support managed commands",
)
.into())
}
}
#[async_trait]
pub trait SpaceProvider: Send + Sync + Debug {
fn kind(&self) -> &str;
fn capabilities(&self) -> ProviderCapabilities;
async fn create_space(
&self,
scope: &ResourceScope,
request: &ProviderSpaceRequest,
idempotency_key: &str,
) -> Result<ProviderResourceRef>;
async fn read_file(&self, external_id: &str, path: &str) -> Result<ProviderFileContent>;
async fn write_file(
&self,
external_id: &str,
path: &str,
content: &[u8],
expected_provider_revision: Option<&str>,
idempotency_key: &str,
) -> Result<ProviderWriteResult>;
async fn create_change_set(
&self,
source_external_id: &str,
target_external_id: &str,
idempotency_key: &str,
) -> Result<String>;
async fn apply_change_set(
&self,
source_external_id: &str,
target_external_id: &str,
provider_change_ref: &str,
idempotency_key: &str,
) -> Result<ProviderWriteResult>;
async fn delete_space(&self, external_id: &str, idempotency_key: &str) -> Result<()>;
}
#[async_trait]
pub trait SnapshotProviderPort: Send + Sync + Debug {
fn kind(&self) -> &str;
async fn capture_snapshot(
&self,
source: &ProviderSnapshotSource,
idempotency_key: &str,
) -> Result<ProviderResourceRef>;
async fn delete_snapshot(
&self,
snapshot_external_id: &str,
idempotency_key: &str,
) -> Result<()>;
}
#[async_trait]
pub trait TemplateProviderPort: Send + Sync + Debug {
fn kind(&self) -> &str;
async fn inspect_template(&self, external_id: &str) -> Result<ProviderCapabilities>;
}
pub trait EnvironmentProviderRegistry: Send + Sync + Debug {
fn computer(&self, provider: &str) -> Result<Arc<dyn ComputerProvider>>;
fn sandbox(&self, provider: &str) -> Result<Arc<dyn SandboxProvider>>;
fn space(&self, provider: &str) -> Result<Arc<dyn SpaceProvider>>;
fn snapshot(&self, provider: &str) -> Result<Arc<dyn SnapshotProviderPort>>;
fn template(&self, provider: &str) -> Result<Arc<dyn TemplateProviderPort>>;
}