midi2 0.11.1

Ergonomic, versatile, strong types wrapping MIDI 2.0 message data.
Documentation
use crate::{
    buffer::{BufferMut, Ump},
    detail::{common_properties, property},
    ump_stream,
    ump_stream::UMP_MESSAGE_TYPE,
};

pub(crate) const STATUS: u16 = 0x12;

#[midi2_proc::generate_message(Via(ump_stream::UmpStream), MinSizeUmp(4))]
struct FunctionBlockName {
    #[property(common_properties::UmpMessageTypeProperty<UMP_MESSAGE_TYPE>)]
    ump_type: (),
    #[property(ump_stream::StatusProperty<STATUS>)]
    status: (),
    #[property(ump_stream::ConsistentFormatsProperty)]
    consistent_formats: (),
    #[property(FunctionBlockProperty)]
    function_block: u8,
    #[property(ump_stream::TextWriteStrProperty<1>)]
    #[writeonly]
    #[resize]
    name: &str,
    #[property(TextReadBytesProperty)]
    #[readonly]
    name_bytes: ump_stream::TextBytesIterator,
    #[property(TextReadStringProperty)]
    #[readonly]
    #[std]
    name: std::string::String,
}

impl<B: Ump> crate::traits::Size<B> for FunctionBlockName<B> {
    fn size(&self) -> usize {
        ump_stream::message_size(&self.0)
    }
}

struct FunctionBlockProperty;

impl<B: Ump> property::Property<B> for FunctionBlockProperty {
    type Type = u8;
}

impl<'a, B: Ump> property::ReadProperty<'a, B> for FunctionBlockProperty {
    fn validate(buffer: &B) -> Result<(), crate::error::InvalidData> {
        use crate::detail::BitOps;

        let function_block = buffer.buffer()[0].octet(2);
        if !buffer
            .buffer()
            .chunks_exact(4)
            .all(|packet| packet[0].octet(2) == function_block)
        {
            Err(crate::error::InvalidData(
                "Inconsistent function block fields",
            ))
        } else {
            Ok(())
        }
    }
    fn read(buffer: &'a B) -> Self::Type {
        use crate::detail::BitOps;
        buffer.buffer()[0].octet(2)
    }
}

impl<B: Ump + BufferMut> property::WriteProperty<B> for FunctionBlockProperty {
    fn validate(_v: &Self::Type) -> Result<(), crate::error::InvalidData> {
        Ok(())
    }
    fn default() -> Self::Type {
        Default::default()
    }
    fn write(buffer: &mut B, v: Self::Type) {
        use crate::detail::BitOps;
        for packet in buffer.buffer_mut().chunks_exact_mut(4) {
            packet[0].set_octet(2, v);
        }
    }
}

pub struct TextReadBytesProperty<'a>(core::marker::PhantomData<&'a u8>);

impl<'a, B: Ump> property::Property<B> for TextReadBytesProperty<'a> {
    type Type = ump_stream::TextBytesIterator<'a>;
}

impl<'a, B: 'a + Ump> property::ReadProperty<'a, B> for TextReadBytesProperty<'a> {
    fn read(buffer: &'a B) -> <Self as property::Property<B>>::Type {
        ump_stream::TextBytesIterator {
            buffer: buffer.buffer(),
            packet_index: 0,
            byte_index: 0,
            offset: 1,
        }
    }
    fn validate(_buffer: &B) -> Result<(), crate::error::InvalidData> {
        Ok(())
    }
}

#[cfg(feature = "std")]
pub struct TextReadStringProperty;

#[cfg(feature = "std")]
impl<B: Ump> property::Property<B> for TextReadStringProperty {
    type Type = std::string::String;
}

#[cfg(feature = "std")]
impl<'a, B: Ump> property::ReadProperty<'a, B> for TextReadStringProperty {
    fn read(buffer: &'a B) -> Self::Type {
        let bytes = TextReadBytesProperty::read(buffer).collect();
        std::string::String::from_utf8(bytes).unwrap()
    }
    fn validate(buffer: &B) -> Result<(), crate::error::InvalidData> {
        let bytes = TextReadBytesProperty::read(buffer).collect();
        std::string::String::from_utf8(bytes).map_err(|_| {
            crate::error::InvalidData("Payload bytes do not represent a valid utf string")
        })?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions::assert_eq;

    #[test]
    fn set_name() {
        let mut message = FunctionBlockName::<std::vec::Vec<u32>>::new();
        message.set_name("SynthWave🌊²");
        message.set_function_block(0x09);
        assert_eq!(
            message,
            FunctionBlockName(std::vec![
                0xF412_0953,
                0x796E_7468,
                0x5761_7665,
                0xF09F_8C8A,
                0xFC12_09C2,
                0xB200_0000,
                0x0000_0000,
                0x0000_0000,
            ])
        );
    }

    #[test]
    #[cfg(feature = "std")]
    fn name() {
        assert_eq!(
            FunctionBlockName::try_from(
                &[
                    0xF412_0953,
                    0x796E_7468,
                    0x5761_7665,
                    0xF09F_8C8A,
                    0xFC12_09C2,
                    0xB200_0000,
                    0x0000_0000,
                    0x0000_0000,
                ][..]
            )
            .unwrap()
            .name(),
            "SynthWave🌊²",
        );
    }

    #[test]
    fn function_block() {
        assert_eq!(
            FunctionBlockName::try_from(
                &[
                    0xF412_0953,
                    0x796E_7468,
                    0x5761_7665,
                    0xF09F_8C8A,
                    0xFC12_09C2,
                    0xB200_0000,
                    0x0000_0000,
                    0x0000_0000,
                ][..]
            )
            .unwrap()
            .function_block(),
            0x9,
        );
    }

    #[test]
    fn read_empty_bytes() {
        assert_eq!(
            FunctionBlockName::<std::vec::Vec<u32>>::new()
                .name_bytes()
                .collect::<std::vec::Vec<u8>>(),
            std::vec::Vec::<u8>::new(),
        );
    }
}