use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use crate::sdf;
#[derive(Debug, Clone)]
pub struct Spec {
pub ty: sdf::SpecType,
pub fields: Vec<(String, sdf::Value)>,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum SpecError {
#[error("field {field} exists with non-{expected} value")]
FieldType {
field: &'static str,
expected: &'static str,
},
}
impl Spec {
pub fn new(ty: sdf::SpecType) -> Self {
Self { ty, fields: Vec::new() }
}
pub fn add(&mut self, key: impl AsRef<str>, value: impl Into<sdf::Value>) {
let key = key.as_ref();
let value = value.into();
if let Some(slot) = self.fields.iter_mut().find(|(k, _)| k == key) {
slot.1 = value;
} else {
self.fields.push((key.to_owned(), value));
}
}
pub fn get(&self, key: &str) -> Option<&sdf::Value> {
self.fields.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
pub fn get_mut(&mut self, key: &str) -> Option<&mut sdf::Value> {
self.fields.iter_mut().find(|(k, _)| k == key).map(|(_, v)| v)
}
pub fn contains(&self, key: &str) -> bool {
self.fields.iter().any(|(k, _)| k == key)
}
pub fn remove(&mut self, key: &str) -> Option<sdf::Value> {
let idx = self.fields.iter().position(|(k, _)| k == key)?;
Some(self.fields.remove(idx).1)
}
pub fn extend_from(&mut self, other: Spec) {
for (k, v) in other.fields {
self.add(k, v);
}
}
pub fn as_prim(&self) -> Option<PrimSpec<'_>> {
(self.ty == sdf::SpecType::Prim).then_some(PrimSpec::new(self))
}
pub fn as_prim_mut(&mut self) -> Option<PrimSpecMut<'_>> {
(self.ty == sdf::SpecType::Prim).then_some(PrimSpec::new(self))
}
pub fn as_attr(&self) -> Option<AttributeSpec<'_>> {
(self.ty == sdf::SpecType::Attribute).then_some(AttributeSpec::new(self))
}
pub fn as_attr_mut(&mut self) -> Option<AttributeSpecMut<'_>> {
(self.ty == sdf::SpecType::Attribute).then_some(AttributeSpec::new(self))
}
pub fn as_relationship(&self) -> Option<RelationshipSpec<'_>> {
(self.ty == sdf::SpecType::Relationship).then_some(RelationshipSpec::new(self))
}
pub fn as_relationship_mut(&mut self) -> Option<RelationshipSpecMut<'_>> {
(self.ty == sdf::SpecType::Relationship).then_some(RelationshipSpec::new(self))
}
pub fn as_pseudo_root(&self) -> Option<PseudoRootSpec<'_>> {
(self.ty == sdf::SpecType::PseudoRoot).then_some(PseudoRootSpec::new(self))
}
pub fn as_pseudo_root_mut(&mut self) -> Option<PseudoRootSpecMut<'_>> {
(self.ty == sdf::SpecType::PseudoRoot).then_some(PseudoRootSpec::new(self))
}
}
#[derive(Debug)]
pub struct PrimSpec<'a, B = &'a Spec> {
spec: B,
_marker: PhantomData<&'a Spec>,
}
pub type PrimSpecMut<'a> = PrimSpec<'a, &'a mut Spec>;
impl<'a, B> PrimSpec<'a, B> {
fn new(spec: B) -> Self {
Self {
spec,
_marker: PhantomData,
}
}
}
impl<'a, B> Deref for PrimSpec<'a, B>
where
B: Deref<Target = Spec>,
{
type Target = Spec;
fn deref(&self) -> &Spec {
self.spec.deref()
}
}
impl<'a, B> DerefMut for PrimSpec<'a, B>
where
B: DerefMut<Target = Spec>,
{
fn deref_mut(&mut self) -> &mut Spec {
self.spec.deref_mut()
}
}
impl<'a, B> PrimSpec<'a, B>
where
B: Deref<Target = Spec>,
{
pub fn type_name(&self) -> Option<&str> {
match self.get(sdf::FieldKey::TypeName.as_str())? {
sdf::Value::Token(t) => Some(t.as_str()),
_ => None,
}
}
pub fn specifier(&self) -> Option<sdf::Specifier> {
match self.get(sdf::FieldKey::Specifier.as_str())? {
sdf::Value::Specifier(s) => Some(*s),
_ => None,
}
}
pub fn kind(&self) -> Option<&str> {
match self.get(sdf::FieldKey::Kind.as_str())? {
sdf::Value::Token(t) => Some(t.as_str()),
_ => None,
}
}
pub fn is_active(&self) -> Option<bool> {
match self.get(sdf::FieldKey::Active.as_str())? {
sdf::Value::Bool(b) => Some(*b),
_ => None,
}
}
pub fn is_hidden(&self) -> Option<bool> {
match self.get(sdf::FieldKey::Hidden.as_str())? {
sdf::Value::Bool(b) => Some(*b),
_ => None,
}
}
pub fn is_instanceable(&self) -> Option<bool> {
match self.get(sdf::FieldKey::Instanceable.as_str())? {
sdf::Value::Bool(b) => Some(*b),
_ => None,
}
}
pub fn prim_children(&self) -> Option<&[String]> {
match self.get(sdf::ChildrenKey::PrimChildren.as_str())? {
sdf::Value::TokenVec(v) => Some(v.as_slice()),
_ => None,
}
}
pub fn property_children(&self) -> Option<&[String]> {
match self.get(sdf::ChildrenKey::PropertyChildren.as_str())? {
sdf::Value::TokenVec(v) => Some(v.as_slice()),
_ => None,
}
}
pub fn api_schemas(&self) -> Option<&sdf::TokenListOp> {
match self.get(sdf::FieldKey::ApiSchemas.as_str())? {
sdf::Value::TokenListOp(op) => Some(op),
_ => None,
}
}
}
impl<'a, B> PrimSpec<'a, B>
where
B: DerefMut<Target = Spec>,
{
pub fn set_type_name(&mut self, name: impl Into<String>) {
let name = name.into();
if name.is_empty() {
self.remove(sdf::FieldKey::TypeName.as_str());
} else {
self.add(sdf::FieldKey::TypeName, sdf::Value::Token(name));
}
}
pub fn set_specifier(&mut self, specifier: sdf::Specifier) {
self.add(sdf::FieldKey::Specifier, sdf::Value::Specifier(specifier));
}
pub fn set_kind(&mut self, kind: impl Into<String>) {
self.add(sdf::FieldKey::Kind, sdf::Value::Token(kind.into()));
}
pub fn set_active(&mut self, active: bool) {
self.add(sdf::FieldKey::Active, sdf::Value::Bool(active));
}
pub fn set_hidden(&mut self, hidden: bool) {
self.add(sdf::FieldKey::Hidden, sdf::Value::Bool(hidden));
}
pub fn set_instanceable(&mut self, instanceable: bool) {
self.add(sdf::FieldKey::Instanceable, sdf::Value::Bool(instanceable));
}
pub fn add_applied_schema(&mut self, name: impl Into<String>) -> Result<bool, SpecError> {
let name = name.into();
match self.get_mut(sdf::FieldKey::ApiSchemas.as_str()) {
Some(sdf::Value::TokenListOp(op)) => Ok(add_applied_schema_to_list_op(op, name)),
Some(_) => Err(SpecError::FieldType {
field: sdf::FieldKey::ApiSchemas.as_str(),
expected: "sdf::TokenListOp",
}),
None => {
self.add(
sdf::FieldKey::ApiSchemas,
sdf::Value::TokenListOp(sdf::TokenListOp::prepended([name])),
);
Ok(true)
}
}
}
}
fn add_applied_schema_to_list_op(op: &mut sdf::TokenListOp, name: String) -> bool {
let already_applied = op.explicit_items.iter().any(|n| n == &name)
|| op.prepended_items.iter().any(|n| n == &name)
|| op.appended_items.iter().any(|n| n == &name)
|| (!op.explicit && op.added_items.iter().any(|n| n == &name));
let before = op.deleted_items.len();
op.deleted_items.retain(|n| n != &name);
let mut changed = op.deleted_items.len() != before;
if already_applied {
return changed;
}
if op.explicit {
op.explicit_items.push(name);
} else {
op.prepended_items.push(name);
}
changed = true;
changed
}
#[derive(Debug)]
pub struct AttributeSpec<'a, B = &'a Spec> {
spec: B,
_marker: PhantomData<&'a Spec>,
}
pub type AttributeSpecMut<'a> = AttributeSpec<'a, &'a mut Spec>;
impl<'a, B> AttributeSpec<'a, B> {
fn new(spec: B) -> Self {
Self {
spec,
_marker: PhantomData,
}
}
}
impl<'a, B> Deref for AttributeSpec<'a, B>
where
B: Deref<Target = Spec>,
{
type Target = Spec;
fn deref(&self) -> &Spec {
self.spec.deref()
}
}
impl<'a, B> DerefMut for AttributeSpec<'a, B>
where
B: DerefMut<Target = Spec>,
{
fn deref_mut(&mut self) -> &mut Spec {
self.spec.deref_mut()
}
}
impl<'a, B> AttributeSpec<'a, B>
where
B: Deref<Target = Spec>,
{
pub fn type_name(&self) -> Option<&str> {
match self.get(sdf::FieldKey::TypeName.as_str())? {
sdf::Value::Token(t) => Some(t.as_str()),
_ => None,
}
}
pub fn variability(&self) -> sdf::Variability {
match self.get(sdf::FieldKey::Variability.as_str()) {
Some(sdf::Value::Variability(v)) => *v,
_ => sdf::Variability::Varying,
}
}
pub fn is_custom(&self) -> bool {
match self.get(sdf::FieldKey::Custom.as_str()) {
Some(sdf::Value::Bool(b)) => *b,
_ => false,
}
}
pub fn default(&self) -> Option<&sdf::Value> {
self.get(sdf::FieldKey::Default.as_str())
}
pub fn time_samples(&self) -> Option<&[(f64, sdf::Value)]> {
match self.get(sdf::FieldKey::TimeSamples.as_str())? {
sdf::Value::TimeSamples(map) => Some(map.as_slice()),
_ => None,
}
}
pub fn color_space(&self) -> Option<&str> {
match self.get(sdf::FieldKey::ColorSpace.as_str())? {
sdf::Value::Token(t) => Some(t.as_str()),
_ => None,
}
}
pub fn allowed_tokens(&self) -> Option<&[String]> {
match self.get(sdf::FieldKey::AllowedTokens.as_str())? {
sdf::Value::TokenVec(v) => Some(v.as_slice()),
_ => None,
}
}
pub fn connection_path_list(&self) -> Option<&sdf::PathListOp> {
match self.get(sdf::FieldKey::ConnectionPaths.as_str())? {
sdf::Value::PathListOp(op) => Some(op),
_ => None,
}
}
}
impl<'a, B> AttributeSpec<'a, B>
where
B: DerefMut<Target = Spec>,
{
pub fn set_default(&mut self, value: impl Into<sdf::Value>) {
self.add(sdf::FieldKey::Default, value.into());
}
pub fn clear_default(&mut self) {
self.remove(sdf::FieldKey::Default.as_str());
}
pub fn set_time_sample(&mut self, time: f64, value: impl Into<sdf::Value>) {
let value = value.into();
match self.get_mut(sdf::FieldKey::TimeSamples.as_str()) {
Some(sdf::Value::TimeSamples(map)) => upsert_time_sample(map, time, value),
Some(other) => {
debug_assert!(false, "timeSamples field is not a TimeSamples (got {other:?})");
let mut map = Vec::new();
upsert_time_sample(&mut map, time, value);
self.add(sdf::FieldKey::TimeSamples, sdf::Value::TimeSamples(map));
}
None => {
let mut map = Vec::new();
upsert_time_sample(&mut map, time, value);
self.add(sdf::FieldKey::TimeSamples, sdf::Value::TimeSamples(map));
}
}
}
pub fn erase_time_sample(&mut self, time: f64) -> bool {
let key = sdf::FieldKey::TimeSamples.as_str();
let Some(sdf::Value::TimeSamples(map)) = self.get_mut(key) else {
return false;
};
let Some(idx) = map.iter().position(|(t, _)| t.total_cmp(&time).is_eq()) else {
return false;
};
map.remove(idx);
if map.is_empty() {
self.remove(key);
}
true
}
pub fn set_custom(&mut self, custom: bool) {
self.add(sdf::FieldKey::Custom, sdf::Value::Bool(custom));
}
pub fn set_color_space(&mut self, color_space: impl Into<String>) {
self.add(sdf::FieldKey::ColorSpace, sdf::Value::Token(color_space.into()));
}
pub fn set_allowed_tokens<I, S>(&mut self, tokens: I)
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let tokens: Vec<String> = tokens.into_iter().map(Into::into).collect();
self.add(sdf::FieldKey::AllowedTokens, sdf::Value::TokenVec(tokens));
}
pub fn set_connection_paths<I>(&mut self, paths: I)
where
I: IntoIterator<Item = sdf::Path>,
{
let paths: Vec<sdf::Path> = paths.into_iter().collect();
self.add(
sdf::FieldKey::ConnectionPaths,
sdf::Value::PathListOp(sdf::PathListOp::explicit(paths)),
);
}
}
fn upsert_time_sample(map: &mut Vec<(f64, sdf::Value)>, time: f64, value: sdf::Value) {
match map.binary_search_by(|(t, _)| t.total_cmp(&time)) {
Ok(idx) => map[idx].1 = value,
Err(idx) => map.insert(idx, (time, value)),
}
}
#[derive(Debug)]
pub struct RelationshipSpec<'a, B = &'a Spec> {
spec: B,
_marker: PhantomData<&'a Spec>,
}
pub type RelationshipSpecMut<'a> = RelationshipSpec<'a, &'a mut Spec>;
impl<'a, B> RelationshipSpec<'a, B> {
fn new(spec: B) -> Self {
Self {
spec,
_marker: PhantomData,
}
}
}
impl<'a, B> Deref for RelationshipSpec<'a, B>
where
B: Deref<Target = Spec>,
{
type Target = Spec;
fn deref(&self) -> &Spec {
self.spec.deref()
}
}
impl<'a, B> DerefMut for RelationshipSpec<'a, B>
where
B: DerefMut<Target = Spec>,
{
fn deref_mut(&mut self) -> &mut Spec {
self.spec.deref_mut()
}
}
impl<'a, B> RelationshipSpec<'a, B>
where
B: Deref<Target = Spec>,
{
pub fn target_path_list(&self) -> Option<&sdf::PathListOp> {
match self.get(sdf::FieldKey::TargetPaths.as_str())? {
sdf::Value::PathListOp(op) => Some(op),
_ => None,
}
}
pub fn is_custom(&self) -> bool {
match self.get(sdf::FieldKey::Custom.as_str()) {
Some(sdf::Value::Bool(b)) => *b,
_ => false,
}
}
pub fn variability(&self) -> sdf::Variability {
match self.get(sdf::FieldKey::Variability.as_str()) {
Some(sdf::Value::Variability(v)) => *v,
_ => sdf::Variability::Varying,
}
}
}
impl<'a, B> RelationshipSpec<'a, B>
where
B: DerefMut<Target = Spec>,
{
pub fn set_target_paths<I>(&mut self, paths: I)
where
I: IntoIterator<Item = sdf::Path>,
{
let paths: Vec<sdf::Path> = paths.into_iter().collect();
self.add(
sdf::FieldKey::TargetPaths,
sdf::Value::PathListOp(sdf::PathListOp::explicit(paths)),
);
}
pub fn add_target(&mut self, path: sdf::Path) {
match self.get_mut(sdf::FieldKey::TargetPaths.as_str()) {
Some(sdf::Value::PathListOp(op)) => {
if !op.iter().any(|p| p == &path) {
if op.explicit {
op.explicit_items.push(path);
} else {
op.added_items.push(path);
}
}
}
Some(other) => {
debug_assert!(false, "targetPaths field is not a sdf::PathListOp (got {other:?})");
self.add(
sdf::FieldKey::TargetPaths,
sdf::Value::PathListOp(sdf::PathListOp::explicit([path])),
);
}
None => {
self.add(
sdf::FieldKey::TargetPaths,
sdf::Value::PathListOp(sdf::PathListOp::explicit([path])),
);
}
}
}
pub fn remove_target(&mut self, path: &sdf::Path) -> bool {
if let Some(sdf::Value::PathListOp(op)) = self.get_mut(sdf::FieldKey::TargetPaths.as_str()) {
return remove_path(&mut op.explicit_items, path)
| remove_path(&mut op.added_items, path)
| remove_path(&mut op.prepended_items, path)
| remove_path(&mut op.appended_items, path);
}
false
}
pub fn set_custom(&mut self, custom: bool) {
self.add(sdf::FieldKey::Custom, sdf::Value::Bool(custom));
}
}
fn remove_path(paths: &mut Vec<sdf::Path>, path: &sdf::Path) -> bool {
let Some(idx) = paths.iter().position(|p| p == path) else {
return false;
};
paths.remove(idx);
true
}
#[derive(Debug)]
pub struct PseudoRootSpec<'a, B = &'a Spec> {
spec: B,
_marker: PhantomData<&'a Spec>,
}
pub type PseudoRootSpecMut<'a> = PseudoRootSpec<'a, &'a mut Spec>;
impl<'a, B> PseudoRootSpec<'a, B> {
fn new(spec: B) -> Self {
Self {
spec,
_marker: PhantomData,
}
}
}
impl<'a, B> Deref for PseudoRootSpec<'a, B>
where
B: Deref<Target = Spec>,
{
type Target = Spec;
fn deref(&self) -> &Spec {
self.spec.deref()
}
}
impl<'a, B> DerefMut for PseudoRootSpec<'a, B>
where
B: DerefMut<Target = Spec>,
{
fn deref_mut(&mut self) -> &mut Spec {
self.spec.deref_mut()
}
}
impl<'a, B> PseudoRootSpec<'a, B>
where
B: Deref<Target = Spec>,
{
pub fn default_prim(&self) -> Option<&str> {
match self.get(sdf::FieldKey::DefaultPrim.as_str())? {
sdf::Value::Token(t) => Some(t.as_str()),
_ => None,
}
}
pub fn sublayers(&self) -> Option<&[String]> {
match self.get(sdf::FieldKey::SubLayers.as_str())? {
sdf::Value::StringVec(v) | sdf::Value::TokenVec(v) => Some(v.as_slice()),
_ => None,
}
}
pub fn documentation(&self) -> Option<&str> {
match self.get(sdf::FieldKey::Documentation.as_str())? {
sdf::Value::String(s) => Some(s.as_str()),
_ => None,
}
}
pub fn start_time_code(&self) -> Option<f64> {
match self.get(sdf::FieldKey::StartTimeCode.as_str())? {
sdf::Value::Double(v) => Some(*v),
_ => None,
}
}
pub fn end_time_code(&self) -> Option<f64> {
match self.get(sdf::FieldKey::EndTimeCode.as_str())? {
sdf::Value::Double(v) => Some(*v),
_ => None,
}
}
pub fn time_codes_per_second(&self) -> Option<f64> {
match self.get(sdf::FieldKey::TimeCodesPerSecond.as_str())? {
sdf::Value::Double(v) => Some(*v),
_ => None,
}
}
pub fn frames_per_second(&self) -> Option<f64> {
match self.get(sdf::FieldKey::FramesPerSecond.as_str())? {
sdf::Value::Double(v) => Some(*v),
_ => None,
}
}
pub fn prim_children(&self) -> Option<&[String]> {
match self.get(sdf::ChildrenKey::PrimChildren.as_str())? {
sdf::Value::TokenVec(v) => Some(v.as_slice()),
_ => None,
}
}
}
impl<'a, B> PseudoRootSpec<'a, B>
where
B: DerefMut<Target = Spec>,
{
pub fn set_default_prim(&mut self, name: impl Into<String>) {
self.add(sdf::FieldKey::DefaultPrim, sdf::Value::Token(name.into()));
}
pub fn set_sublayers<I, S>(&mut self, paths: I)
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let paths: Vec<String> = paths.into_iter().map(Into::into).collect();
self.add(sdf::FieldKey::SubLayers, sdf::Value::StringVec(paths));
}
pub fn add_sublayer(&mut self, path: impl Into<String>) {
let path = path.into();
let mut paths: Vec<String> = match self.remove(sdf::FieldKey::SubLayers.as_str()) {
Some(sdf::Value::StringVec(v)) | Some(sdf::Value::TokenVec(v)) => v,
_ => Vec::new(),
};
paths.push(path);
self.add(sdf::FieldKey::SubLayers, sdf::Value::StringVec(paths));
}
pub fn set_documentation(&mut self, doc: impl Into<String>) {
self.add(sdf::FieldKey::Documentation, sdf::Value::String(doc.into()));
}
pub fn set_start_time_code(&mut self, time: f64) {
self.add(sdf::FieldKey::StartTimeCode, sdf::Value::Double(time));
}
pub fn set_end_time_code(&mut self, time: f64) {
self.add(sdf::FieldKey::EndTimeCode, sdf::Value::Double(time));
}
pub fn set_time_codes_per_second(&mut self, rate: f64) {
self.add(sdf::FieldKey::TimeCodesPerSecond, sdf::Value::Double(rate));
}
pub fn set_frames_per_second(&mut self, rate: f64) {
self.add(sdf::FieldKey::FramesPerSecond, sdf::Value::Double(rate));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prim_mut_reads() {
let mut spec = Spec::new(sdf::SpecType::Prim);
let mut prim = spec.as_prim_mut().expect("prim spec");
prim.set_type_name("Xform");
prim.set_specifier(sdf::Specifier::Def);
assert_eq!(prim.type_name(), Some("Xform"));
assert_eq!(prim.specifier(), Some(sdf::Specifier::Def));
}
#[test]
fn add_api_schema_prepends() -> Result<(), SpecError> {
let mut spec = Spec::new(sdf::SpecType::Prim);
let mut prim = spec.as_prim_mut().expect("prim spec");
assert!(prim.add_applied_schema("MaterialBindingAPI")?);
assert!(prim.add_applied_schema("SkelBindingAPI")?);
assert!(!prim.add_applied_schema("MaterialBindingAPI")?);
let op = prim.api_schemas().expect("apiSchemas");
assert!(!op.explicit);
assert_eq!(
op.prepended_items,
vec!["MaterialBindingAPI".to_string(), "SkelBindingAPI".to_string()]
);
Ok(())
}
#[test]
fn add_api_schema_explicit() -> Result<(), SpecError> {
let mut spec = Spec::new(sdf::SpecType::Prim);
spec.add(
sdf::FieldKey::ApiSchemas,
sdf::Value::TokenListOp(sdf::TokenListOp::explicit(["ExistingAPI".to_string()])),
);
let mut prim = spec.as_prim_mut().expect("prim spec");
assert!(prim.add_applied_schema("NewAPI")?);
let op = prim.api_schemas().expect("apiSchemas");
assert!(op.explicit);
assert_eq!(op.explicit_items, vec!["ExistingAPI".to_string(), "NewAPI".to_string()]);
Ok(())
}
#[test]
fn add_api_schema_keeps_add() -> Result<(), SpecError> {
let mut spec = Spec::new(sdf::SpecType::Prim);
spec.add(
sdf::FieldKey::ApiSchemas,
sdf::Value::TokenListOp(sdf::TokenListOp {
added_items: vec!["ExistingAPI".to_string()],
..Default::default()
}),
);
let mut prim = spec.as_prim_mut().expect("prim spec");
assert!(!prim.add_applied_schema("ExistingAPI")?);
let op = prim.api_schemas().expect("apiSchemas");
assert_eq!(op.added_items, vec!["ExistingAPI".to_string()]);
assert!(op.prepended_items.is_empty());
Ok(())
}
#[test]
fn add_api_schema_clears_delete() -> Result<(), SpecError> {
let mut spec = Spec::new(sdf::SpecType::Prim);
spec.add(
sdf::FieldKey::ApiSchemas,
sdf::Value::TokenListOp(sdf::TokenListOp {
deleted_items: vec!["RemovedAPI".to_string()],
..Default::default()
}),
);
let mut prim = spec.as_prim_mut().expect("prim spec");
assert!(prim.add_applied_schema("RemovedAPI")?);
let op = prim.api_schemas().expect("apiSchemas");
assert_eq!(op.prepended_items, vec!["RemovedAPI".to_string()]);
assert!(op.deleted_items.is_empty());
Ok(())
}
#[test]
fn add_api_schema_stale_added() -> Result<(), SpecError> {
let mut spec = Spec::new(sdf::SpecType::Prim);
spec.add(
sdf::FieldKey::ApiSchemas,
sdf::Value::TokenListOp(sdf::TokenListOp {
explicit: true,
added_items: vec!["StaleAPI".to_string()],
..Default::default()
}),
);
let mut prim = spec.as_prim_mut().expect("prim spec");
assert!(prim.add_applied_schema("StaleAPI")?);
let op = prim.api_schemas().expect("apiSchemas");
assert!(op.explicit);
assert_eq!(op.explicit_items, vec!["StaleAPI".to_string()]);
Ok(())
}
#[test]
fn add_api_schema_dup_delete() -> Result<(), SpecError> {
let mut spec = Spec::new(sdf::SpecType::Prim);
spec.add(
sdf::FieldKey::ApiSchemas,
sdf::Value::TokenListOp(sdf::TokenListOp {
deleted_items: vec!["RemovedAPI".to_string(), "RemovedAPI".to_string()],
..Default::default()
}),
);
let mut prim = spec.as_prim_mut().expect("prim spec");
assert!(prim.add_applied_schema("RemovedAPI")?);
let op = prim.api_schemas().expect("apiSchemas");
assert!(op.deleted_items.is_empty());
assert_eq!(op.prepended_items, vec!["RemovedAPI".to_string()]);
Ok(())
}
#[test]
fn add_api_schema_rejects_wrong_type() {
let mut spec = Spec::new(sdf::SpecType::Prim);
spec.add(
sdf::FieldKey::ApiSchemas,
sdf::Value::TokenVec(vec!["ExistingAPI".to_string()]),
);
let mut prim = spec.as_prim_mut().expect("prim spec");
assert!(matches!(
prim.add_applied_schema("NewAPI"),
Err(SpecError::FieldType {
field: "apiSchemas",
expected: "sdf::TokenListOp"
})
));
}
#[test]
fn attribute_mut_reads() {
let mut spec = Spec::new(sdf::SpecType::Attribute);
let mut attr = spec.as_attr_mut().expect("attribute spec");
attr.set_default(sdf::Value::Int(42));
attr.set_custom(true);
assert_eq!(attr.default(), Some(&sdf::Value::Int(42)));
assert!(attr.is_custom());
}
#[test]
fn relationship_mut_reads() {
let mut spec = Spec::new(sdf::SpecType::Relationship);
let mut rel = spec.as_relationship_mut().expect("relationship spec");
let target = sdf::Path::new("/Target").expect("valid path");
rel.add_target(target.clone());
assert_eq!(rel.target_path_list().and_then(|op| op.iter().next()), Some(&target));
}
#[test]
fn pseudo_root_mut_reads() {
let mut spec = Spec::new(sdf::SpecType::PseudoRoot);
let mut root = spec.as_pseudo_root_mut().expect("pseudo-root spec");
root.set_default_prim("World");
assert_eq!(root.default_prim(), Some("World"));
}
}