use std::sync::Arc;
use crate::{pcp, sdf, tf};
use super::{PrimTypeInfo, SpecSite, Stage, StageAuthoringError};
#[derive(Debug, Clone, PartialEq)]
pub(super) enum PropertyDeclaration {
Attribute {
type_name: sdf::ValueTypeName,
variability: sdf::Variability,
custom: bool,
},
Relationship {
variability: sdf::Variability,
custom: bool,
},
}
impl PropertyDeclaration {
pub fn kind(&self) -> sdf::SpecType {
match self {
Self::Attribute { .. } => sdf::SpecType::Attribute,
Self::Relationship { .. } => sdf::SpecType::Relationship,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) enum EnsurePlan {
Existing,
Stamp(PropertyDeclaration),
}
impl EnsurePlan {
pub fn attribute_type(&self) -> Option<&sdf::ValueTypeName> {
match self {
Self::Stamp(PropertyDeclaration::Attribute { type_name, .. }) => Some(type_name),
Self::Existing | Self::Stamp(PropertyDeclaration::Relationship { .. }) => None,
}
}
}
pub(super) fn plan_property_spec(
stage: &Stage,
path: &sdf::Path,
kind: sdf::SpecType,
fallback: Option<PropertyDeclaration>,
) -> Result<EnsurePlan, StageAuthoringError> {
if let Some(found) = stage.local_spec_type(path)? {
return if found == kind {
Ok(EnsurePlan::Existing)
} else {
Err(kind_mismatch(path, kind, found))
};
}
let Some((info, name)) = schema_definition(stage, path)? else {
return Err(sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason: "property authoring needs a property path",
}
.into());
};
if let Some(property) = info.prim_definition().property(&name) {
let found = property.spec_type();
if found != kind {
return Err(kind_mismatch(path, kind, found));
}
let declaration = if kind == sdf::SpecType::Attribute {
PropertyDeclaration::Attribute {
type_name: property
.type_name_token()
.map(sdf::ValueTypeName::from)
.ok_or(sdf::ValueTypeError::Empty)?,
variability: property.variability(),
custom: false,
}
} else {
PropertyDeclaration::Relationship {
variability: property.variability(),
custom: false,
}
};
return Ok(EnsurePlan::Stamp(declaration));
}
let strongest = stage
.masked(path, |graph, cache| cache.property_stack(graph, path, None))?
.into_iter()
.next();
if let Some(site) = strongest {
let declaration = declaration_at(stage, &site, kind)?;
if declaration.kind() != kind {
return Err(kind_mismatch(path, kind, declaration.kind()));
}
return Ok(EnsurePlan::Stamp(declaration));
}
fallback.map(EnsurePlan::Stamp).ok_or_else(|| missing_spec(path, kind))
}
pub(super) fn apply_plan(
data: &mut dyn sdf::AbstractData,
path: &sdf::Path,
kind: sdf::SpecType,
plan: &EnsurePlan,
) -> Result<(), StageAuthoringError> {
match data.spec_type(path) {
Some(found) if found == kind => Ok(()),
Some(found) => Err(kind_mismatch(path, kind, found)),
None => match plan {
EnsurePlan::Existing => Err(missing_spec(path, kind)),
EnsurePlan::Stamp(declaration) => stamp(data, path, declaration),
},
}
}
pub(super) fn schema_definition(
stage: &Stage,
path: &sdf::Path,
) -> Result<Option<(Arc<PrimTypeInfo>, tf::Token)>, pcp::QueryError> {
let Some((prim, name)) = path.split_property() else {
return Ok(None);
};
Ok(Some((stage.prim_type_info_composed(prim)?, tf::Token::from(name))))
}
pub(super) fn edit_spec<'a, S>(
data: &'a mut dyn sdf::AbstractData,
path: sdf::Path,
kind: sdf::SpecType,
get: impl FnOnce(&'a mut dyn sdf::AbstractData, sdf::Path) -> Option<S>,
f: impl FnOnce(&mut S) -> Result<(), StageAuthoringError>,
) -> Result<(), StageAuthoringError> {
match get(data, path.clone()) {
Some(mut spec) => f(&mut spec),
None => Err(missing_spec(&path, kind)),
}
}
pub(super) fn edit_existing_spec<'a, S>(
data: &'a mut dyn sdf::AbstractData,
path: sdf::Path,
kind: sdf::SpecType,
get: impl FnOnce(&'a mut dyn sdf::AbstractData, sdf::Path) -> Option<S>,
f: impl FnOnce(&mut S) -> Result<(), StageAuthoringError>,
) -> Result<(), StageAuthoringError> {
match data.spec_type(&path) {
None => Ok(()),
Some(found) if found == kind => {
let mut spec = get(data, path).expect("the kind was checked");
f(&mut spec)
}
Some(found) => Err(kind_mismatch(&path, kind, found)),
}
}
pub(super) fn check_reserved(kind: sdf::SpecType, key: &'static str) -> Result<(), StageAuthoringError> {
let reserved: &[sdf::FieldKey] = match kind {
sdf::SpecType::Attribute => &[
sdf::FieldKey::Default,
sdf::FieldKey::TimeSamples,
sdf::FieldKey::TypeName,
sdf::FieldKey::ConnectionPaths,
sdf::FieldKey::Variability,
sdf::FieldKey::Custom,
],
sdf::SpecType::Relationship => &[
sdf::FieldKey::TargetPaths,
sdf::FieldKey::Variability,
sdf::FieldKey::Custom,
],
_ => &[],
};
if reserved.iter().any(|field| field.as_str() == key) {
return Err(StageAuthoringError::ReservedField { field: key });
}
Ok(())
}
fn declaration_at(
stage: &Stage,
site: &SpecSite,
wanted: sdf::SpecType,
) -> Result<PropertyDeclaration, StageAuthoringError> {
let layer = stage
.layer(&site.layer)
.ok_or_else(|| StageAuthoringError::LayerNotFound {
layer: site.layer.clone(),
})?;
declaration_of(layer.data(), &site.path, wanted)
}
fn declaration_of(
data: &dyn sdf::AbstractData,
path: &sdf::Path,
wanted: sdf::SpecType,
) -> Result<PropertyDeclaration, StageAuthoringError> {
match data.spec_type(path) {
Some(sdf::SpecType::Attribute) => {
let spec = sdf::AttributeSpecRef::get(data, path.clone()).expect("the kind was checked");
Ok(PropertyDeclaration::Attribute {
type_name: spec.declared_type()?.ok_or(sdf::ValueTypeError::Empty)?,
variability: spec
.typed_field(sdf::FieldKey::Variability, "Variability")?
.unwrap_or_default(),
custom: spec.typed_field(sdf::FieldKey::Custom, "Bool")?.unwrap_or(false),
})
}
Some(sdf::SpecType::Relationship) => {
let spec = sdf::RelationshipSpecRef::get(data, path.clone()).expect("the kind was checked");
Ok(PropertyDeclaration::Relationship {
variability: spec
.typed_field(sdf::FieldKey::Variability, "Variability")?
.unwrap_or_default(),
custom: spec.typed_field(sdf::FieldKey::Custom, "Bool")?.unwrap_or(false),
})
}
Some(found) => Err(kind_mismatch(path, wanted, found)),
None => Err(missing_spec(path, wanted)),
}
}
fn stamp(
data: &mut dyn sdf::AbstractData,
path: &sdf::Path,
declaration: &PropertyDeclaration,
) -> Result<(), StageAuthoringError> {
match declaration {
PropertyDeclaration::Attribute {
type_name,
variability,
custom,
} => {
sdf::AttributeSpec::new(data, path.clone(), type_name.clone(), *variability, *custom)?;
}
PropertyDeclaration::Relationship { variability, custom } => {
sdf::RelationshipSpec::new(data, path.clone(), *variability, *custom)?;
}
}
Ok(())
}
fn kind_mismatch(path: &sdf::Path, expected: sdf::SpecType, found: sdf::SpecType) -> StageAuthoringError {
StageAuthoringError::SpecKindMismatch {
path: path.clone(),
expected,
found,
}
}
pub(super) fn missing_spec(path: &sdf::Path, kind: sdf::SpecType) -> StageAuthoringError {
let reason = match kind {
sdf::SpecType::Prim => "no prim spec at path on the edit target layer",
sdf::SpecType::Attribute => "no attribute spec at path on the edit target layer",
sdf::SpecType::Relationship => "no relationship spec at path on the edit target layer",
_ => "no spec at path on the edit target layer",
};
sdf::AuthoringError::InvalidPath {
path: path.clone(),
reason,
}
.into()
}