dwn-core 0.4.0

Core DWN types.
Documentation
use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD};
use serde::{Deserialize, Serialize};
use serde_with::{DisplayFromStr, serde_as, skip_serializing_none};
use time::OffsetDateTime;

use crate::message::{
    Message,
    cid::CidGenerationError,
    data::{Data, compute_data_cid},
    descriptor::{Descriptor, Interface, Method},
};

#[serde_as]
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RecordsWrite {
    interface: Interface,
    method: Method,
    pub data_cid: Option<String>,
    #[serde_as(as = "Option<DisplayFromStr>")]
    pub data_format: Option<mime::Mime>,
    #[serde(with = "time::serde::rfc3339")]
    pub message_timestamp: OffsetDateTime,
    pub protocol: Option<String>,
    pub protocol_version: Option<semver::Version>,
    pub protocol_path: Option<String>,
    pub published: Option<bool>,
    pub schema: Option<String>,
}

#[derive(Default)]
pub struct RecordsWriteBuilder {
    pub record_id: Option<String>,
    pub context_id: Option<String>,
    pub data: Option<Vec<u8>>,
    pub data_format: Option<mime::Mime>,
    pub schema: Option<String>,
    pub protocol: Option<String>,
    pub protocol_version: Option<semver::Version>,
    pub protocol_path: Option<String>,
    pub published: Option<bool>,
}

impl RecordsWriteBuilder {
    pub fn build(self) -> Result<Message, CidGenerationError> {
        let data_cid = self.data.as_ref().and_then(|d| compute_data_cid(d));

        let descriptor = Descriptor::RecordsWrite(Box::new(RecordsWrite {
            interface: Interface::Records,
            method: Method::Write,
            data_cid,
            data_format: self.data_format,
            schema: self.schema,
            protocol: self.protocol,
            protocol_version: self.protocol_version,
            protocol_path: self.protocol_path,
            published: self.published,
            message_timestamp: OffsetDateTime::now_utc(),
        }));

        let record_id = match self.record_id {
            Some(v) => v,
            None => descriptor.compute_entry_id()?,
        };

        let data = self
            .data
            .as_ref()
            .map(|d| Data::Base64(BASE64_URL_SAFE_NO_PAD.encode(d)));

        Ok(Message {
            record_id,
            context_id: self.context_id,
            data,
            descriptor,
            attestation: None,
            authorization: None,
        })
    }
}

#[cfg(test)]
mod tests {
    use mime::TEXT_PLAIN;

    use super::*;

    #[test]
    fn test_record_id_generation() {
        let msg = RecordsWriteBuilder::default().build().unwrap();
        assert_eq!(msg.record_id, msg.descriptor.compute_entry_id().unwrap());
    }

    #[test]
    fn test_data() {
        let data = "test data".as_bytes().to_owned();

        let msg = RecordsWriteBuilder {
            data: Some(data.clone()),
            data_format: Some(TEXT_PLAIN),
            ..Default::default()
        }
        .build()
        .unwrap();

        let Descriptor::RecordsWrite(desc) = msg.descriptor else {
            panic!()
        };
        assert!(desc.data_cid.is_some());
        assert_eq!(desc.data_format, Some(TEXT_PLAIN));

        let encoded = match msg.data.as_ref().unwrap() {
            Data::Base64(s) => s,
            Data::Encrypted(_) => panic!("data encrypted"),
        };
        let parsed = BASE64_URL_SAFE_NO_PAD.decode(encoded).unwrap();
        assert_eq!(parsed, data);
    }
}