reliar-store-postgres 0.9.0

PostgreSQL provider for the Reliar transactional outbox and inbox: migrations, migrate(), enqueue and the SKIP LOCKED claim.
Documentation
use std::sync::LazyLock;

use reliar_core::{ContentType, Message, Serializer};
use serde::{Deserialize, Serialize};

/// A minimal message body used across scenario files.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub(crate) struct OrderCreated {
    pub order_id: u64,
}

impl Message for OrderCreated {
    const TYPE: &'static str = "orders.created";
    const VERSION: u16 = 1;
}

/// A second `Serializer` with a **non-JSON** `ContentType`, so ยง43.A.4's round-trip equality
/// (`acquired.content_type == store.content_type()`) is proven for a store that is not `JSON`,
/// not just by coincidence with the default. Encodes as JSON under the hood โ€” only the declared
/// `ContentType` differs โ€” so the test body stays a plain `Message`.
#[derive(Clone, Debug, Default)]
pub(crate) struct TestVndSerializer;

#[derive(Debug)]
pub(crate) struct TestVndSerializerError(String);

impl std::fmt::Display for TestVndSerializerError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "test serializer error: {}", self.0)
    }
}

impl std::error::Error for TestVndSerializerError {}

impl Serializer for TestVndSerializer {
    type Error = TestVndSerializerError;

    fn content_type(&self) -> &ContentType {
        static CONTENT_TYPE: LazyLock<ContentType> =
            LazyLock::new(|| ContentType::parse("application/vnd.reliar-test+json").unwrap());

        &CONTENT_TYPE
    }

    fn serialize<T: Message>(&self, body: &T) -> Result<bytes::Bytes, Self::Error> {
        serde_json::to_vec(body)
            .map(bytes::Bytes::from)
            .map_err(|err| TestVndSerializerError(err.to_string()))
    }

    fn deserialize<T: Message>(&self, bytes: &[u8]) -> Result<T, Self::Error> {
        serde_json::from_slice(bytes).map_err(|err| TestVndSerializerError(err.to_string()))
    }
}

/// A `Serializer` whose `serialize` always fails โ€” proves `enqueue`'s
/// `EnqueueError::Serialize` path never reaches the database at all (no partial `INSERT`, no
/// SQL round trip for a body the serializer itself rejected).
#[derive(Clone, Debug, Default)]
pub(crate) struct AlwaysFailingSerializer;

#[derive(Debug)]
pub(crate) struct AlwaysFailingSerializerError;

impl std::fmt::Display for AlwaysFailingSerializerError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "this serializer always fails, by design")
    }
}

impl std::error::Error for AlwaysFailingSerializerError {}

impl Serializer for AlwaysFailingSerializer {
    type Error = AlwaysFailingSerializerError;

    fn content_type(&self) -> &ContentType {
        &ContentType::JSON
    }

    fn serialize<T: Message>(&self, _body: &T) -> Result<bytes::Bytes, Self::Error> {
        Err(AlwaysFailingSerializerError)
    }

    fn deserialize<T: Message>(&self, _bytes: &[u8]) -> Result<T, Self::Error> {
        Err(AlwaysFailingSerializerError)
    }
}