use std::sync::Arc;
use super::error::DynamicError;
use super::value::DynamicValue;
#[derive(Clone, Debug, PartialEq)]
pub enum FieldType {
Bool,
Int8,
Int16,
Int32,
Int64,
Uint8,
Uint16,
Uint32,
Uint64,
Float32,
Float64,
String,
BoundedString(usize),
Message(Arc<MessageSchema>),
Array(Box<FieldType>, usize),
Sequence(Box<FieldType>),
BoundedSequence(Box<FieldType>, usize),
}
impl FieldType {
pub fn fixed_size(&self) -> Option<usize> {
match self {
FieldType::Bool | FieldType::Int8 | FieldType::Uint8 => Some(1),
FieldType::Int16 | FieldType::Uint16 => Some(2),
FieldType::Int32 | FieldType::Uint32 | FieldType::Float32 => Some(4),
FieldType::Int64 | FieldType::Uint64 | FieldType::Float64 => Some(8),
FieldType::Array(inner, len) => inner.fixed_size().map(|s| s * len),
FieldType::Message(schema) => schema.fixed_cdr_size(),
_ => None,
}
}
pub fn alignment(&self) -> usize {
match self {
FieldType::Bool | FieldType::Int8 | FieldType::Uint8 => 1,
FieldType::Int16 | FieldType::Uint16 => 2,
FieldType::Int32 | FieldType::Uint32 | FieldType::Float32 => 4,
FieldType::Int64 | FieldType::Uint64 | FieldType::Float64 => 8,
FieldType::String | FieldType::BoundedString(_) => 4, FieldType::Array(inner, _) => inner.alignment(),
FieldType::Sequence(_) | FieldType::BoundedSequence(_, _) => 4, FieldType::Message(schema) => schema.alignment(),
}
}
pub fn is_primitive(&self) -> bool {
matches!(
self,
FieldType::Bool
| FieldType::Int8
| FieldType::Int16
| FieldType::Int32
| FieldType::Int64
| FieldType::Uint8
| FieldType::Uint16
| FieldType::Uint32
| FieldType::Uint64
| FieldType::Float32
| FieldType::Float64
| FieldType::String
| FieldType::BoundedString(_)
)
}
pub fn is_numeric(&self) -> bool {
matches!(
self,
FieldType::Int8
| FieldType::Int16
| FieldType::Int32
| FieldType::Int64
| FieldType::Uint8
| FieldType::Uint16
| FieldType::Uint32
| FieldType::Uint64
| FieldType::Float32
| FieldType::Float64
)
}
pub fn element_type(&self) -> Option<&FieldType> {
match self {
FieldType::Array(inner, _)
| FieldType::Sequence(inner)
| FieldType::BoundedSequence(inner, _) => Some(inner),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct FieldSchema {
pub name: String,
pub field_type: FieldType,
pub default_value: Option<DynamicValue>,
}
impl FieldSchema {
pub fn new(name: impl Into<String>, field_type: FieldType) -> Self {
Self {
name: name.into(),
field_type,
default_value: None,
}
}
pub fn with_default(mut self, value: DynamicValue) -> Self {
self.default_value = Some(value);
self
}
}
#[derive(Clone, Debug)]
pub struct MessageSchema {
pub type_name: String,
pub package: String,
pub name: String,
pub fields: Vec<FieldSchema>,
pub type_hash: Option<String>,
}
impl MessageSchema {
pub fn field(&self, name: &str) -> Option<&FieldSchema> {
self.fields.iter().find(|f| f.name == name)
}
pub fn field_index(&self, name: &str) -> Option<usize> {
self.fields.iter().position(|f| f.name == name)
}
pub fn field_path_indices(&self, path: &str) -> Result<Vec<usize>, DynamicError> {
let mut indices = Vec::new();
let mut current_schema = self;
for part in path.split('.') {
let idx = current_schema
.field_index(part)
.ok_or_else(|| DynamicError::FieldNotFound(part.to_string()))?;
indices.push(idx);
if let FieldType::Message(nested) = ¤t_schema.fields[idx].field_type {
current_schema = nested;
}
}
Ok(indices)
}
pub fn fixed_cdr_size(&self) -> Option<usize> {
let mut size = 0usize;
for field in &self.fields {
let field_size = field.field_type.fixed_size()?;
let align = field.field_type.alignment();
size = (size + align - 1) & !(align - 1);
size += field_size;
}
Some(size)
}
pub fn alignment(&self) -> usize {
self.fields
.iter()
.map(|f| f.field_type.alignment())
.max()
.unwrap_or(1)
}
pub fn builder(type_name: &str) -> MessageSchemaBuilder {
MessageSchemaBuilder::new(type_name)
}
pub fn field_count(&self) -> usize {
self.fields.len()
}
pub fn field_names(&self) -> impl Iterator<Item = &str> {
self.fields.iter().map(|f| f.name.as_str())
}
}
impl PartialEq for MessageSchema {
fn eq(&self, other: &Self) -> bool {
self.type_name == other.type_name
}
}
pub struct MessageSchemaBuilder {
type_name: String,
fields: Vec<FieldSchema>,
type_hash: Option<String>,
}
impl MessageSchemaBuilder {
pub fn new(type_name: &str) -> Self {
Self {
type_name: type_name.to_string(),
fields: Vec::new(),
type_hash: None,
}
}
pub fn field(mut self, name: &str, field_type: FieldType) -> Self {
self.fields.push(FieldSchema::new(name, field_type));
self
}
pub fn field_with_default(
mut self,
name: &str,
field_type: FieldType,
default: DynamicValue,
) -> Self {
self.fields
.push(FieldSchema::new(name, field_type).with_default(default));
self
}
pub fn type_hash(mut self, hash: impl Into<String>) -> Self {
self.type_hash = Some(hash.into());
self
}
pub fn build(self) -> Result<Arc<MessageSchema>, DynamicError> {
let parts: Vec<&str> = self.type_name.split('/').collect();
if parts.len() != 3 || parts[1] != "msg" {
return Err(DynamicError::InvalidTypeName(self.type_name));
}
Ok(Arc::new(MessageSchema {
type_name: self.type_name.clone(),
package: parts[0].to_string(),
name: parts[2].to_string(),
fields: self.fields,
type_hash: self.type_hash,
}))
}
}