Simpllify Option + Result into one encoding layer

This commit is contained in:
mbecker20
2025-10-12 03:24:00 -07:00
parent 0900e48cb8
commit 44ab89600f
15 changed files with 248 additions and 369 deletions

View File

@@ -10,8 +10,8 @@ use anyhow::anyhow;
use cache::CloneCache;
use database::mungos::{by_id::update_one_by_id, mongodb::bson::doc};
use encoding::{
CastBytes as _, Decode as _, EncodedJsonMessage, EncodedOption,
EncodedResult, WithChannel,
CastBytes as _, Decode as _, EncodedJsonMessage, EncodedResponse,
WithChannel,
};
use komodo_client::entities::{
builder::{AwsBuilderConfig, UrlBuilderConfig},
@@ -253,10 +253,8 @@ impl<'a> From<&'a OwnedPeripheryConnectionArgs>
}
/// Sends None as InProgress ping.
pub type ResponseChannels = CloneCache<
Uuid,
Sender<EncodedOption<EncodedResult<EncodedJsonMessage>>>,
>;
pub type ResponseChannels =
CloneCache<Uuid, Sender<EncodedResponse<EncodedJsonMessage>>>;
pub type TerminalChannels = CloneCache<Uuid, Sender<Vec<u8>>>;

View File

@@ -135,11 +135,11 @@ impl PeripheryClient {
.await?;
// Still in progress, sent to avoid timeout.
let Some(message) = message.decode()?.into_std() else {
let Some(message) = message.decode()? else {
continue;
};
return message.decode_into();
return message.decode();
}
}
}

View File

@@ -1,6 +1,6 @@
use command::run_komodo_command;
use derive_variants::EnumVariants;
use encoding::{EncodedJsonMessage, EncodedResult};
use encoding::{EncodedJsonMessage, EncodedResponse};
use futures::FutureExt;
use komodo_client::entities::{
config::{DockerRegistry, GitProvider},
@@ -41,7 +41,7 @@ pub struct Args {
Serialize, Deserialize, Debug, Clone, Resolve, EnumVariants,
)]
#[args(Args)]
#[response(EncodedResult<EncodedJsonMessage>)]
#[response(EncodedResponse<EncodedJsonMessage>)]
#[error(anyhow::Error)]
#[variant_derive(Debug)]
#[serde(tag = "type", content = "params")]

View File

@@ -1,7 +1,8 @@
use anyhow::{Context, anyhow};
use derive_variants::{EnumVariants, ExtractVariant};
use encoding::{
CastBytes, Decode, Encode, EncodedResult, impl_cast_bytes_vec,
CastBytes, Decode, Encode, EncodedResponse, impl_cast_bytes_vec,
impl_from_for_wrapper,
};
use noise::key::SpkiPublicKey;
@@ -9,18 +10,14 @@ use crate::transport::{EncodedTransportMessage, TransportMessage};
#[derive(Debug)]
pub struct EncodedLoginMessage(
EncodedResult<InnerEncodedLoginMessage>,
EncodedResponse<InnerEncodedLoginMessage>,
);
impl From<EncodedResult<InnerEncodedLoginMessage>>
for EncodedLoginMessage
{
fn from(value: EncodedResult<InnerEncodedLoginMessage>) -> Self {
Self(value)
}
}
impl_cast_bytes_vec!(EncodedLoginMessage, EncodedResult);
impl_from_for_wrapper!(
EncodedLoginMessage,
EncodedResponse<InnerEncodedLoginMessage>
);
impl_cast_bytes_vec!(EncodedLoginMessage, EncodedResponse);
/// ```markdown
/// | -- u8[] -- | --------- u8 ------------ |
@@ -90,7 +87,11 @@ impl Encode<EncodedTransportMessage> for LoginMessage {
impl Decode<LoginMessage> for EncodedLoginMessage {
fn decode(self) -> anyhow::Result<LoginMessage> {
let mut bytes = self.0.decode()?.into_vec();
let mut bytes = self
.0
.decode()?
.context("Should not receive Pending (2) Response message")?
.into_vec();
let variant_byte = bytes
.pop()

View File

@@ -2,7 +2,8 @@ use anyhow::{Context as _, anyhow};
use derive_variants::{EnumVariants, ExtractVariant as _};
use encoding::{
CastBytes, Decode, Encode, EncodedChannel, EncodedJsonMessage,
EncodedOption, EncodedResult, WithChannel, impl_cast_bytes_vec,
EncodedResponse, WithChannel, impl_cast_bytes_vec,
impl_from_for_wrapper,
};
mod login;
@@ -152,15 +153,10 @@ pub struct EncodedResponseMessage(
EncodedChannel<InnerEncodedResponseMessage>,
);
impl From<EncodedChannel<InnerEncodedResponseMessage>>
for EncodedResponseMessage
{
fn from(
value: EncodedChannel<InnerEncodedResponseMessage>,
) -> Self {
Self(value)
}
}
impl_from_for_wrapper!(
EncodedResponseMessage,
EncodedChannel<InnerEncodedResponseMessage>
);
impl_cast_bytes_vec!(EncodedResponseMessage, EncodedChannel);
@@ -168,28 +164,27 @@ impl_cast_bytes_vec!(EncodedResponseMessage, EncodedChannel);
/// and passed to response handler for parsing.
#[derive(Debug)]
pub struct InnerEncodedResponseMessage(
EncodedOption<EncodedResult<EncodedJsonMessage>>,
EncodedResponse<EncodedJsonMessage>,
);
impl_cast_bytes_vec!(InnerEncodedResponseMessage, EncodedOption);
impl_cast_bytes_vec!(InnerEncodedResponseMessage, EncodedResponse);
pub struct ResponseMessage(WithChannel<InnerEncodedResponseMessage>);
impl ResponseMessage {
pub fn new(
channel: Uuid,
response: Option<EncodedResult<EncodedJsonMessage>>,
response: EncodedResponse<EncodedJsonMessage>,
) -> Self {
Self(WithChannel {
channel,
data: InnerEncodedResponseMessage(response.encode()),
data: InnerEncodedResponseMessage(response),
})
}
pub fn extract(
self,
) -> WithChannel<EncodedOption<EncodedResult<EncodedJsonMessage>>>
{
) -> WithChannel<EncodedResponse<EncodedJsonMessage>> {
self.0.map(|data| data.0)
}
}

View File

@@ -2,7 +2,7 @@ use anyhow::anyhow;
use bytes::Bytes;
use uuid::Uuid;
use crate::{CastBytes, Decode, Encode, impl_wrapper};
use crate::{CastBytes, Decode, Encode};
/// Message wrapper to handle Error unwrapping
/// anywhere in the en/decoding chain.

View File

@@ -1,9 +1,7 @@
use anyhow::Context;
use serde::{Serialize, de::DeserializeOwned};
use crate::{
CastBytes, Decode, Encode, EncodedResult, impl_identity,
};
use crate::{CastBytes, Decode, Encode, EncodedResponse};
/// ```markdown
/// | --- u8[] --- |
@@ -26,15 +24,14 @@ impl CastBytes for EncodedJsonMessage {
pub struct JsonMessage<'a, T>(pub &'a T);
impl<'a, T: Serialize + Send>
Encode<crate::Result<EncodedJsonMessage>> for JsonMessage<'a, T>
Encode<anyhow::Result<EncodedJsonMessage>> for JsonMessage<'a, T>
where
&'a T: Send,
{
fn encode(self) -> crate::Result<EncodedJsonMessage> {
fn encode(self) -> anyhow::Result<EncodedJsonMessage> {
serde_json::to_vec(self.0)
.context("Failed to serialize data to bytes")
.map(EncodedJsonMessage)
.into()
}
}
@@ -46,7 +43,7 @@ impl<T: DeserializeOwned> Decode<T> for EncodedJsonMessage {
}
impl<T: Serialize + Send> From<T>
for EncodedResult<EncodedJsonMessage>
for EncodedResponse<EncodedJsonMessage>
{
fn from(value: T) -> Self {
serde_json::to_vec(&value)

View File

@@ -2,15 +2,16 @@
use bytes::Bytes;
#[macro_use]
mod macros;
mod channel;
mod json;
mod option;
mod result;
mod response;
pub use channel::*;
pub use json::*;
pub use option::*;
pub use result::*;
pub use response::*;
pub trait Encode<Target>: Sized + Send {
fn encode(self) -> Target;
@@ -70,57 +71,3 @@ impl CastBytes for Vec<u8> {
}
}
#[macro_export]
macro_rules! impl_identity {
($typ:ty) => {
impl Encode<$typ> for $typ {
fn encode(self) -> $typ {
self
}
}
impl Decode<$typ> for $typ {
fn decode(self) -> anyhow::Result<$typ> {
Ok(self)
}
}
};
}
#[macro_export]
macro_rules! impl_wrapper {
($struct:ident) => {
impl<T> From<T> for $struct<T> {
fn from(value: T) -> Self {
Self(value)
}
}
impl<T: CastBytes> CastBytes for $struct<T> {
fn from_bytes(bytes: Bytes) -> Self {
Self(T::from_bytes(bytes))
}
fn into_bytes(self) -> Bytes {
self.0.into_bytes()
}
fn from_vec(vec: Vec<u8>) -> Self {
Self(T::from_vec(vec))
}
fn into_vec(self) -> Vec<u8> {
self.0.into_vec()
}
}
};
}
#[macro_export]
macro_rules! impl_cast_bytes_vec {
($typ:ty, $through:ident) => {
impl CastBytes for $typ {
fn from_vec(bytes: Vec<u8>) -> Self {
Self($through::from_vec(bytes))
}
fn into_vec(self) -> Vec<u8> {
self.0.into_vec()
}
}
};
}

View File

@@ -0,0 +1,63 @@
#[macro_export]
macro_rules! impl_cast_bytes_vec {
($typ:ty, $through:ident) => {
impl CastBytes for $typ {
fn from_vec(bytes: Vec<u8>) -> Self {
Self($through::from_vec(bytes))
}
fn into_vec(self) -> Vec<u8> {
self.0.into_vec()
}
}
};
}
#[macro_export]
macro_rules! impl_from_for_wrapper {
($typ:ty, $through:ty) => {
impl From<$through> for $typ {
fn from(value: $through) -> Self {
Self(value)
}
}
};
}
macro_rules! impl_identity {
($typ:ty) => {
impl Encode<$typ> for $typ {
fn encode(self) -> $typ {
self
}
}
impl Decode<$typ> for $typ {
fn decode(self) -> anyhow::Result<$typ> {
Ok(self)
}
}
};
}
macro_rules! impl_wrapper {
($struct:ident) => {
impl<T> From<T> for $struct<T> {
fn from(value: T) -> Self {
Self(value)
}
}
impl<T: CastBytes> CastBytes for $struct<T> {
fn from_bytes(bytes: Bytes) -> Self {
Self(T::from_bytes(bytes))
}
fn into_bytes(self) -> Bytes {
self.0.into_bytes()
}
fn from_vec(vec: Vec<u8>) -> Self {
Self(T::from_vec(vec))
}
fn into_vec(self) -> Vec<u8> {
self.0.into_vec()
}
}
};
}

View File

@@ -1,118 +0,0 @@
use std::option::Option as StdOption;
use anyhow::Context;
use bytes::Bytes;
use crate::{CastBytes, Decode, Encode, impl_wrapper};
/// Message wrapper to handle Option unwrapping
/// anywhere in the en/decoding chain.
/// ```markdown
/// | -- u8[] -- | ------- u8 ------- |
/// | <CONTENTS> | 0: Some or _: None |
/// ```
#[derive(Clone, Debug)]
pub struct EncodedOption<T>(T);
impl_wrapper!(EncodedOption);
impl<B: CastBytes + Send> EncodedOption<B> {
/// Will only produce None if really None, with confirmed None byte.
/// Any error in decoding will lead to Some(Err(e))
pub fn decode_map<T>(self) -> StdOption<anyhow::Result<T>>
where
B: Decode<T>,
{
self.decode().and_then(|data| data.map_decode()).transpose()
}
}
/// Just std Option,
/// but can implement on it.
pub enum Option<T> {
Some(T),
None,
}
impl<T> Option<T> {
pub fn into_std(self) -> StdOption<T> {
self.into()
}
pub fn map<R>(self, map: impl FnOnce(T) -> R) -> Option<R> {
use Option::*;
match self {
Some(t) => Some(map(t)),
None => None,
}
}
pub fn map_encode<B: CastBytes + Send>(self) -> EncodedOption<B>
where
T: Encode<B>,
{
self.map(Encode::encode).encode()
}
pub fn map_decode<D>(self) -> anyhow::Result<StdOption<D>>
where
T: CastBytes + Send + Decode<D>,
{
match self.map(Decode::decode) {
Option::Some(res) => res.map(Some),
Option::None => Ok(None),
}
}
}
impl<T: CastBytes + Send> Encode<EncodedOption<T>> for Option<T> {
fn encode(self) -> EncodedOption<T> {
use Option::*;
match self {
Some(data) => {
let mut bytes = data.into_vec();
bytes.push(0);
EncodedOption(T::from_vec(bytes))
}
None => EncodedOption(T::from_vec(vec![1])),
}
}
}
impl<T: CastBytes + Send> Encode<EncodedOption<T>> for StdOption<T> {
fn encode(self) -> EncodedOption<T> {
Option::from(self).encode()
}
}
impl<T: CastBytes> Decode<Option<T>> for EncodedOption<T> {
fn decode(self) -> anyhow::Result<Option<T>> {
use Option::*;
let mut bytes = self.0.into_vec();
let option_byte =
bytes.pop().context("OptionWrapper bytes cannot be empty")?;
if option_byte == 0 {
Ok(Some(T::from_vec(bytes)))
} else {
Ok(None)
}
}
}
impl<T> From<StdOption<T>> for Option<T> {
fn from(value: StdOption<T>) -> Self {
match value {
Some(t) => Self::Some(t),
None => Self::None,
}
}
}
impl<T> From<Option<T>> for StdOption<T> {
fn from(value: Option<T>) -> Self {
match value {
Option::Some(t) => Some(t),
Option::None => None,
}
}
}

View File

@@ -0,0 +1,125 @@
use anyhow::{Context, Result as AnyhowResult};
use bytes::Bytes;
use serror::{deserialize_error_bytes, serialize_error_bytes};
use crate::{CastBytes, Decode, Encode};
/// Message wrapper to handle Error unwrapping
/// anywhere in the en/decoding chain.
/// ```markdown
/// | -- u8[] -- | ---------- u8 ----------- |
/// | <CONTENTS> | 0: Ok, 1: Err, 2: Pending |
/// ```
#[derive(Clone, Debug)]
pub struct EncodedResponse<T>(T);
impl_wrapper!(EncodedResponse);
pub enum Response<T> {
Ok(T),
Err(anyhow::Error),
Pending,
}
impl<T> Response<T> {
pub fn into_anyhow(self) -> AnyhowResult<Option<T>> {
self.into()
}
pub fn map<R>(self, map: impl FnOnce(T) -> R) -> Response<R> {
use Response::*;
match self {
Ok(t) => Ok(map(t)),
Err(e) => Err(e),
Pending => Pending,
}
}
pub fn map_encode<B: CastBytes + Send>(self) -> EncodedResponse<B>
where
T: Encode<B>,
{
self.map(Encode::encode).encode()
}
pub fn map_decode<D>(self) -> anyhow::Result<Option<D>>
where
T: CastBytes + Send + Decode<D>,
{
match self.map(Decode::decode) {
Response::Ok(res) => res.map(Some),
Response::Err(e) => Err(e),
Response::Pending => Ok(None),
}
}
}
impl<T: CastBytes + Send> Encode<EncodedResponse<T>> for Response<T> {
fn encode(self) -> EncodedResponse<T> {
use Response::*;
let bytes = match self {
Ok(data) => {
let mut bytes = data.into_vec();
bytes.push(0);
bytes
}
Err(e) => {
let mut bytes = serialize_error_bytes(&e);
bytes.push(1);
bytes
}
Pending => {
vec![2]
}
};
EncodedResponse(T::from_vec(bytes))
}
}
impl<T: CastBytes + Send> Encode<EncodedResponse<T>>
for AnyhowResult<T>
{
fn encode(self) -> EncodedResponse<T> {
Response::from(self).encode()
}
}
impl<T: CastBytes> Encode<EncodedResponse<T>> for &anyhow::Error {
fn encode(self) -> EncodedResponse<T> {
let mut bytes = serialize_error_bytes(self);
bytes.push(1);
EncodedResponse::from_vec(bytes)
}
}
impl<T: CastBytes> Decode<Option<T>> for EncodedResponse<T> {
fn decode(self) -> AnyhowResult<Option<T>> {
let mut bytes = self.0.into_vec();
let result_byte =
bytes.pop().context("ResultWrapper bytes cannot be empty")?;
match result_byte {
0 => Ok(Some(T::from_vec(bytes))),
1 => Err(deserialize_error_bytes(&bytes)),
_ => Ok(None),
}
}
}
impl<T> From<AnyhowResult<T>> for Response<T> {
fn from(value: AnyhowResult<T>) -> Self {
match value {
Ok(t) => Self::Ok(t),
Err(e) => Self::Err(e),
}
}
}
impl<T> From<Response<T>> for AnyhowResult<Option<T>> {
fn from(value: Response<T>) -> Self {
match value {
Response::Ok(t) => Ok(Some(t)),
Response::Err(e) => Err(e),
Response::Pending => Ok(None),
}
}
}

View File

@@ -1,120 +0,0 @@
use anyhow::{Context, Result as AnyhowResult};
use bytes::Bytes;
use serror::{deserialize_error_bytes, serialize_error_bytes};
use crate::{CastBytes, Decode, Encode, impl_wrapper};
/// Message wrapper to handle Error unwrapping
/// anywhere in the en/decoding chain.
/// ```markdown
/// | -- u8[] -- | ----- u8 ------ |
/// | <CONTENTS> | 0: Ok or _: Err |
/// ```
#[derive(Clone, Debug)]
pub struct EncodedResult<T>(T);
impl_wrapper!(EncodedResult);
/// Just anyhow's Result,
/// but can implement on it.
pub enum Result<T> {
Ok(T),
Err(anyhow::Error),
}
impl<T> Result<T> {
pub fn into_anyhow(self) -> AnyhowResult<T> {
self.into()
}
pub fn map<R>(self, map: impl FnOnce(T) -> R) -> Result<R> {
use Result::*;
match self {
Ok(t) => Ok(map(t)),
Err(e) => Err(e),
}
}
pub fn map_encode<B: CastBytes + Send>(self) -> EncodedResult<B>
where
T: Encode<B>,
{
self.map(Encode::encode).encode()
}
pub fn map_decode<D>(self) -> anyhow::Result<D>
where
T: CastBytes + Send + Decode<D>,
{
match self.map(Decode::decode) {
Result::Ok(res) => res,
Result::Err(e) => Err(e),
}
}
}
impl<T: CastBytes + Send> Encode<EncodedResult<T>> for Result<T> {
fn encode(self) -> EncodedResult<T> {
use Result::*;
let bytes = match self {
Ok(data) => {
let mut bytes = data.into_vec();
bytes.push(0);
bytes
}
Err(e) => {
let mut bytes = serialize_error_bytes(&e);
bytes.push(1);
bytes
}
};
EncodedResult(T::from_vec(bytes))
}
}
impl<T: CastBytes + Send> Encode<EncodedResult<T>>
for AnyhowResult<T>
{
fn encode(self) -> EncodedResult<T> {
Result::from(self).encode()
}
}
impl<T: CastBytes> Encode<EncodedResult<T>> for &anyhow::Error {
fn encode(self) -> EncodedResult<T> {
let mut bytes = serialize_error_bytes(self);
bytes.push(1);
EncodedResult::from_vec(bytes)
}
}
impl<T: CastBytes> Decode<T> for EncodedResult<T> {
fn decode(self) -> AnyhowResult<T> {
let mut bytes = self.0.into_vec();
let result_byte =
bytes.pop().context("ResultWrapper bytes cannot be empty")?;
if result_byte == 0 {
Ok(T::from_vec(bytes))
} else {
Err(deserialize_error_bytes(&bytes))
}
}
}
impl<T> From<AnyhowResult<T>> for Result<T> {
fn from(value: AnyhowResult<T>) -> Self {
match value {
Ok(t) => Self::Ok(t),
Err(e) => Self::Err(e),
}
}
}
impl<T> From<Result<T>> for AnyhowResult<T> {
fn from(value: Result<T>) -> Self {
match value {
Result::Ok(t) => Ok(t),
Result::Err(e) => Err(e),
}
}
}

View File

@@ -74,20 +74,3 @@ impl JsonString {
}
}
}
pub enum JsonBytes {
Ok(Vec<u8>),
Err(serde_json::Error),
}
impl<T> From<T> for JsonBytes
where
T: Serialize,
{
fn from(value: T) -> JsonBytes {
match serde_json::to_vec(&value) {
Ok(body) => JsonBytes::Ok(body),
Err(e) => JsonBytes::Err(e),
}
}
}

View File

@@ -1,6 +1,6 @@
use anyhow::{Context, anyhow};
use encoding::{
Encode, EncodedJsonMessage, EncodedResult, JsonMessage,
Encode, EncodedJsonMessage, EncodedResponse, JsonMessage,
};
use futures_util::FutureExt;
use periphery_client::transport::{
@@ -87,7 +87,7 @@ impl Sender<EncodedTransportMessage> {
where
&'a T: Send,
{
let json = JsonMessage(request).encode().into_anyhow()?;
let json = JsonMessage(request).encode()?;
self.send_message(RequestMessage::new(channel, json)).await
}
@@ -95,16 +95,21 @@ impl Sender<EncodedTransportMessage> {
&self,
channel: Uuid,
) -> anyhow::Result<()> {
self.send_message(ResponseMessage::new(channel, None)).await
self
.send_message(ResponseMessage::new(
channel,
encoding::Response::Pending.encode(),
))
.await
}
pub async fn send_response(
&self,
channel: Uuid,
response: EncodedResult<EncodedJsonMessage>,
response: EncodedResponse<EncodedJsonMessage>,
) -> anyhow::Result<()> {
self
.send_message(ResponseMessage::new(channel, Some(response)))
.send_message(ResponseMessage::new(channel, response))
.await
}

View File

@@ -4,7 +4,7 @@ use anyhow::{Context, anyhow};
use bytes::Bytes;
use encoding::{
CastBytes as _, Decode as _, Encode, EncodedJsonMessage,
EncodedResult, JsonMessage,
EncodedResponse, JsonMessage,
};
use periphery_client::transport::{
EncodedTransportMessage, RequestMessage, ResponseMessage,
@@ -120,7 +120,7 @@ pub trait WebsocketSenderExt: WebsocketSender + Send {
&'a T: Send,
{
async move {
let json = JsonMessage(request).encode().into_anyhow()?;
let json = JsonMessage(request).encode()?;
self.send_message(RequestMessage::new(channel, json)).await
}
}
@@ -129,15 +129,18 @@ pub trait WebsocketSenderExt: WebsocketSender + Send {
&mut self,
channel: Uuid,
) -> impl Future<Output = anyhow::Result<()>> + Send {
self.send_message(ResponseMessage::new(channel, None))
self.send_message(ResponseMessage::new(
channel,
encoding::Response::Pending.encode(),
))
}
fn send_response(
&mut self,
channel: Uuid,
response: EncodedResult<EncodedJsonMessage>,
response: EncodedResponse<EncodedJsonMessage>,
) -> impl Future<Output = anyhow::Result<()>> + Send {
self.send_message(ResponseMessage::new(channel, Some(response)))
self.send_message(ResponseMessage::new(channel, response))
}
fn send_terminal(