use super::{
CURRENT_CLIENT_CONTRACT_VERSION, ClientContractError, ClientRequest, ClientVersionOffer,
ContractVersion, DEFAULT_CLIENT_CONTRACT_BODY_BYTES, MAX_CLIENT_CONTRACT_BODY_BYTES,
ServerFailure, ServerResponse,
};
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::io::Write;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct ClientContractPolicy {
minimum_version: ContractVersion,
current_version: ContractVersion,
max_body_bytes: usize,
}
impl ClientContractPolicy {
pub const fn new(
minimum_version: ContractVersion,
current_version: ContractVersion,
max_body_bytes: usize,
) -> Result<Self, ClientContractError> {
if minimum_version.get() > current_version.get()
|| max_body_bytes == 0
|| max_body_bytes > MAX_CLIENT_CONTRACT_BODY_BYTES
{
return Err(ClientContractError::InvalidPolicy);
}
Ok(Self {
minimum_version,
current_version,
max_body_bytes,
})
}
pub const fn minimum_version(self) -> ContractVersion {
self.minimum_version
}
pub const fn current_version(self) -> ContractVersion {
self.current_version
}
pub const fn max_body_bytes(self) -> usize {
self.max_body_bytes
}
pub fn decode_offer(self, body: &[u8]) -> Result<ClientVersionOffer, ClientContractError> {
self.validate_body_size(body.len())?;
serde_json::from_slice(body).map_err(ClientContractError::InvalidJson)
}
pub fn encode_offer(self, offer: &ClientVersionOffer) -> Result<Vec<u8>, ClientContractError> {
self.encode_bounded(offer)
}
pub fn negotiate(
self,
offer: &ClientVersionOffer,
) -> Result<ContractVersion, ClientContractError> {
offer
.supported_versions()
.iter()
.rev()
.copied()
.find(|version| *version >= self.minimum_version && *version <= self.current_version)
.ok_or(ClientContractError::NoMutualVersion)
}
pub const fn validate_version(
self,
version: ContractVersion,
) -> Result<(), ClientContractError> {
if version.get() < self.minimum_version.get() || version.get() > self.current_version.get()
{
Err(ClientContractError::UnsupportedVersion {
received: version.get(),
minimum: self.minimum_version.get(),
current: self.current_version.get(),
})
} else {
Ok(())
}
}
pub fn decode_request<T>(self, body: &[u8]) -> Result<ClientRequest<T>, ClientContractError>
where
T: DeserializeOwned,
{
self.validate_body_size(body.len())?;
let request = serde_json::from_slice::<ClientRequest<T>>(body)
.map_err(ClientContractError::InvalidJson)?;
self.validate_version(request.version())?;
Ok(request)
}
pub fn encode_request<T>(
self,
request: &ClientRequest<T>,
) -> Result<Vec<u8>, ClientContractError>
where
T: Serialize,
{
self.validate_version(request.version())?;
self.encode_bounded(request)
}
pub fn decode_response<T>(self, body: &[u8]) -> Result<ServerResponse<T>, ClientContractError>
where
T: DeserializeOwned,
{
self.validate_body_size(body.len())?;
let response = serde_json::from_slice::<ServerResponse<T>>(body)
.map_err(ClientContractError::InvalidJson)?;
self.validate_version(response.version())?;
Ok(response)
}
pub fn encode_response<T>(
self,
response: &ServerResponse<T>,
) -> Result<Vec<u8>, ClientContractError>
where
T: Serialize,
{
self.validate_version(response.version())?;
self.encode_bounded(response)
}
pub fn decode_failure(self, body: &[u8]) -> Result<ServerFailure, ClientContractError> {
self.validate_body_size(body.len())?;
let response = serde_json::from_slice::<ServerFailure>(body)
.map_err(ClientContractError::InvalidJson)?;
self.validate_version(response.version())?;
Ok(response)
}
pub fn encode_failure(self, response: &ServerFailure) -> Result<Vec<u8>, ClientContractError> {
self.validate_version(response.version())?;
self.encode_bounded(response)
}
fn encode_bounded<T>(self, value: &T) -> Result<Vec<u8>, ClientContractError>
where
T: Serialize,
{
let mut writer = BoundedJsonWriter::new(self.max_body_bytes);
match serde_json::to_writer(&mut writer, value) {
Ok(()) => Ok(writer.into_bytes()),
Err(_) if writer.exceeded() => Err(ClientContractError::BodyTooLarge {
maximum: self.max_body_bytes,
}),
Err(error) => Err(ClientContractError::EncodeJson(error)),
}
}
const fn validate_body_size(self, received: usize) -> Result<(), ClientContractError> {
if received > self.max_body_bytes {
Err(ClientContractError::BodyTooLarge {
maximum: self.max_body_bytes,
})
} else {
Ok(())
}
}
}
struct BoundedJsonWriter {
bytes: Vec<u8>,
maximum: usize,
exceeded: bool,
}
impl BoundedJsonWriter {
fn new(maximum: usize) -> Self {
Self {
bytes: Vec::new(),
maximum,
exceeded: false,
}
}
fn exceeded(&self) -> bool {
self.exceeded
}
fn into_bytes(self) -> Vec<u8> {
self.bytes
}
}
impl Write for BoundedJsonWriter {
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
let remaining = self.maximum.saturating_sub(self.bytes.len());
if buffer.len() > remaining {
self.exceeded = true;
return Err(std::io::Error::other("client contract body limit reached"));
}
self.bytes.extend_from_slice(buffer);
Ok(buffer.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl Default for ClientContractPolicy {
fn default() -> Self {
Self {
minimum_version: CURRENT_CLIENT_CONTRACT_VERSION,
current_version: CURRENT_CLIENT_CONTRACT_VERSION,
max_body_bytes: DEFAULT_CLIENT_CONTRACT_BODY_BYTES,
}
}
}