Migrate schema 2 ports through frozen readers and expose schema 3 source API
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
//! Single-owner on-disk envelopes. All methods run on a blocking worker.
|
||||
use crate::error::{ApiError, ApiResult, issues};
|
||||
use axum::http::StatusCode;
|
||||
use oa_core::persistence::{DecodeResult, MigrationReport};
|
||||
use oa_core::{Id, Project};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
@@ -29,6 +30,8 @@ pub struct StoredProject {
|
||||
pub resource_id: Id,
|
||||
pub revision: u64,
|
||||
pub model: Project,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub migration: Option<MigrationReport>,
|
||||
}
|
||||
|
||||
impl StoredProject {
|
||||
@@ -46,6 +49,7 @@ pub(crate) struct ProjectSummary {
|
||||
pub name: String,
|
||||
pub elements: usize,
|
||||
pub relations: usize,
|
||||
pub migration: Option<MigrationReport>,
|
||||
}
|
||||
impl From<&StoredProject> for ProjectSummary {
|
||||
fn from(stored: &StoredProject) -> Self {
|
||||
@@ -57,6 +61,7 @@ impl From<&StoredProject> for ProjectSummary {
|
||||
name: stored.model.name.clone(),
|
||||
elements: stored.model.elements.len(),
|
||||
relations: stored.model.relations.len(),
|
||||
migration: stored.migration.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,11 +93,11 @@ struct StoreInner {
|
||||
|
||||
/// Constructed only after complete candidate validation, outside the catalog
|
||||
/// mutex. Commit methods cannot accidentally accept an unchecked candidate.
|
||||
struct PreparedModel(Project);
|
||||
struct PreparedModel(Project, Option<MigrationReport>);
|
||||
impl PreparedModel {
|
||||
fn new(model: Project) -> ApiResult<Self> {
|
||||
validate_model(&model)?;
|
||||
Ok(Self(model))
|
||||
Ok(Self(model, None))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +134,22 @@ impl Store {
|
||||
self.lock()?.create(prepared)
|
||||
}
|
||||
|
||||
pub(crate) fn create_decoded(&self, decoded: DecodeResult) -> ApiResult<StoredProject> {
|
||||
let mut prepared = PreparedModel::new(decoded.project)?;
|
||||
prepared.1 = decoded.migration;
|
||||
self.lock()?.create(prepared)
|
||||
}
|
||||
pub(crate) fn replace_decoded(
|
||||
&self,
|
||||
id: Id,
|
||||
expected: &str,
|
||||
decoded: DecodeResult,
|
||||
) -> ApiResult<(StoredProject, bool)> {
|
||||
let mut prepared = PreparedModel::new(decoded.project)?;
|
||||
prepared.1 = decoded.migration;
|
||||
self.lock()?.replace(id, expected, prepared)
|
||||
}
|
||||
|
||||
pub(crate) fn replace(
|
||||
&self,
|
||||
id: Id,
|
||||
@@ -201,8 +222,26 @@ impl Store {
|
||||
"At most 128 models and 256 MiB of serialized envelopes are supported.".into(),
|
||||
));
|
||||
}
|
||||
let stored: StoredProject =
|
||||
#[derive(Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Envelope {
|
||||
resource_id: Id,
|
||||
revision: u64,
|
||||
model: Box<serde_json::value::RawValue>,
|
||||
}
|
||||
let envelope: Envelope =
|
||||
serde_json::from_slice(&bytes).map_err(|error| corrupt(error.to_string()))?;
|
||||
let decoded = oa_core::persistence::decode_with_report_limited(
|
||||
envelope.model.get().as_bytes(),
|
||||
MODEL_LIMITS,
|
||||
)
|
||||
.map_err(|error| corrupt(error.to_string()))?;
|
||||
let stored = StoredProject {
|
||||
resource_id: envelope.resource_id,
|
||||
revision: envelope.revision,
|
||||
model: decoded.project,
|
||||
migration: decoded.migration,
|
||||
};
|
||||
if stored.resource_id != filename_id || stored.revision == 0 {
|
||||
return Err(corrupt(
|
||||
"Resource ID/filename mismatch or zero revision.".into(),
|
||||
@@ -254,6 +293,7 @@ impl StoreInner {
|
||||
resource_id: Id::new_v4(),
|
||||
revision: 1,
|
||||
model,
|
||||
migration: prepared.1,
|
||||
};
|
||||
let size = self.write(&stored, true)?;
|
||||
self.adopt(stored.clone(), size);
|
||||
@@ -270,7 +310,11 @@ impl StoreInner {
|
||||
let current = self.checked(id, expected)?;
|
||||
let model = prepared.0;
|
||||
if current.model == model {
|
||||
return Ok((current.clone(), false));
|
||||
let mut unchanged = current.clone();
|
||||
if prepared.1.is_some() {
|
||||
unchanged.migration = prepared.1;
|
||||
}
|
||||
return Ok((unchanged, false));
|
||||
}
|
||||
let revision = current.revision.checked_add(1).ok_or_else(|| {
|
||||
ApiError::new(
|
||||
@@ -283,6 +327,7 @@ impl StoreInner {
|
||||
resource_id: id,
|
||||
revision,
|
||||
model,
|
||||
migration: prepared.1,
|
||||
};
|
||||
let size = self.write(&stored, false)?;
|
||||
self.adopt(stored.clone(), size);
|
||||
@@ -312,7 +357,9 @@ impl StoreInner {
|
||||
}
|
||||
|
||||
fn write(&self, stored: &StoredProject, create: bool) -> ApiResult<usize> {
|
||||
let bytes = serde_json::to_vec(stored)
|
||||
// Migration observations are response metadata, never persisted facts.
|
||||
let envelope = serde_json::json!({"resource_id": stored.resource_id, "revision": stored.revision, "model": stored.model});
|
||||
let bytes = serde_json::to_vec(&envelope)
|
||||
.map_err(|_| ApiError::internal("Could not serialize model."))?;
|
||||
if bytes.len() > MAX_ENVELOPE_BYTES {
|
||||
return Err(ApiError::new(
|
||||
|
||||
Reference in New Issue
Block a user