use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub struct SchemaId(pub u32);
impl SchemaId {
pub fn value(self) -> u32 {
self.0
}
}
impl std::fmt::Display for SchemaId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SchemaId({})", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub struct SchemaVersion(pub u32);
impl SchemaVersion {
pub fn value(self) -> u32 {
self.0
}
}
impl std::fmt::Display for SchemaVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SchemaVersion({})", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum FieldType {
Int32,
Int64,
UInt32,
UInt64,
Float,
Double,
Bool,
String,
Bytes,
Message(std::string::String),
Repeated(Box<FieldType>),
}
impl FieldType {
pub fn proto_name(&self) -> std::string::String {
match self {
FieldType::Int32 => "int32".to_string(),
FieldType::Int64 => "int64".to_string(),
FieldType::UInt32 => "uint32".to_string(),
FieldType::UInt64 => "uint64".to_string(),
FieldType::Float => "float".to_string(),
FieldType::Double => "double".to_string(),
FieldType::Bool => "bool".to_string(),
FieldType::String => "string".to_string(),
FieldType::Bytes => "bytes".to_string(),
FieldType::Message(name) => format!("message({name})"),
FieldType::Repeated(inner) => format!("repeated {}", inner.proto_name()),
}
}
pub fn is_varint(&self) -> bool {
matches!(
self,
FieldType::Int32
| FieldType::Int64
| FieldType::UInt32
| FieldType::UInt64
| FieldType::Bool
)
}
pub fn is_length_delimited(&self) -> bool {
matches!(
self,
FieldType::String | FieldType::Bytes | FieldType::Message(_) | FieldType::Repeated(_)
)
}
}
impl std::fmt::Display for FieldType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.proto_name())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FieldDescriptor {
pub field_number: u32,
pub name: std::string::String,
pub field_type: FieldType,
pub required: bool,
}
impl FieldDescriptor {
pub fn optional(
field_number: u32,
name: impl Into<std::string::String>,
field_type: FieldType,
) -> Self {
Self {
field_number,
name: name.into(),
field_type,
required: false,
}
}
pub fn required(
field_number: u32,
name: impl Into<std::string::String>,
field_type: FieldType,
) -> Self {
Self {
field_number,
name: name.into(),
field_type,
required: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MessageDescriptor {
pub name: std::string::String,
pub package: std::string::String,
pub fields: Vec<FieldDescriptor>,
}
impl MessageDescriptor {
pub fn new(
name: impl Into<std::string::String>,
package: impl Into<std::string::String>,
) -> Self {
Self {
name: name.into(),
package: package.into(),
fields: Vec::new(),
}
}
pub fn with_field(mut self, field: FieldDescriptor) -> Self {
self.fields.push(field);
self
}
pub fn fully_qualified_name(&self) -> std::string::String {
if self.package.is_empty() {
self.name.clone()
} else {
format!("{}.{}", self.package, self.name)
}
}
pub fn field_by_number(&self, number: u32) -> Option<&FieldDescriptor> {
self.fields.iter().find(|f| f.field_number == number)
}
pub fn field_by_name(&self, name: &str) -> Option<&FieldDescriptor> {
self.fields.iter().find(|f| f.name == name)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Schema {
pub id: SchemaId,
pub version: SchemaVersion,
pub descriptor: MessageDescriptor,
pub created_at: u64,
}
impl Schema {
pub fn new(
id: SchemaId,
version: SchemaVersion,
descriptor: MessageDescriptor,
created_at: u64,
) -> Self {
Self {
id,
version,
descriptor,
created_at,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RegistryConfig {
pub max_schemas: usize,
pub allow_schema_evolution: bool,
}
impl Default for RegistryConfig {
fn default() -> Self {
Self {
max_schemas: 1_000,
allow_schema_evolution: true,
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum SchemaRegistryError {
NotFound(SchemaId),
VersionConflict,
IncompatibleEvolution(std::string::String),
Serialization(std::string::String),
RegistryFull,
VersionNotFound {
id: SchemaId,
version: SchemaVersion,
},
WireFormat(std::string::String),
Validation(std::string::String),
}
impl std::fmt::Display for SchemaRegistryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SchemaRegistryError::NotFound(id) => write!(f, "Schema not found: {id}"),
SchemaRegistryError::VersionConflict => {
write!(
f,
"Schema version conflict: evolution disabled or non-monotonic version"
)
}
SchemaRegistryError::IncompatibleEvolution(msg) => {
write!(f, "Incompatible schema evolution: {msg}")
}
SchemaRegistryError::Serialization(msg) => write!(f, "Serialization error: {msg}"),
SchemaRegistryError::RegistryFull => write!(f, "Schema registry is full"),
SchemaRegistryError::VersionNotFound { id, version } => {
write!(f, "Version {version} not found for schema {id}")
}
SchemaRegistryError::WireFormat(msg) => write!(f, "Wire format error: {msg}"),
SchemaRegistryError::Validation(msg) => write!(f, "Validation error: {msg}"),
}
}
}
impl std::error::Error for SchemaRegistryError {}
pub type SchemaRegistryResult<T> = Result<T, SchemaRegistryError>;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum FieldValue {
Int32(i32),
Int64(i64),
UInt32(u32),
UInt64(u64),
Float(f32),
Double(f64),
Bool(bool),
Str(std::string::String),
Bytes(Vec<u8>),
Message(Vec<u8>),
}
impl FieldValue {
pub fn field_type(&self) -> FieldType {
match self {
FieldValue::Int32(_) => FieldType::Int32,
FieldValue::Int64(_) => FieldType::Int64,
FieldValue::UInt32(_) => FieldType::UInt32,
FieldValue::UInt64(_) => FieldType::UInt64,
FieldValue::Float(_) => FieldType::Float,
FieldValue::Double(_) => FieldType::Double,
FieldValue::Bool(_) => FieldType::Bool,
FieldValue::Str(_) => FieldType::String,
FieldValue::Bytes(_) => FieldType::Bytes,
FieldValue::Message(_) => FieldType::Message(std::string::String::new()),
}
}
}
impl std::fmt::Display for FieldValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FieldValue::Int32(v) => write!(f, "{v}"),
FieldValue::Int64(v) => write!(f, "{v}"),
FieldValue::UInt32(v) => write!(f, "{v}"),
FieldValue::UInt64(v) => write!(f, "{v}"),
FieldValue::Float(v) => write!(f, "{v}"),
FieldValue::Double(v) => write!(f, "{v}"),
FieldValue::Bool(v) => write!(f, "{v}"),
FieldValue::Str(s) => write!(f, "{s}"),
FieldValue::Bytes(b) => write!(f, "<{} bytes>", b.len()),
FieldValue::Message(b) => write!(f, "<message {} bytes>", b.len()),
}
}
}