#[cfg(feature = "alloc")]
use alloc::{collections::BTreeMap, vec::Vec};
use smol_str::SmolStr;
use crate::value::Value;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct ThingId(pub SmolStr);
impl ThingId {
pub fn new(s: impl Into<SmolStr>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn split(&self) -> Option<(&str, &str)> {
let (ns, name) = self.0.as_str().split_once(':')?;
if ns.is_empty() || name.is_empty() {
return None;
}
Some((ns, name))
}
pub fn is_valid(&self) -> bool {
self.split().is_some()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct FeatureId(pub SmolStr);
impl FeatureId {
pub fn new(s: impl Into<SmolStr>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct PropertyId(pub SmolStr);
impl PropertyId {
pub fn new(s: impl Into<SmolStr>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct PolicyId(pub SmolStr);
impl PolicyId {
pub fn new(s: impl Into<SmolStr>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct DefinitionIdentifier(pub SmolStr);
impl DefinitionIdentifier {
pub fn new(s: impl Into<SmolStr>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Feature {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Vec::is_empty")
)]
pub definition: Vec<DefinitionIdentifier>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "BTreeMap::is_empty")
)]
pub properties: BTreeMap<PropertyId, Value>,
#[cfg_attr(
feature = "serde",
serde(
default,
skip_serializing_if = "BTreeMap::is_empty",
rename = "desiredProperties"
)
)]
pub desired_properties: BTreeMap<PropertyId, Value>,
}
#[cfg(feature = "alloc")]
impl Feature {
pub fn new() -> Self {
Self::default()
}
pub fn with_property(mut self, id: PropertyId, value: Value) -> Self {
self.properties.insert(id, value);
self
}
pub fn property(&self, id: &PropertyId) -> Option<&Value> {
self.properties.get(id)
}
}
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Thing {
#[cfg_attr(feature = "serde", serde(rename = "thingId"))]
pub thing_id: ThingId,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none", rename = "policyId")
)]
pub policy_id: Option<PolicyId>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "BTreeMap::is_empty")
)]
pub attributes: BTreeMap<SmolStr, Value>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "BTreeMap::is_empty")
)]
pub features: BTreeMap<FeatureId, Feature>,
}
#[cfg(feature = "alloc")]
impl Thing {
pub fn new(thing_id: ThingId) -> Self {
Self {
thing_id,
policy_id: None,
attributes: BTreeMap::new(),
features: BTreeMap::new(),
}
}
pub fn with_feature(mut self, id: FeatureId, feature: Feature) -> Self {
self.features.insert(id, feature);
self
}
pub fn feature(&self, id: &FeatureId) -> Option<&Feature> {
self.features.get(id)
}
pub fn feature_mut_or_default(&mut self, id: FeatureId) -> &mut Feature {
self.features.entry(id).or_default()
}
}
#[cfg(test)]
#[cfg(feature = "alloc")]
mod tests {
use super::*;
#[test]
fn thing_id_split() {
let id = ThingId::new("acme:pump-7");
assert!(id.is_valid());
assert_eq!(id.split(), Some(("acme", "pump-7")));
let bad = ThingId::new("no-colon");
assert!(!bad.is_valid());
}
#[test]
fn build_thing() {
let t = Thing::new(ThingId::new("acme:pump-7")).with_feature(
FeatureId::new("temperature"),
Feature::new().with_property(PropertyId::new("value"), 23.5.into()),
);
assert_eq!(
t.feature(&FeatureId::new("temperature"))
.and_then(|f| f.property(&PropertyId::new("value")))
.and_then(Value::as_float),
Some(23.5)
);
}
}