net-core-api 0.5.3

This crate defines core traits and types for the api defined through the net-stalker project. Amazon Ion is used as the serialization format.
Documentation
use ion_rs;

use ion_rs::IonReader;
use ion_rs::IonType;
use ion_rs::IonWriter;

use ion_rs::ReaderBuilder;

use crate::core::api::API;
use crate::core::decoder_api::Decoder;
use crate::core::encoder_api::Encoder;
use crate::core::typed_api::Typed;

use crate::api::envelope::envelope::Envelope;


const DATA_TYPE: &str = "result";

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ResultDTO {
    is_ok: bool,
    description: Option<String>,
    value: Option<Envelope>,
}
impl API for ResultDTO { }

impl ResultDTO {

    pub fn new(
        is_ok: bool,
        description: Option<&str>,
        value: Option<Envelope>
    ) -> Self {
        Self {
            is_ok,
            description: description.map(|id| id.into()),
            value
        }
    }
    
    pub fn is_ok(&self) -> bool {
        self.is_ok
    }

    pub fn get_description(&self) -> Result<&str, &str> {
        self.description
            .as_deref()
            .map_or_else(|| Err("There is no description provided"), Ok)
    }

    pub fn into_inner(self) -> Option<Envelope> {
        self.value
    }
}

impl Encoder for ResultDTO {
    fn encode(&self) -> Vec<u8> {
        let buffer: Vec<u8> = Vec::new();

        let binary_writer_builder = ion_rs::BinaryWriterBuilder::new();
        let mut writer = binary_writer_builder.build(buffer.clone()).unwrap();
        
        writer.step_in(IonType::Struct).expect("Error while creating an ion struct");

        writer.set_field_name("is_ok");
        writer.write_bool(self.is_ok).unwrap();

        writer.set_field_name("description");
        match &self.description {
            Some(description) => writer.write_string(description).unwrap(),
            None => writer.write_null(ion_rs::IonType::String).unwrap(),
        };

        writer.set_field_name("value");
        match &self.value {
            Some(value) => writer.write_blob(value.encode()).unwrap(),
            None => writer.write_null(ion_rs::IonType::Blob).unwrap(),
        };

        writer.step_out().unwrap();
        writer.flush().unwrap();

        writer.output().as_slice().into()
    }
}

impl Decoder for ResultDTO {
    fn decode(data: &[u8]) -> Self {

        let mut binary_user_reader = ReaderBuilder::new().build(data).unwrap();
        binary_user_reader.next().unwrap();
        binary_user_reader.step_in().unwrap();

        binary_user_reader.next().unwrap();
        let is_ok = binary_user_reader.read_bool().unwrap();

        let binding;
        binary_user_reader.next().unwrap();
        let description = match binary_user_reader.current() {
            ion_rs::StreamItem::Value(_) => {
                binding = binary_user_reader.read_string().unwrap();
                Some(binding.text())
            },
            ion_rs::StreamItem::Null(_) => {
                None
            },
            //TODO: Return en error here in future
            ion_rs::StreamItem::Nothing => todo!(),
        };

        binary_user_reader.next().unwrap();
        let value = match binary_user_reader.current() {
            ion_rs::StreamItem::Value(_) => {
                Some(Envelope::decode(binary_user_reader.read_blob().unwrap().as_slice()))
            },
            ion_rs::StreamItem::Null(_) => {
                None
            },
            //TODO: Return en error here in future
            ion_rs::StreamItem::Nothing => todo!(),
        };

        binary_user_reader.step_out().unwrap();

        ResultDTO::new(
            is_ok,
            description,
            value
        )
    }
}

impl Typed for ResultDTO {
    fn get_data_type() -> &'static str {
        DATA_TYPE
    }

    fn get_type(&self) -> &str {
        Self::get_data_type()
    }
}


#[cfg(test)]
mod tests {
    use ion_rs::IonType;
    use ion_rs::IonReader;
    use ion_rs::ReaderBuilder;
    use ion_rs::StreamItem;

    use crate::core::decoder_api::Decoder;
    use crate::core::encoder_api::Encoder;
    use crate::core::typed_api::Typed;
    
    use crate::api::envelope::envelope::Envelope;
    
    use crate::api::result::result::ResultDTO;


    #[test]
    fn reader_correctly_read_encoded_result() {
        const IS_OK: bool = true;
        const DESCRIPTION: Option<&str> = Some("DESCRIPTION");

        const TENANT_ID: &str = "TENANT_ID";
        const ENVELOPE_TYPE: &str = "ENVELOPE_TYPE";
        const ENVELOPE_DATA: &[u8] = "ENVELOPE_DATA".as_bytes();
        let  inner_value: Option<Envelope> = Some(
            Envelope::new(
                TENANT_ID,
                ENVELOPE_TYPE,
                ENVELOPE_DATA
            )
        );

        let request_result = ResultDTO::new(
            IS_OK,
            DESCRIPTION,
            inner_value.clone()
        );
        let mut binary_user_reader = ReaderBuilder::new().build(request_result.encode()).unwrap();

        assert_eq!(StreamItem::Value(IonType::Struct), binary_user_reader.next().unwrap());
        binary_user_reader.step_in().unwrap();
        
        assert_eq!(StreamItem::Value(IonType::Bool), binary_user_reader.next().unwrap());
        assert_eq!("is_ok", binary_user_reader.field_name().unwrap());
        assert_eq!(IS_OK, binary_user_reader.read_bool().unwrap());

        assert_eq!(StreamItem::Value(IonType::String), binary_user_reader.next().unwrap());
        assert_eq!("description", binary_user_reader.field_name().unwrap());
        assert_eq!(DESCRIPTION.unwrap(), binary_user_reader.read_string().unwrap().text());

        assert_eq!(StreamItem::Value(IonType::Blob), binary_user_reader.next().unwrap());
        assert_eq!("value", binary_user_reader.field_name().unwrap());
        assert_eq!(inner_value.unwrap(), Envelope::decode(binary_user_reader.read_blob().unwrap().as_slice()));

        binary_user_reader.step_out().unwrap();
    }

    #[test]
    fn endec_result() {
        const IS_OK: bool = true;
        const DESCRIPTION: Option<&str> = Some("DESCRIPTION");

        const TENANT_ID: &str = "TENANT_ID";
        const ENVELOPE_TYPE: &str = "ENVELOPE_TYPE";
        const ENVELOPE_DATA: &[u8] = "ENVELOPE_DATA".as_bytes();
        let  inner_value: Option<Envelope> = Some(
            Envelope::new(
                TENANT_ID,
                ENVELOPE_TYPE,
                ENVELOPE_DATA
            )
        );

        let request_result = ResultDTO::new(
            IS_OK,
            DESCRIPTION,
            inner_value.clone()
        );
        assert_eq!(request_result, ResultDTO::decode(&request_result.encode()));
    }

    #[test]
    fn test_getting_data_types() {
        const IS_OK: bool = true;
        const DESCRIPTION: Option<&str> = Some("DESCRIPTION");

        const TENANT_ID: &str = "TENANT_ID";
        const ENVELOPE_TYPE: &str = "ENVELOPE_TYPE";
        const ENVELOPE_DATA: &[u8] = "ENVELOPE_DATA".as_bytes();
        let  inner_value: Option<Envelope> = Some(
            Envelope::new(
                TENANT_ID,
                ENVELOPE_TYPE,
                ENVELOPE_DATA
            )
        );

        let request_result = ResultDTO::new(
            IS_OK,
            DESCRIPTION,
            inner_value.clone()
        );
        assert_eq!(request_result.get_type(), ResultDTO::get_data_type());
        assert_eq!(request_result.get_type(), super::DATA_TYPE);
    }
}