170 lines
5.5 KiB
Rust
170 lines
5.5 KiB
Rust
use std::collections::BTreeSet;
|
|
|
|
use crank_core::WorkspaceId;
|
|
use crank_registry::{ArtifactSourceId, DetachArtifactSourceRequest, ImportJobSourceEnvelope};
|
|
use time::OffsetDateTime;
|
|
|
|
use crate::{error::ApiError, service::OpenApiUploadLocale};
|
|
|
|
use super::canonical_external_document_uri;
|
|
|
|
const MAX_IMPORT_JOB_DOCUMENTS: usize = 32;
|
|
|
|
pub(super) fn import_job_source(
|
|
payload: &serde_json::Value,
|
|
locale: OpenApiUploadLocale,
|
|
) -> Result<ImportJobSourceEnvelope, ApiError> {
|
|
let source = payload
|
|
.get("source")
|
|
.ok_or_else(|| ApiError::openapi_upload(locale, "source_unavailable"))?;
|
|
let source_id = source
|
|
.get("source_id")
|
|
.and_then(serde_json::Value::as_str)
|
|
.filter(|value| value.len() <= 132)
|
|
.ok_or_else(|| ApiError::openapi_upload(locale, "source_unavailable"))?;
|
|
let digest = source
|
|
.get("digest")
|
|
.and_then(serde_json::Value::as_str)
|
|
.and_then(|value| value.parse().ok())
|
|
.ok_or_else(|| ApiError::openapi_upload(locale, "source_integrity"))?;
|
|
Ok(ImportJobSourceEnvelope {
|
|
source_id: ArtifactSourceId::new(source_id),
|
|
digest,
|
|
})
|
|
}
|
|
|
|
pub(super) fn import_job_dependencies(
|
|
payload: &serde_json::Value,
|
|
locale: OpenApiUploadLocale,
|
|
) -> Result<Vec<(String, ImportJobSourceEnvelope)>, ApiError> {
|
|
let empty = Vec::new();
|
|
let items = payload
|
|
.get("dependency_snapshots")
|
|
.and_then(serde_json::Value::as_array)
|
|
.unwrap_or(&empty);
|
|
if items.len() > MAX_IMPORT_JOB_DOCUMENTS {
|
|
return Err(ApiError::openapi_upload(locale, "source_integrity"));
|
|
}
|
|
let mut canonical_uris = BTreeSet::new();
|
|
items
|
|
.iter()
|
|
.map(|item| {
|
|
let canonical_uri = item
|
|
.get("canonical_uri")
|
|
.and_then(serde_json::Value::as_str)
|
|
.filter(|value| {
|
|
canonical_external_document_uri(None, value).as_deref() == Some(*value)
|
|
})
|
|
.ok_or_else(|| ApiError::openapi_upload(locale, "source_integrity"))?;
|
|
if !canonical_uris.insert(canonical_uri.to_owned()) {
|
|
return Err(ApiError::openapi_upload(locale, "source_integrity"));
|
|
}
|
|
let source_id = item
|
|
.get("source_id")
|
|
.and_then(serde_json::Value::as_str)
|
|
.filter(|value| value.len() <= 132)
|
|
.ok_or_else(|| ApiError::openapi_upload(locale, "source_integrity"))?;
|
|
let digest = item
|
|
.get("digest")
|
|
.and_then(serde_json::Value::as_str)
|
|
.and_then(|value| value.parse().ok())
|
|
.ok_or_else(|| ApiError::openapi_upload(locale, "source_integrity"))?;
|
|
Ok((
|
|
canonical_uri.to_owned(),
|
|
ImportJobSourceEnvelope {
|
|
source_id: ArtifactSourceId::new(source_id),
|
|
digest,
|
|
},
|
|
))
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub(super) fn import_job_normalization_config(
|
|
payload: &serde_json::Value,
|
|
current: &crank_import::rest::NormalizationConfig,
|
|
legacy_v1: bool,
|
|
locale: OpenApiUploadLocale,
|
|
) -> Result<crank_import::rest::NormalizationConfig, ApiError> {
|
|
if legacy_v1 {
|
|
return Ok(current.clone());
|
|
}
|
|
payload
|
|
.pointer("/normalization/config")
|
|
.cloned()
|
|
.ok_or_else(|| ApiError::openapi_upload(locale, "source_integrity"))
|
|
.and_then(|value| {
|
|
serde_json::from_value(value)
|
|
.map_err(|_| ApiError::openapi_upload(locale, "source_integrity"))
|
|
})
|
|
}
|
|
|
|
pub(super) struct SourceDetachGuard {
|
|
registry: crank_registry::PostgresRegistry,
|
|
workspace_id: WorkspaceId,
|
|
source_id: ArtifactSourceId,
|
|
expected_updated_at: OffsetDateTime,
|
|
armed: bool,
|
|
}
|
|
|
|
impl SourceDetachGuard {
|
|
pub(super) fn new(
|
|
registry: crank_registry::PostgresRegistry,
|
|
workspace_id: WorkspaceId,
|
|
source_id: ArtifactSourceId,
|
|
expected_updated_at: OffsetDateTime,
|
|
) -> Self {
|
|
Self {
|
|
registry,
|
|
workspace_id,
|
|
source_id,
|
|
expected_updated_at,
|
|
armed: true,
|
|
}
|
|
}
|
|
|
|
pub(super) fn disarm(&mut self) {
|
|
self.armed = false;
|
|
}
|
|
|
|
pub(super) async fn detach_now(&mut self) {
|
|
if !self.armed {
|
|
return;
|
|
}
|
|
self.armed = false;
|
|
let _ = self
|
|
.registry
|
|
.detach_artifact_source(DetachArtifactSourceRequest {
|
|
workspace_id: &self.workspace_id,
|
|
source_id: &self.source_id,
|
|
expected_updated_at: Some(self.expected_updated_at),
|
|
detached_at: OffsetDateTime::now_utc(),
|
|
})
|
|
.await;
|
|
}
|
|
}
|
|
|
|
impl Drop for SourceDetachGuard {
|
|
fn drop(&mut self) {
|
|
if !self.armed {
|
|
return;
|
|
}
|
|
let registry = self.registry.clone();
|
|
let workspace_id = self.workspace_id.clone();
|
|
let source_id = self.source_id.clone();
|
|
let expected_updated_at = Some(self.expected_updated_at);
|
|
if let Ok(handle) = tokio::runtime::Handle::try_current() {
|
|
handle.spawn(async move {
|
|
let _ = registry
|
|
.detach_artifact_source(DetachArtifactSourceRequest {
|
|
workspace_id: &workspace_id,
|
|
source_id: &source_id,
|
|
expected_updated_at,
|
|
detached_at: OffsetDateTime::now_utc(),
|
|
})
|
|
.await;
|
|
});
|
|
}
|
|
}
|
|
}
|