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,4 +1,6 @@
|
||||
//! Typed OA/SA language, tolerant source analysis, and explicit V1 migration.
|
||||
//! Typed OA/SA language, tolerant source analysis, and explicit V1/V2 migration.
|
||||
#[path = "migration_v2/source.rs"]
|
||||
mod v2_source;
|
||||
use crate::*;
|
||||
use serde_json::{Map, Value};
|
||||
use std::{collections::BTreeMap, fmt};
|
||||
@@ -317,6 +319,8 @@ fn reference_field(key: &str) -> bool {
|
||||
| "branches"
|
||||
| "Named"
|
||||
| "items"
|
||||
| "incoming_items"
|
||||
| "outgoing_items"
|
||||
| "triggers"
|
||||
| "effects"
|
||||
| "interface"
|
||||
@@ -351,6 +355,8 @@ fn enum_field(key: &str) -> bool {
|
||||
key,
|
||||
"kind"
|
||||
| "direction"
|
||||
| "orientation"
|
||||
| "contract"
|
||||
| "layer"
|
||||
| "projection"
|
||||
| "relation"
|
||||
@@ -364,13 +370,24 @@ fn enum_field(key: &str) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse V2, or explicitly migrate a valid V1 source candidate. Parsing never
|
||||
/// Parse V3, or explicitly migrate a valid V1/V2 source candidate. Parsing never
|
||||
/// adopts a model and never overwrites an editor draft.
|
||||
pub fn parse(source: &str) -> Result<Project, Vec<Diagnostic>> {
|
||||
parse_limited(source, ParseLimits::UNLIMITED)
|
||||
}
|
||||
|
||||
pub fn parse_limited(source: &str, limits: ParseLimits) -> Result<Project, Vec<Diagnostic>> {
|
||||
parse_with_report_limited(source, limits).map(|decoded| decoded.project)
|
||||
}
|
||||
pub fn parse_with_report(
|
||||
source: &str,
|
||||
) -> Result<crate::persistence::DecodeResult, Vec<Diagnostic>> {
|
||||
parse_with_report_limited(source, ParseLimits::UNLIMITED)
|
||||
}
|
||||
pub fn parse_with_report_limited(
|
||||
source: &str,
|
||||
limits: ParseLimits,
|
||||
) -> Result<crate::persistence::DecodeResult, Vec<Diagnostic>> {
|
||||
let parsed = parse_document(source, limits)?;
|
||||
let errors: Vec<_> = parsed
|
||||
.diagnostics(source)
|
||||
@@ -378,7 +395,10 @@ pub fn parse_limited(source: &str, limits: ParseLimits) -> Result<Project, Vec<D
|
||||
.filter(|d| d.severity == validation::Severity::Error)
|
||||
.collect();
|
||||
if errors.is_empty() {
|
||||
Ok(parsed.project)
|
||||
Ok(crate::persistence::DecodeResult {
|
||||
project: parsed.project,
|
||||
migration: parsed.migration,
|
||||
})
|
||||
} else {
|
||||
Err(errors)
|
||||
}
|
||||
@@ -386,6 +406,7 @@ pub fn parse_limited(source: &str, limits: ParseLimits) -> Result<Project, Vec<D
|
||||
|
||||
struct ParsedDocument {
|
||||
project: Project,
|
||||
migration: Option<crate::persistence::MigrationReport>,
|
||||
locations: BTreeMap<Id, usize>,
|
||||
fields: BTreeMap<(Id, String), usize>,
|
||||
}
|
||||
@@ -460,22 +481,31 @@ fn parse_document(source: &str, limits: ParseLimits) -> Result<ParsedDocument, V
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
let project = crate::persistence::migrate_v1_limited(old, limits).map_err(|message| {
|
||||
vec![diagnostic(
|
||||
source,
|
||||
Error {
|
||||
code: "invalid_source",
|
||||
offset: 0,
|
||||
message,
|
||||
},
|
||||
)]
|
||||
})?;
|
||||
let decoded =
|
||||
crate::persistence::migrate_v1_with_report_limited(old, limits).map_err(|error| {
|
||||
vec![diagnostic(
|
||||
source,
|
||||
Error {
|
||||
code: if matches!(error, crate::persistence::FileError::Limit(_)) {
|
||||
"model_limit"
|
||||
} else {
|
||||
"invalid_source"
|
||||
},
|
||||
offset: 0,
|
||||
message: error.to_string(),
|
||||
},
|
||||
)]
|
||||
})?;
|
||||
return Ok(ParsedDocument {
|
||||
project,
|
||||
project: decoded.project,
|
||||
migration: decoded.migration,
|
||||
locations: BTreeMap::new(),
|
||||
fields: BTreeMap::new(),
|
||||
});
|
||||
}
|
||||
if tokens.get(1).is_some_and(|t| t.text(source) == "2") {
|
||||
return v2_source::parse(source, tokens, limits);
|
||||
}
|
||||
Parser {
|
||||
source,
|
||||
tokens,
|
||||
@@ -510,7 +540,7 @@ pub fn format(project: &Project) -> String {
|
||||
.map(|(id, a)| (id, a.to_owned()))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let mut out = std::format!(
|
||||
"arcadia 2;\nproject {} {} id \"{}\" {{\n description {};\n\n",
|
||||
"arcadia 3;\nproject {} {} id \"{}\" {{\n description {};\n\n",
|
||||
project.alias,
|
||||
quote(&project.name),
|
||||
project.id,
|
||||
@@ -1000,8 +1030,8 @@ impl Parser<'_> {
|
||||
}
|
||||
fn document(&mut self) -> Result<ParsedDocument, Error> {
|
||||
self.take("arcadia")?;
|
||||
if self.text() != "2" {
|
||||
return Err(self.error("Unsupported schema version: expected 'arcadia 2;'."));
|
||||
if self.text() != "3" {
|
||||
return Err(self.error("Unsupported schema version: expected 'arcadia 3;'."));
|
||||
}
|
||||
self.cursor += 1;
|
||||
self.take(";")?;
|
||||
@@ -1116,7 +1146,22 @@ impl Parser<'_> {
|
||||
take_string(&mut fields, "description", "").map_err(&conversion_error)?;
|
||||
let parent =
|
||||
take_optional_id(&mut fields, "parent").map_err(&conversion_error)?;
|
||||
let mut initial = ElementBody::new(kind, layer).unwrap();
|
||||
let mut initial = if kind == Kind::Port {
|
||||
if !fields.contains_key("contract") {
|
||||
return Err(conversion_error(
|
||||
"Missing required field 'contract'.".into(),
|
||||
));
|
||||
}
|
||||
ElementBody::shared_in(
|
||||
layer,
|
||||
SharedBody::Port {
|
||||
contract: PortContract::Legacy,
|
||||
legacy: None,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
ElementBody::new(kind, layer).unwrap()
|
||||
};
|
||||
// Schema-2 sources predate explicit scenario viewpoints.
|
||||
// Preserve their mixed lifelines instead of reinterpreting
|
||||
// old source using a new graphical factory's typed default.
|
||||
@@ -1151,7 +1196,12 @@ impl Parser<'_> {
|
||||
DeclKind::Relation(kind) => {
|
||||
let description =
|
||||
take_string(&mut fields, "description", "").map_err(&conversion_error)?;
|
||||
for required in ["source", "target"] {
|
||||
let required: &[&str] = if kind == RelationKind::FunctionalExchange {
|
||||
&["source", "target", "source_port", "target_port"]
|
||||
} else {
|
||||
&["source", "target"]
|
||||
};
|
||||
for &required in required {
|
||||
if !fields.contains_key(required) {
|
||||
return Err(conversion_error(std::format!(
|
||||
"Missing required field '{required}'."
|
||||
@@ -1246,6 +1296,7 @@ impl Parser<'_> {
|
||||
})?;
|
||||
Ok(ParsedDocument {
|
||||
project,
|
||||
migration: None,
|
||||
locations,
|
||||
fields: field_locations,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user