blob: 47b84a7c4e5ebd49633eaa1d157b606213ceda7b [file] [log] [blame]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Internal crate for Submerge, containing the protobuf schema definition and corresponding traits.
//!
//! This is an internal implementation of Submerge. To enable proto serialization / deserialization,
//! use `submerge` with the `proto` feature.
use std::collections::BTreeMap;
use thiserror::Error;
/// Module containing types generated from the proto schema.
#[allow(clippy::unwrap_used, clippy::panic, clippy::cast_possible_truncation)]
#[cfg(feature = "cargo")]
pub mod protos {
include!(concat!(env!("OUT_DIR"), "/proto/mod.rs"));
}
#[cfg(not(feature = "cargo"))]
pub use submerge_internal_proto_proto as protos;
/// A type that can be converted into a proto.
pub trait ToProto {
/// The proto-generated type to be converted into.
type Proto;
/// Converts this type into a proto.
///
/// # Arguments
///
/// * `node_ids`: A mutable mapping from string node IDs to its index. This is used to compress
/// the payload since the node ID may be long and may appear multiple times in a document
/// (e.g. in each vector data and vector clock). The resulting list of `node_ids` will be
/// included in the top level Document or DeltaDocument proto.
fn to_proto(&self, node_ids: &mut NodeMapping<String>) -> Self::Proto;
}
/// Error type when conversion from proto failed.
#[derive(Debug, Error)]
pub enum FromProtoError {
/// The proto references a node ID index that is non-existent on the node_ids list given.
///
/// This is an error in the input proto payload, and typically indicates a serialization error,
/// such as using the wrong `NodeMapping` when serializing the `Document` or `DeltaDocument`.
#[error("Referenced Node ID missing")]
MissingNodeId,
/// Numeric value overflowed.
///
/// Protobufs only support 32-bit and 64-bit integers, while some values in the document are u8
/// or u16. If the deserialization code encounters a numeric value that cannot fit in the target
/// type, this error will be returned. This typically indicates an error in the serialization
/// code.
#[error("Numeric value overflowed")]
NumericOverflow,
/// Missing a required field.
///
/// Proto 3 treats all non-primitive fields as optional, but in Submerge some fields are
/// required to be successfully parsed into their corresponding Rust types.
#[error("Required field missing")]
MissingRequiredField,
/// Length of the elements in the vector clock mismatched.
///
/// A vector clock proto contains two repeated fields that are zipped together into a map, and
/// therefore must have the same length.
#[error("Mismatched vector clock")]
MismatchedVectorClockLength,
}
/// A trait indicating it can be constructed from a proto.
pub trait FromProto: Sized {
/// The proto-generated type to parse into the corresponding Rust type.
type Proto;
/// Construct an instance from the given proto.
fn from_proto(proto: &Self::Proto, node_ids: &[String]) -> Result<Self, FromProtoError>;
}
/// A mapping between node IDs and its corresponding index.
///
/// This is used to compress the serialized data as the node IDs are often long (e.g. 36-character
/// UUID) and appears in a document multiple times. This structure collects all of the node IDs used
/// during serialization and allows them to be replaced with the numeric index.
#[derive(Debug, Default, PartialEq, Eq)]
pub struct NodeMapping<N: Ord>(BTreeMap<N, u32>);
impl<N: Ord + Clone> NodeMapping<N> {
/// Gets the index for the given `node_id`.
///
/// If the `node_id` doesn't already exist, it will be added to the map.
pub fn get_index(&mut self, node_id: &N) -> u32 {
let len = self.0.len();
*self
.0
.entry(node_id.clone())
.or_insert(len.try_into().expect("Too many node IDs"))
}
/// Converts this node mapping into a vector.
///
/// The resulting vector will contain all the node IDs sorted by its value. Since the indices
/// are allocated in a contiguous range, the vec index can be used to look up the corresponding
/// node ID.
pub fn into_vec(self) -> Vec<N> {
let reverse_map = self
.0
.into_iter()
.map(|(k, v)| (v, k))
.collect::<BTreeMap<_, _>>();
debug_assert_eq!(
reverse_map.keys().map(|v| *v as usize).collect::<Vec<_>>(),
(0..reverse_map.len()).collect::<Vec<_>>()
);
reverse_map.values().cloned().collect()
}
}