use crate::{
dynamic_type::{DynamicPrimitiveKind, DynamicTypeAutoId, DynamicTypeExtensibility},
DdsError, DdsResult,
};
use std::collections::BTreeMap;
pub struct DynamicData {
value: DynamicValue,
schema: DynamicTypeSchema,
}
impl DynamicData {
pub fn new(schema: &DynamicTypeSchema) -> Self {
Self {
value: schema.default_value(),
schema: schema.clone(),
}
}
pub fn from_value(schema: &DynamicTypeSchema, value: DynamicValue) -> DdsResult<Self> {
value.validate_against(schema)?;
Ok(Self {
value,
schema: schema.clone(),
})
}
pub fn value(&self) -> &DynamicValue {
&self.value
}
pub fn value_mut(&mut self) -> &mut DynamicValue {
&mut self.value
}
pub fn schema(&self) -> &DynamicTypeSchema {
&self.schema
}
pub fn get_bool(&self, name: &str) -> DdsResult<bool> {
match self.value.field(name) {
Some(DynamicValue::Bool(b)) => Ok(*b),
Some(_) => Err(DdsError::BadParameter(format!(
"field '{}' is not a bool",
name
))),
None => Err(DdsError::BadParameter(format!(
"field '{}' not found",
name
))),
}
}
pub fn get_i32(&self, name: &str) -> DdsResult<i32> {
match self.value.field(name) {
Some(DynamicValue::Int32(v)) => Ok(*v),
Some(_) => Err(DdsError::BadParameter(format!(
"field '{}' is not an i32",
name
))),
None => Err(DdsError::BadParameter(format!(
"field '{}' not found",
name
))),
}
}
pub fn get_u32(&self, name: &str) -> DdsResult<u32> {
match self.value.field(name) {
Some(DynamicValue::UInt32(v)) => Ok(*v),
Some(_) => Err(DdsError::BadParameter(format!(
"field '{}' is not a u32",
name
))),
None => Err(DdsError::BadParameter(format!(
"field '{}' not found",
name
))),
}
}
pub fn get_i64(&self, name: &str) -> DdsResult<i64> {
match self.value.field(name) {
Some(DynamicValue::Int64(v)) => Ok(*v),
Some(_) => Err(DdsError::BadParameter(format!(
"field '{}' is not an i64",
name
))),
None => Err(DdsError::BadParameter(format!(
"field '{}' not found",
name
))),
}
}
pub fn get_u64(&self, name: &str) -> DdsResult<u64> {
match self.value.field(name) {
Some(DynamicValue::UInt64(v)) => Ok(*v),
Some(_) => Err(DdsError::BadParameter(format!(
"field '{}' is not a u64",
name
))),
None => Err(DdsError::BadParameter(format!(
"field '{}' not found",
name
))),
}
}
pub fn get_f32(&self, name: &str) -> DdsResult<f32> {
match self.value.field(name) {
Some(DynamicValue::Float32(v)) => Ok(*v),
Some(_) => Err(DdsError::BadParameter(format!(
"field '{}' is not an f32",
name
))),
None => Err(DdsError::BadParameter(format!(
"field '{}' not found",
name
))),
}
}
pub fn get_f64(&self, name: &str) -> DdsResult<f64> {
match self.value.field(name) {
Some(DynamicValue::Float64(v)) => Ok(*v),
Some(_) => Err(DdsError::BadParameter(format!(
"field '{}' is not an f64",
name
))),
None => Err(DdsError::BadParameter(format!(
"field '{}' not found",
name
))),
}
}
pub fn get_string(&self, name: &str) -> DdsResult<String> {
match self.value.field(name) {
Some(DynamicValue::String(s)) => Ok(s.clone()),
Some(_) => Err(DdsError::BadParameter(format!(
"field '{}' is not a string",
name
))),
None => Err(DdsError::BadParameter(format!(
"field '{}' not found",
name
))),
}
}
pub fn get_enum(&self, name: &str) -> DdsResult<i32> {
match self.value.field(name) {
Some(DynamicValue::Enum { value }) => Ok(*value),
Some(_) => Err(DdsError::BadParameter(format!(
"field '{}' is not an enum",
name
))),
None => Err(DdsError::BadParameter(format!(
"field '{}' not found",
name
))),
}
}
pub fn get_bitmask(&self, name: &str) -> DdsResult<u64> {
match self.value.field(name) {
Some(DynamicValue::Bitmask(v)) => Ok(*v),
Some(_) => Err(DdsError::BadParameter(format!(
"field '{}' is not a bitmask",
name
))),
None => Err(DdsError::BadParameter(format!(
"field '{}' not found",
name
))),
}
}
pub fn get_struct(&self, name: &str) -> DdsResult<DynamicData> {
let field_schema = self.schema.field(name).ok_or_else(|| {
DdsError::BadParameter(format!("field '{}' not found in schema", name))
})?;
match self.value.field(name) {
Some(DynamicValue::Struct(map)) => Ok(DynamicData {
value: DynamicValue::Struct(map.clone()),
schema: field_schema.value.clone(),
}),
Some(DynamicValue::Null) if field_schema.optional => {
Err(DdsError::BadParameter(format!(
"optional field '{}' is not set",
name
)))
}
Some(v) => Err(DdsError::BadParameter(format!(
"field '{}' is {:?}, expected struct",
name, v
))),
None => Err(DdsError::BadParameter(format!(
"field '{}' not found",
name
))),
}
}
pub fn get_sequence(&self, name: &str) -> DdsResult<Vec<DynamicValue>> {
match self.value.field(name) {
Some(DynamicValue::Sequence(elems)) => Ok(elems.clone()),
Some(DynamicValue::Array(elems)) => Ok(elems.clone()),
Some(_) => Err(DdsError::BadParameter(format!(
"field '{}' is not a sequence",
name
))),
None => Err(DdsError::BadParameter(format!(
"field '{}' not found",
name
))),
}
}
pub fn set_bool(&mut self, name: &str, value: bool) -> DdsResult<()> {
self.set_field(name, DynamicValue::Bool(value))
}
pub fn set_i32(&mut self, name: &str, value: i32) -> DdsResult<()> {
self.set_field(name, DynamicValue::Int32(value))
}
pub fn set_u32(&mut self, name: &str, value: u32) -> DdsResult<()> {
self.set_field(name, DynamicValue::UInt32(value))
}
pub fn set_i64(&mut self, name: &str, value: i64) -> DdsResult<()> {
self.set_field(name, DynamicValue::Int64(value))
}
pub fn set_u64(&mut self, name: &str, value: u64) -> DdsResult<()> {
self.set_field(name, DynamicValue::UInt64(value))
}
pub fn set_f32(&mut self, name: &str, value: f32) -> DdsResult<()> {
self.set_field(name, DynamicValue::Float32(value))
}
pub fn set_f64(&mut self, name: &str, value: f64) -> DdsResult<()> {
self.set_field(name, DynamicValue::Float64(value))
}
pub fn set_string(&mut self, name: &str, value: impl Into<String>) -> DdsResult<()> {
self.set_field(name, DynamicValue::String(value.into()))
}
pub fn set_enum(&mut self, name: &str, value: i32) -> DdsResult<()> {
self.set_field(name, DynamicValue::Enum { value })
}
pub fn set_bitmask(&mut self, name: &str, value: u64) -> DdsResult<()> {
self.set_field(name, DynamicValue::Bitmask(value))
}
pub fn set_null(&mut self, name: &str) -> DdsResult<()> {
let field = self.schema.field(name).ok_or_else(|| {
DdsError::BadParameter(format!("field '{}' not found in schema", name))
})?;
if !field.optional {
return Err(DdsError::BadParameter(format!(
"field '{}' is not optional, cannot set to null",
name
)));
}
self.value.set_field(&self.schema, name, DynamicValue::Null)
}
pub fn set_field(&mut self, name: &str, value: DynamicValue) -> DdsResult<()> {
self.value.set_field(&self.schema, name, value)
}
pub fn is_set(&self, name: &str) -> bool {
match self.value.field(name) {
Some(DynamicValue::Null) => false,
Some(_) => true,
None => false,
}
}
pub fn field_names(&self) -> Vec<&str> {
match &self.schema {
DynamicTypeSchema::Struct { fields, .. } => {
fields.iter().map(|f| f.name.as_str()).collect()
}
_ => Vec::new(),
}
}
pub fn validate(&self) -> DdsResult<()> {
self.value.validate_against(&self.schema)
}
}
impl std::fmt::Debug for DynamicData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DynamicData")
.field("schema", &self.schema)
.field("value", &self.value)
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DynamicFieldSchema {
pub name: String,
pub member_id: u32,
pub value: DynamicTypeSchema,
pub optional: bool,
pub key: bool,
pub external: bool,
pub must_understand: bool,
pub hash_id_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DynamicUnionCaseSchema {
pub name: String,
pub member_id: u32,
pub value: DynamicTypeSchema,
pub labels: Vec<i32>,
pub default: bool,
pub external: bool,
pub hash_id_name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DynamicEnumLiteralSchema {
pub name: String,
pub value: i32,
pub default: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DynamicBitmaskFieldSchema {
pub name: String,
pub position: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DynamicTypeSchema {
Primitive(DynamicPrimitiveKind),
String {
bound: Option<u32>,
},
Sequence {
name: String,
bound: Option<u32>,
element: Box<DynamicTypeSchema>,
},
Array {
name: String,
bounds: Vec<u32>,
element: Box<DynamicTypeSchema>,
},
Struct {
name: String,
base: Option<Box<DynamicTypeSchema>>,
fields: Vec<DynamicFieldSchema>,
extensibility: Option<DynamicTypeExtensibility>,
autoid: Option<DynamicTypeAutoId>,
nested: bool,
},
Enum {
name: String,
literals: Vec<DynamicEnumLiteralSchema>,
bit_bound: Option<u16>,
},
Bitmask {
name: String,
fields: Vec<DynamicBitmaskFieldSchema>,
bit_bound: Option<u16>,
},
Alias {
name: String,
target: Box<DynamicTypeSchema>,
},
Union {
name: String,
discriminator: Box<DynamicTypeSchema>,
cases: Vec<DynamicUnionCaseSchema>,
autoid: Option<DynamicTypeAutoId>,
nested: bool,
},
Map {
name: String,
bound: Option<u32>,
key: Box<DynamicTypeSchema>,
value: Box<DynamicTypeSchema>,
},
}
impl DynamicTypeSchema {
pub fn default_value(&self) -> DynamicValue {
match self {
DynamicTypeSchema::Primitive(kind) => match kind {
DynamicPrimitiveKind::Boolean => DynamicValue::Bool(false),
DynamicPrimitiveKind::Byte => DynamicValue::Byte(0),
DynamicPrimitiveKind::Int8 => DynamicValue::Int8(0),
DynamicPrimitiveKind::UInt8 => DynamicValue::UInt8(0),
DynamicPrimitiveKind::Int16 => DynamicValue::Int16(0),
DynamicPrimitiveKind::UInt16 => DynamicValue::UInt16(0),
DynamicPrimitiveKind::Int32 => DynamicValue::Int32(0),
DynamicPrimitiveKind::UInt32 => DynamicValue::UInt32(0),
DynamicPrimitiveKind::Int64 => DynamicValue::Int64(0),
DynamicPrimitiveKind::UInt64 => DynamicValue::UInt64(0),
DynamicPrimitiveKind::Float32 => DynamicValue::Float32(0.0),
DynamicPrimitiveKind::Float64 => DynamicValue::Float64(0.0),
DynamicPrimitiveKind::Char8 => DynamicValue::Char8(0),
DynamicPrimitiveKind::Char16 => DynamicValue::Char16(0),
},
DynamicTypeSchema::String { .. } => DynamicValue::String(String::new()),
DynamicTypeSchema::Sequence { .. } => DynamicValue::Sequence(Vec::new()),
DynamicTypeSchema::Array {
bounds, element, ..
} => {
let len = bounds
.iter()
.copied()
.fold(1usize, |acc, b| acc.saturating_mul(b as usize));
DynamicValue::Array((0..len).map(|_| element.default_value()).collect())
}
DynamicTypeSchema::Struct { fields, base, .. } => {
let mut values = match base.as_deref() {
Some(DynamicTypeSchema::Struct { .. }) => {
match base.as_ref().unwrap().default_value() {
DynamicValue::Struct(map) => map,
_ => BTreeMap::new(),
}
}
_ => BTreeMap::new(),
};
for field in fields {
values.insert(
field.name.clone(),
if field.optional {
DynamicValue::Null
} else {
field.value.default_value()
},
);
}
DynamicValue::Struct(values)
}
DynamicTypeSchema::Enum { literals, .. } => {
let value = literals
.iter()
.find(|literal| literal.default)
.or_else(|| literals.first())
.map(|literal| literal.value)
.unwrap_or(0);
DynamicValue::Enum { value }
}
DynamicTypeSchema::Bitmask { .. } => DynamicValue::Bitmask(0),
DynamicTypeSchema::Alias { target, .. } => target.default_value(),
DynamicTypeSchema::Union { cases, .. } => {
if let Some(case) = cases
.iter()
.find(|case| case.default)
.or_else(|| cases.first())
{
let discriminator = case.labels.first().copied().unwrap_or_default();
DynamicValue::Union {
discriminator,
field: case.name.clone(),
value: Box::new(case.value.default_value()),
}
} else {
DynamicValue::Null
}
}
DynamicTypeSchema::Map { .. } => DynamicValue::Map(Vec::new()),
}
}
pub fn field(&self, name: &str) -> Option<&DynamicFieldSchema> {
match self {
DynamicTypeSchema::Struct { fields, base, .. } => fields
.iter()
.find(|field| field.name == name)
.or_else(|| base.as_deref().and_then(|base| base.field(name))),
_ => None,
}
}
pub fn union_case(&self, name: &str) -> Option<&DynamicUnionCaseSchema> {
match self {
DynamicTypeSchema::Union { cases, .. } => cases.iter().find(|case| case.name == name),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum DynamicValue {
Bool(bool),
Byte(u8),
Int8(i8),
UInt8(u8),
Int16(i16),
UInt16(u16),
Int32(i32),
UInt32(u32),
Int64(i64),
UInt64(u64),
Float32(f32),
Float64(f64),
Char8(u8),
Char16(u16),
String(String),
Sequence(Vec<DynamicValue>),
Array(Vec<DynamicValue>),
Struct(BTreeMap<String, DynamicValue>),
Enum {
value: i32,
},
Bitmask(u64),
Union {
discriminator: i32,
field: String,
value: Box<DynamicValue>,
},
Map(Vec<(DynamicValue, DynamicValue)>),
Null,
}
impl DynamicValue {
pub fn new(schema: &DynamicTypeSchema) -> Self {
schema.default_value()
}
pub fn as_struct(&self) -> Option<&BTreeMap<String, DynamicValue>> {
match self {
DynamicValue::Struct(fields) => Some(fields),
_ => None,
}
}
pub fn as_struct_mut(&mut self) -> Option<&mut BTreeMap<String, DynamicValue>> {
match self {
DynamicValue::Struct(fields) => Some(fields),
_ => None,
}
}
pub fn field(&self, name: &str) -> Option<&DynamicValue> {
self.as_struct()?.get(name)
}
pub fn set_field(
&mut self,
schema: &DynamicTypeSchema,
name: &str,
value: DynamicValue,
) -> DdsResult<()> {
let field = schema.field(name).ok_or_else(|| {
DdsError::BadParameter(format!("field '{}' not present in dynamic schema", name))
})?;
value.validate_against(&field.value)?;
let fields = self
.as_struct_mut()
.ok_or_else(|| DdsError::BadParameter("dynamic value is not a struct".into()))?;
fields.insert(name.to_string(), value);
Ok(())
}
pub fn as_sequence(&self) -> Option<&Vec<DynamicValue>> {
match self {
DynamicValue::Sequence(values) | DynamicValue::Array(values) => Some(values),
_ => None,
}
}
pub fn as_sequence_mut(&mut self) -> Option<&mut Vec<DynamicValue>> {
match self {
DynamicValue::Sequence(values) | DynamicValue::Array(values) => Some(values),
_ => None,
}
}
pub fn push(&mut self, schema: &DynamicTypeSchema, value: DynamicValue) -> DdsResult<()> {
let (bound, element_schema) = match schema {
DynamicTypeSchema::Sequence { bound, element, .. } => (*bound, element.as_ref()),
DynamicTypeSchema::Map { .. } | DynamicTypeSchema::Array { .. } => {
return Err(DdsError::BadParameter(
"push is only supported for dynamic sequences".into(),
))
}
_ => {
return Err(DdsError::BadParameter(
"dynamic value is not backed by a sequence schema".into(),
))
}
};
value.validate_against(element_schema)?;
let values = self
.as_sequence_mut()
.ok_or_else(|| DdsError::BadParameter("dynamic value is not a sequence".into()))?;
if let Some(bound) = bound {
if values.len() >= bound as usize {
return Err(DdsError::BadParameter(format!(
"sequence already at bound {}",
bound
)));
}
}
values.push(value);
Ok(())
}
pub fn union(
schema: &DynamicTypeSchema,
discriminator: i32,
field: impl Into<String>,
value: DynamicValue,
) -> DdsResult<Self> {
let field = field.into();
let case = schema.union_case(&field).ok_or_else(|| {
DdsError::BadParameter(format!("union field '{}' not present in schema", field))
})?;
value.validate_against(&case.value)?;
let union_value = DynamicValue::Union {
discriminator,
field,
value: Box::new(value),
};
union_value.validate_against(schema)?;
Ok(union_value)
}
pub fn union_discriminator(&self) -> Option<i32> {
match self {
DynamicValue::Union { discriminator, .. } => Some(*discriminator),
_ => None,
}
}
pub fn union_field_name(&self) -> Option<&str> {
match self {
DynamicValue::Union { field, .. } => Some(field.as_str()),
_ => None,
}
}
pub fn union_field_value(&self) -> Option<&DynamicValue> {
match self {
DynamicValue::Union { value, .. } => Some(value.as_ref()),
_ => None,
}
}
pub fn validate_against(&self, schema: &DynamicTypeSchema) -> DdsResult<()> {
match (self, schema) {
(
DynamicValue::Bool(_),
DynamicTypeSchema::Primitive(DynamicPrimitiveKind::Boolean),
) => Ok(()),
(DynamicValue::Byte(_), DynamicTypeSchema::Primitive(DynamicPrimitiveKind::Byte)) => {
Ok(())
}
(DynamicValue::Int8(_), DynamicTypeSchema::Primitive(DynamicPrimitiveKind::Int8)) => {
Ok(())
}
(DynamicValue::UInt8(_), DynamicTypeSchema::Primitive(DynamicPrimitiveKind::UInt8)) => {
Ok(())
}
(DynamicValue::Int16(_), DynamicTypeSchema::Primitive(DynamicPrimitiveKind::Int16)) => {
Ok(())
}
(
DynamicValue::UInt16(_),
DynamicTypeSchema::Primitive(DynamicPrimitiveKind::UInt16),
) => Ok(()),
(DynamicValue::Int32(_), DynamicTypeSchema::Primitive(DynamicPrimitiveKind::Int32)) => {
Ok(())
}
(
DynamicValue::UInt32(_),
DynamicTypeSchema::Primitive(DynamicPrimitiveKind::UInt32),
) => Ok(()),
(DynamicValue::Int64(_), DynamicTypeSchema::Primitive(DynamicPrimitiveKind::Int64)) => {
Ok(())
}
(
DynamicValue::UInt64(_),
DynamicTypeSchema::Primitive(DynamicPrimitiveKind::UInt64),
) => Ok(()),
(
DynamicValue::Float32(_),
DynamicTypeSchema::Primitive(DynamicPrimitiveKind::Float32),
) => Ok(()),
(
DynamicValue::Float64(_),
DynamicTypeSchema::Primitive(DynamicPrimitiveKind::Float64),
) => Ok(()),
(DynamicValue::Char8(_), DynamicTypeSchema::Primitive(DynamicPrimitiveKind::Char8)) => {
Ok(())
}
(
DynamicValue::Char16(_),
DynamicTypeSchema::Primitive(DynamicPrimitiveKind::Char16),
) => Ok(()),
(DynamicValue::String(value), DynamicTypeSchema::String { bound }) => {
if let Some(bound) = bound {
if value.len() > *bound as usize {
return Err(DdsError::BadParameter(format!(
"string length {} exceeds bound {}",
value.len(),
bound
)));
}
}
Ok(())
}
(
DynamicValue::Sequence(values),
DynamicTypeSchema::Sequence { bound, element, .. },
) => {
if let Some(bound) = bound {
if values.len() > *bound as usize {
return Err(DdsError::BadParameter(format!(
"sequence length {} exceeds bound {}",
values.len(),
bound
)));
}
}
for value in values {
value.validate_against(element)?;
}
Ok(())
}
(
DynamicValue::Array(values),
DynamicTypeSchema::Array {
bounds, element, ..
},
) => {
let expected_len = bounds
.iter()
.copied()
.fold(1usize, |acc, b| acc.saturating_mul(b as usize));
if values.len() != expected_len {
return Err(DdsError::BadParameter(format!(
"array value length {} does not match expected flattened length {}",
values.len(),
expected_len
)));
}
for value in values {
value.validate_against(element)?;
}
Ok(())
}
(DynamicValue::Struct(values), DynamicTypeSchema::Struct { fields, base, .. }) => {
if let Some(base) = base {
self.validate_against(base)?;
}
for field in fields {
match values.get(&field.name) {
Some(value) => {
if field.optional && matches!(value, DynamicValue::Null) {
continue;
}
value.validate_against(&field.value)?;
}
None if field.optional => {}
None => {
return Err(DdsError::BadParameter(format!(
"missing required dynamic field '{}'",
field.name
)))
}
}
}
Ok(())
}
(
DynamicValue::Enum { value },
DynamicTypeSchema::Enum {
literals,
bit_bound,
..
},
) => {
if !literals.iter().any(|literal| literal.value == *value) {
return Err(DdsError::BadParameter(format!(
"enum value {} not defined in schema",
value
)));
}
if let Some(bit_bound) = bit_bound {
if *bit_bound < 32 {
let max = if *bit_bound == 31 {
i32::MAX as i64
} else {
(1i64 << *bit_bound) - 1
};
if (*value as i64) > max {
return Err(DdsError::BadParameter(format!(
"enum value {} exceeds bit bound {}",
value, bit_bound
)));
}
}
}
Ok(())
}
(DynamicValue::Bitmask(value), DynamicTypeSchema::Bitmask { bit_bound, .. }) => {
if let Some(bit_bound) = bit_bound {
if *bit_bound < 64 && *value >= (1u64 << *bit_bound) {
return Err(DdsError::BadParameter(format!(
"bitmask value {} exceeds bit bound {}",
value, bit_bound
)));
}
}
Ok(())
}
(
DynamicValue::Union {
discriminator,
field,
value,
},
DynamicTypeSchema::Union { cases, .. },
) => {
let case = cases
.iter()
.find(|case| case.name == *field)
.ok_or_else(|| {
DdsError::BadParameter(format!(
"union field '{}' not defined in schema",
field
))
})?;
let label_match = case.default || case.labels.contains(discriminator);
if !label_match {
return Err(DdsError::BadParameter(format!(
"union discriminator {} does not match field '{}'",
discriminator, field
)));
}
value.validate_against(&case.value)
}
(
DynamicValue::Map(entries),
DynamicTypeSchema::Map {
bound, key, value, ..
},
) => {
if let Some(bound) = bound {
if entries.len() > *bound as usize {
return Err(DdsError::BadParameter(format!(
"map length {} exceeds bound {}",
entries.len(),
bound
)));
}
}
for (k, v) in entries {
k.validate_against(key)?;
v.validate_against(value)?;
}
Ok(())
}
(value, DynamicTypeSchema::Alias { target, .. }) => value.validate_against(target),
(DynamicValue::Null, _) => Ok(()),
(value, schema) => Err(DdsError::BadParameter(format!(
"dynamic value {value:?} does not match schema {schema:?}"
))),
}
}
}