Migrate frozen data contracts and authored source to schema 4
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
//! Pure, bounded source3 projects. All includes resolve against supplied UTF-8
|
||||
//! Pure, bounded source projects. All includes resolve against supplied UTF-8
|
||||
//! documents; this module never reads a filesystem, URL, or process directory.
|
||||
use crate::{Id, Kind, Project, language, persistence::DecodeResult, validation};
|
||||
use language::{Diagnostic, ParseLimits, Span};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
#[path = "source_project/source_migration.rs"]
|
||||
mod source_migration;
|
||||
#[path = "source_project/source_structure.rs"]
|
||||
mod source_structure;
|
||||
|
||||
@@ -278,21 +280,24 @@ pub fn lint_bundle(bundle: &SourceBundle, limits: ProjectLimits) -> Vec<SourceDi
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile all reachable files as one source3 model and stabilize omitted UUIDs.
|
||||
/// Compile reachable source3/4 files, explicitly upgrade data contracts, and stabilize UUIDs.
|
||||
/// Missing/unreachable files and every admission or engineering error leave the
|
||||
/// input untouched. V1/V2 single-file migration remains in `language::parse`.
|
||||
pub fn compile_bundle(
|
||||
bundle: &SourceBundle,
|
||||
limits: ProjectLimits,
|
||||
) -> Result<CompiledSourceProject, Vec<SourceDiagnostic>> {
|
||||
let (compiled, insertions) = compile_once(bundle, limits)?;
|
||||
if insertions.is_empty() {
|
||||
return Ok(compiled);
|
||||
let (mut compiled, insertions) = compile_once(bundle, limits)?;
|
||||
if !insertions.is_empty() {
|
||||
let mut stable = bundle.clone();
|
||||
apply_edits(&mut stable, insertions)?;
|
||||
// Re-index actual returned bytes before performing the schema upgrade.
|
||||
compiled = compile_once(&stable, limits)?.0;
|
||||
}
|
||||
let mut stable = bundle.clone();
|
||||
apply_edits(&mut stable, insertions)?;
|
||||
// Re-index the actual returned bytes. Never expose offsets into pre-ID text.
|
||||
compile_once(&stable, limits).map(|(compiled, _)| compiled)
|
||||
if compiled.decoded.migration.is_some() {
|
||||
return source_migration::upgrade(&compiled, limits);
|
||||
}
|
||||
Ok(compiled)
|
||||
}
|
||||
|
||||
fn compile_once(
|
||||
@@ -568,7 +573,7 @@ fn compile_once(
|
||||
span,
|
||||
}),
|
||||
};
|
||||
let project =
|
||||
let decoded =
|
||||
language::resolve_declarations(header.project, &declarations, &aliases, limits.model)
|
||||
.map_err(|(index, error)| {
|
||||
let path = declaration_paths.get(index).unwrap_or(&bundle.entry);
|
||||
@@ -578,7 +583,7 @@ fn compile_once(
|
||||
related: Vec::new(),
|
||||
}]
|
||||
})?;
|
||||
let diagnostics = model_diagnostics(bundle, &index, &project);
|
||||
let diagnostics = model_diagnostics(bundle, &index, &decoded.project);
|
||||
let errors: Vec<_> = diagnostics
|
||||
.iter()
|
||||
.filter(|issue| issue.diagnostic.severity == validation::Severity::Error)
|
||||
@@ -590,10 +595,7 @@ fn compile_once(
|
||||
Ok((
|
||||
CompiledSourceProject {
|
||||
bundle: bundle.clone(),
|
||||
decoded: DecodeResult {
|
||||
project,
|
||||
migration: None,
|
||||
},
|
||||
decoded,
|
||||
index,
|
||||
diagnostics,
|
||||
},
|
||||
@@ -718,7 +720,7 @@ fn apply_edits(bundle: &mut SourceBundle, edits: Edits) -> Result<(), Vec<Source
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Canonical, readable source3 export. Like `language::format`, the caller is
|
||||
/// Canonical, readable source4 export. Like `language::format`, the caller is
|
||||
/// responsible for supplying a valid model; compilation checks the result.
|
||||
/// Existing source layout is preserved by `update_bundle`, not this formatter.
|
||||
pub fn format_bundle(project: &Project) -> SourceBundle {
|
||||
@@ -734,7 +736,7 @@ pub fn format_bundle(project: &Project) -> SourceBundle {
|
||||
.push_str(&unindent(&declaration));
|
||||
}
|
||||
let mut entry = format!(
|
||||
"arcadia 3;\n{}{{\n description {};\n",
|
||||
"arcadia 4;\n{}{{\n description {};\n",
|
||||
project_header(project),
|
||||
quote(&project.description)
|
||||
);
|
||||
@@ -923,6 +925,7 @@ pub fn update_bundle_with_identity_map(
|
||||
after: new_text,
|
||||
old_aliases: &old_aliases,
|
||||
new_aliases: &new_aliases,
|
||||
legacy_value_paths: false,
|
||||
};
|
||||
let local = edits.entry(path.clone()).or_default();
|
||||
update.header(&actual.header, &previous.header, &next.header, local);
|
||||
@@ -951,6 +954,7 @@ pub fn update_bundle_with_identity_map(
|
||||
after: &after,
|
||||
old_aliases: &old_aliases,
|
||||
new_aliases: &new_aliases,
|
||||
legacy_value_paths: false,
|
||||
};
|
||||
let mut local = Vec::new();
|
||||
update.header(
|
||||
@@ -1119,6 +1123,9 @@ struct SyntaxUpdate<'a> {
|
||||
after: &'a str,
|
||||
old_aliases: &'a BTreeMap<String, String>,
|
||||
new_aliases: &'a BTreeMap<String, String>,
|
||||
// Source3 migration only adds path qualification; the existing field identity
|
||||
// must keep its authored argument trivia during that one-time transformation.
|
||||
legacy_value_paths: bool,
|
||||
}
|
||||
impl SyntaxUpdate<'_> {
|
||||
fn header(
|
||||
@@ -1338,11 +1345,11 @@ impl SyntaxUpdate<'_> {
|
||||
let previous = before.unwrap_or(&[]);
|
||||
let old_keys: Option<Vec<_>> = previous
|
||||
.iter()
|
||||
.map(|item| item_key(item, self.before, self.old_aliases))
|
||||
.map(|item| item_key(item, self.before, self.old_aliases, self.legacy_value_paths))
|
||||
.collect();
|
||||
let new_keys: Option<Vec<_>> = after
|
||||
.iter()
|
||||
.map(|item| item_key(item, self.after, self.new_aliases))
|
||||
.map(|item| item_key(item, self.after, self.new_aliases, self.legacy_value_paths))
|
||||
.collect();
|
||||
let keyed = old_keys.zip(new_keys).filter(|(old, new)| {
|
||||
old.iter().collect::<BTreeSet<_>>().len() == old.len()
|
||||
@@ -1449,6 +1456,7 @@ fn item_key(
|
||||
value: &language::EditValue,
|
||||
source: &str,
|
||||
aliases: &BTreeMap<String, String>,
|
||||
legacy_value_paths: bool,
|
||||
) -> Option<String> {
|
||||
use language::EditKind;
|
||||
match &value.kind {
|
||||
@@ -1462,7 +1470,31 @@ fn item_key(
|
||||
.find_map(|key| {
|
||||
fields.get(key).and_then(|field| {
|
||||
if let EditKind::Reference(alias) = &field.value.kind {
|
||||
aliases.get(alias).map(|id| format!("{key}:{id}"))
|
||||
aliases.get(alias).map(|id| {
|
||||
if key == "field" && !legacy_value_paths {
|
||||
let path = fields
|
||||
.get("path")
|
||||
.and_then(|field| {
|
||||
if let EditKind::List(path) = &field.value.kind {
|
||||
path.iter()
|
||||
.map(|hop| {
|
||||
if let EditKind::Reference(alias) = &hop.kind {
|
||||
aliases.get(alias).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
format!("field:{}:{id}", path.join("/"))
|
||||
} else {
|
||||
format!("{key}:{id}")
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user