1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
use std::error;
use bytes::{Buf, BufMut};
/// Converts back and forth between user-friendly metadata types and the on-disk integer representation.
pub trait AsMetadata: Sized {
/// Converts this metadata value into its integer representation.
fn into_u32(self) -> u32;
/// Converts an integer representation of metadata into its real type, if possible.
///
/// If the given integer does not represent a valid representation of the given metadata type,
/// possibly due to including bits not valid for the type, and so on, then `None` will be
/// returned. Otherwise, `Some(Self)` will be returned.
fn from_u32(value: u32) -> Option<Self>;
}
impl AsMetadata for () {
fn into_u32(self) -> u32 {
0
}
fn from_u32(_: u32) -> Option<Self> {
Some(())
}
}
/// An object that can encode and decode itself to and from a buffer.
///
/// # Metadata
///
/// While an encoding implementation is typically fixed i.e. `MyJsonEncoderType` only encodes and
/// decodes JSON, we want to provide the ability to change encodings and schemas over time without
/// fundamentally changing all of the code in the buffer implementations.
///
/// We provide the ability to express "metadata" about the encoding implementation such that any
/// relevant information can be included alongside the encoded object, and then passed back when
/// decoding is required.
///
/// ## Implementation
///
/// As designed, an implementor would define a primary encoding scheme, schema version, and so on,
/// that matched how an object would be encoded. This is acquired from `get_metadata` by code that
/// depends on `Encodable` and will be stored alongside the encoded object. When the encoded object
/// is later read back, and the caller wants to decode it, they would also read the metadata and do
/// two things: check that the metadata is still valid for this implementation by calling
/// `can_decode` and then pass it along to the `decode` call itself.
///
/// ## Verifying ability to decode
///
/// Calling `can_decode` first allows callers to check if the encoding implementation supports the
/// parameters used to encode the given object, which provides a means to allow for versioning,
/// schema evolution, and more. Practically speaking, an implementation might bump the version of
/// its schema, but still support the old version for some time, and so `can_decode` might simply
/// check that the metadata represents the current version of the schema, or the last version, but
/// no other versions would be allowed. When the old version of the schema was finally removed and
/// no longer supported, `can_decode` would no longer say it could decode any object whose metadata
/// referenced that old version.
///
/// The `can_decode` method is provided separately, instead of being lumped together in the `decode`
/// call, as a means to distinguish a lack of decoding support for a given metadata from a general
/// decoding failure.
///
/// ## Metadata-aware decoding
///
/// Likewise, the call to `decode` is given the metadata that was stored with the encoded object so
/// that it knows exactly what parameters were originally used and thus how it needs approach
/// decoding the object.
///
/// ## Metadata format and meaning
///
/// Ostensibly, the metadata would represent either some sort of numeric version identifier, or
/// could be used in a bitflags-style fashion, where each bit represents a particular piece of
/// information: encoding type, schema version, whether specific information is present in the
/// encoded object, and so on.
pub trait Encodable: Sized {
type Metadata: AsMetadata + Copy;
type EncodeError: error::Error + Send + Sync + 'static;
type DecodeError: error::Error + Send + Sync + 'static;
/// Gets the version metadata associated with this encoding scheme.
///
/// The value provided is ostensibly used as a bitfield-esque container, or potentially as a raw
/// numeric version identifier, that identifies how a value was encoded, as well as any other
/// information that may be necessary to successfully decode it.
fn get_metadata() -> Self::Metadata;
/// Whether or not this encoding scheme can understand and successfully decode a value based on
/// the given version metadata that was bundled with the value.
fn can_decode(metadata: Self::Metadata) -> bool;
/// Attempts to encode this value into the given buffer.
///
/// # Errors
///
/// If there is an error while attempting to encode this value, an error variant will be
/// returned describing the error.
///
/// Practically speaking, based on the API, encoding errors should generally only occur if there
/// is insufficient space in the buffer to fully encode this value. However, this is not
/// guaranteed.
fn encode<B: BufMut>(self, buffer: &mut B) -> Result<(), Self::EncodeError>;
/// Gets the encoded size, in bytes, of this value, if available.
///
/// Not all types can know ahead of time how many bytes they will occupy when encoded, hence the
/// fallibility of this method.
fn encoded_size(&self) -> Option<usize> {
None
}
/// Attempts to decode an instance of this type from the given buffer and metadata.
///
/// # Errors
///
/// If there is an error while attempting to decode a value from the given buffer, or the given
/// metadata is not valid for the implementation, an error variant will be returned describing
/// the error.
fn decode<B: Buf + Clone>(
metadata: Self::Metadata,
buffer: B,
) -> Result<Self, Self::DecodeError>;
}
/// An object that can encode and decode itself to and from a buffer, with a fixed representation.
///
/// This trait is a companion trait to `Encodable` that provides a blanket implementation of
/// `Encodable` that does not use or care about encoding metadata. It fulfills the necessary
/// methods to work in `Encodable` contexts without requiring any of the boilerplate.
///
/// ## Warning
///
/// You should _not_ typically use this trait unless you're trying to implement `Encodable` for
/// testing purposes where you won't be dealing with a need for versioning payloads, etc.
///
/// For any types that will potentially be encoded in real use cases, `Encodable` should be
/// preferred as it requires an upfront decision to be made about metadata and how it's dealt with.
pub trait FixedEncodable {
type EncodeError: error::Error + Send + Sync + 'static;
type DecodeError: error::Error + Send + Sync + 'static;
/// Attempts to encode this value into the given buffer.
///
/// # Errors
///
/// If there is an error while attempting to encode this value, an error variant will be
/// returned describing the error.
///
/// Practically speaking, based on the API, encoding errors should generally only occur if there
/// is insufficient space in the buffer to fully encode this value. However, this is not
/// guaranteed.
fn encode<B: BufMut>(self, buffer: &mut B) -> Result<(), Self::EncodeError>;
/// Gets the encoded size, in bytes, of this value, if available.
///
/// Not all types can know ahead of time how many bytes they will occupy when encoded, hence the
/// fallibility of this method.
fn encoded_size(&self) -> Option<usize> {
None
}
/// Attempts to decode an instance of this type from the given buffer.
///
/// # Errors
///
/// If there is an error while attempting to decode a value from the given buffer, an error
/// variant will be returned describing the error.
fn decode<B: Buf + Clone>(buffer: B) -> Result<Self, Self::DecodeError>
where
Self: Sized;
}
impl<T: FixedEncodable> Encodable for T {
type Metadata = ();
type EncodeError = <T as FixedEncodable>::EncodeError;
type DecodeError = <T as FixedEncodable>::DecodeError;
fn get_metadata() -> Self::Metadata {}
fn can_decode((): Self::Metadata) -> bool {
true
}
fn encode<B: BufMut>(self, buffer: &mut B) -> Result<(), Self::EncodeError> {
FixedEncodable::encode(self, buffer)
}
fn encoded_size(&self) -> Option<usize> {
FixedEncodable::encoded_size(self)
}
fn decode<B: Buf + Clone>((): Self::Metadata, buffer: B) -> Result<Self, Self::DecodeError> {
<Self as FixedEncodable>::decode(buffer)
}
}