#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Dataspace {
pub uri: String,
pub name: String,
pub created: Option<String>,
pub last_changed: Option<String>,
pub store_created: Option<i64>,
pub store_last_write: Option<i64>,
pub custom_data: std::collections::BTreeMap<String, CustomValue>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CustomValue {
Text(String),
Texts(Vec<String>),
Bool(bool),
Integer(i64),
}
impl Dataspace {
pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
Self {
uri: uri.into(),
name: name.into(),
created: None,
last_changed: None,
store_created: None,
store_last_write: None,
custom_data: std::collections::BTreeMap::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ActiveStatus {
Active,
Inactive,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ObjectSummary {
pub uri: String,
pub uuid: String,
pub qualified_type: String,
pub content_type: String,
pub locator: String,
pub alternate_uris: Vec<String>,
pub store_created: Option<i64>,
pub store_last_write: Option<i64>,
pub active_status: Option<ActiveStatus>,
pub source_count: Option<u32>,
pub target_count: Option<u32>,
pub contained_in: Vec<String>,
}
impl ObjectSummary {
pub fn new(uri: impl Into<String>, uuid: impl Into<String>, qualified_type: impl Into<String>) -> Self {
Self {
uri: uri.into(),
uuid: uuid.into(),
qualified_type: qualified_type.into(),
content_type: String::new(),
locator: String::new(),
alternate_uris: Vec::new(),
store_created: None,
store_last_write: None,
active_status: None,
source_count: None,
target_count: None,
contained_in: Vec::new(),
}
}
pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
self.content_type = content_type.into();
self
}
pub fn with_locator(mut self, locator: impl Into<String>) -> Self {
self.locator = locator.into();
self
}
pub fn with_contained_in(mut self, mut containers: Vec<String>) -> Self {
containers.sort();
containers.dedup();
self.contained_in = containers;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ObjectHeader {
pub summary: ObjectSummary,
pub title: Option<String>,
pub originator: Option<String>,
pub creation: Option<String>,
pub last_update: Option<String>,
pub format: Option<String>,
pub object_version: Option<String>,
pub schema_version: Option<String>,
}
impl ObjectHeader {
pub fn new(summary: ObjectSummary) -> Self {
Self {
summary,
title: None,
originator: None,
creation: None,
last_update: None,
format: None,
object_version: None,
schema_version: None,
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn uri(&self) -> &str {
&self.summary.uri
}
pub fn display_name(&self) -> &str {
match self.title.as_deref() {
Some(title) if !title.trim().is_empty() => title,
_ => &self.summary.uuid,
}
}
pub fn titled(&self) -> String {
match self
.object_version
.as_deref()
.map(str::trim)
.filter(|version| !version.is_empty())
{
Some(version) => format!("{} (version {version})", self.display_name()),
None => self.display_name().to_string(),
}
}
pub fn label(&self) -> String {
match energyml_core::identity::ObjectId::from_qualified_type(
&self.summary.qualified_type,
&self.summary.uuid,
) {
Ok(mut id) => {
id.object_version = self.object_version.clone();
id.label(self.title.as_deref())
}
Err(_) => format!("{} - {}", self.summary.qualified_type, self.display_name()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Relationship {
pub id: String,
pub kind: String,
pub target: String,
pub external: bool,
}
impl Relationship {
pub fn short_kind(&self) -> &str {
self.kind.rsplit('/').next().unwrap_or(&self.kind)
}
pub fn is_external_resource(&self) -> bool {
self.external || self.short_kind() == "externalResource"
}
}
pub fn type_matches(qualified_type: &str, filter: &str) -> bool {
qualified_type.to_lowercase().contains(&filter.to_lowercase())
}
pub fn title_matches(title: &str, filter: &str) -> bool {
title.to_lowercase().contains(&filter.to_lowercase())
}
#[cfg(test)]
mod tests {
use super::*;
fn header(title: Option<&str>, version: Option<&str>) -> ObjectHeader {
let mut header = ObjectHeader::new(ObjectSummary::new(
"eml:///resqml22.BoundaryFeature(7f000000-0000-4000-8000-000000000001)",
"7f000000-0000-4000-8000-000000000001",
"resqml22.BoundaryFeature",
));
header.title = title.map(str::to_string);
header.object_version = version.map(str::to_string);
header
}
#[test]
fn a_view_shows_the_version_when_the_object_declares_one() {
let versioned = header(Some("Top Brent"), Some("2"));
assert_eq!(
versioned.label(),
"resqml22.BoundaryFeature - Top Brent (version 2)"
);
assert_eq!(versioned.titled(), "Top Brent (version 2)");
}
#[test]
fn no_version_a_blank_one_or_no_title_changes_nothing_but_what_is_missing() {
for none in [None, Some(""), Some(" ")] {
let plain = header(Some("Top Brent"), none);
assert_eq!(plain.label(), "resqml22.BoundaryFeature - Top Brent", "{none:?}");
assert_eq!(plain.titled(), "Top Brent", "{none:?}");
}
let untitled = header(None, Some("1"));
assert_eq!(
untitled.titled(),
"7f000000-0000-4000-8000-000000000001 (version 1)"
);
}
}