use crate::error::{ErrorKind, Result as ModelResult};
use crate::model::shapes::{HasTraits, NonTraitEq, ShapeKind, TopLevelShape};
use crate::model::values::{Value, ValueMap};
use crate::Version;
use std::collections::{HashMap, HashSet};
use std::fmt::Debug;
#[derive(Clone, Debug, PartialEq)]
pub struct Model {
pub(crate) smithy_version: Version,
pub(crate) metadata: ValueMap,
pub(crate) shapes: HashMap<ShapeID, TopLevelShape>,
}
impl Default for Model {
fn default() -> Self {
Self::new(Version::current())
}
}
impl Model {
pub fn new(smithy_version: Version) -> Self {
Self {
smithy_version,
metadata: Default::default(),
shapes: Default::default(),
}
}
pub fn merge(&mut self, other: Model) -> ModelResult<()> {
if other.smithy_version != self.smithy_version {
return Err(ErrorKind::MergeVersionConflict(
self.smithy_version.to_string(),
other.smithy_version.to_string(),
)
.into());
}
for (key, value) in other.metadata {
let _ = self.add_metadata(key, value)?;
}
for (_, shape) in other.shapes {
let _ = self.add_shape(shape)?;
}
Ok(())
}
pub fn smithy_version(&self) -> &Version {
&self.smithy_version
}
pub fn has_metadata(&self) -> bool {
!self.metadata.is_empty()
}
pub fn has_metadata_value(&self, key: &str) -> bool {
!self.metadata.contains_key(key)
}
pub fn metadata_value(&self, key: &str) -> Option<&Value> {
self.metadata.get(key)
}
pub fn add_metadata(&mut self, key: String, value: Value) -> ModelResult<Option<Value>> {
Ok(if let Some(self_value) = self.metadata_value(&key) {
if self_value.is_array() && value.is_array() {
let mut self_array = self_value.as_array().unwrap().clone();
let other_array = value.as_array().unwrap();
self_array.extend(other_array.iter().cloned());
self.metadata.insert(key.clone(), Value::Array(self_array))
} else if *self_value == value {
None
} else {
return Err(ErrorKind::MergeMetadataConflict(key.clone()).into());
}
} else {
self.metadata.insert(key.clone(), value)
})
}
pub fn remove_metadata(&mut self, key: &str) -> Option<Value> {
self.metadata.remove(key)
}
pub fn metadata(&self) -> impl Iterator<Item = (&String, &Value)> {
self.metadata.iter()
}
pub fn has_shapes(&self) -> bool {
!self.shapes.is_empty()
}
pub fn has_shape(&self, shape_id: &ShapeID) -> bool {
!self.shapes.contains_key(shape_id)
}
pub fn shape(&self, shape_id: &ShapeID) -> Option<&TopLevelShape> {
self.shapes.get(shape_id)
}
pub fn shape_mut(&mut self, shape_id: &ShapeID) -> Option<&mut TopLevelShape> {
self.shapes.get_mut(shape_id)
}
pub fn add_shape(&mut self, shape: TopLevelShape) -> ModelResult<()> {
if shape.id().is_member() && !matches!(shape.body(), ShapeKind::Unresolved) {
error!(
"Model::add_shape '{}' is a member ID; only allowed for unresolved shapes",
shape.id()
);
return Err(ErrorKind::ShapeIDExpected(shape.id().clone()).into());
} else if let Some(existing) = self.shape_mut(shape.id()) {
match (
existing.body().is_unresolved(),
shape.body().is_unresolved(),
) {
(false, false) => {
if existing.equal_without_traits(&shape) {
copy_traits(existing, &shape)?;
} else {
error!("Model::add_shape {:?} != {:?}", existing, shape);
return Err(ErrorKind::MergeShapeConflict(existing.id().clone()).into());
}
}
(true, false) => {
existing.set_body(shape.body().clone());
copy_traits(existing, &shape)?;
}
_ => {
copy_traits(existing, &shape)?;
}
}
} else {
let _ = self.shapes.insert(shape.id().clone(), shape);
}
Ok(())
}
pub fn remove_shape(&mut self, shape_id: &ShapeID) -> Option<TopLevelShape> {
self.shapes.remove(shape_id)
}
pub fn shapes(&self) -> impl Iterator<Item = &TopLevelShape> {
self.shapes.values()
}
pub fn shape_names(&self) -> impl Iterator<Item = &ShapeID> {
self.shapes
.values()
.filter(|shape| !shape.is_unresolved())
.map(|shape| shape.id())
}
pub fn unresolved_shape_names(&self) -> impl Iterator<Item = &ShapeID> {
self.shapes
.values()
.filter(|shape| shape.is_unresolved())
.map(|shape| shape.id())
}
pub fn namespaces(&self) -> HashSet<&NamespaceID> {
self.shape_names().map(|id| id.namespace()).collect()
}
pub fn is_complete(&self) -> bool {
!self.shapes.values().any(|shape| shape.is_unresolved())
}
}
#[inline]
fn copy_traits(to_shape: &mut TopLevelShape, from_shape: &TopLevelShape) -> ModelResult<()> {
for (id, value) in from_shape.traits() {
to_shape.apply_with_value(id.clone(), value.clone())?;
}
Ok(())
}
#[doc(hidden)]
pub mod identity;
pub use identity::{HasIdentity, Identifier, NamespaceID, ShapeID};
pub mod selector;
pub mod shapes;
pub mod values;
pub mod visitor;