postro 0.1.1

Asynchronous Postgres Driver and Utility
Documentation
use crate::{
    ext::FmtExt,
    postgres::backend::{ErrorResponse, NoticeResponse},
};

// TODO: Appendix A, error code / sqlstate message
// https://www.postgresql.org/docs/current/errcodes-appendix.html

/// a field of [`ErrorResponse`] or [`NoticeResponse`]
///
/// Each field type has a single-byte identification token.
///
/// Note that any given field type should appear at most once per message.
///
/// The message body consists of one or more identified fields, followed by a zero byte as a terminator.
/// Fields can appear in any order.
///
/// For each field there is the following:
///
/// `Byte1` A code identifying the field type; if zero, this is the message terminator and no string follows.
/// The presently defined field types are listed in Section 53.8.
/// Since more field types might be added in future,
/// frontends should silently ignore fields of unrecognized type.
///
/// `String` The field value.
//
// previously, this have their own explicit fields, but clippy detect it
// to big in size, 432 bytes to be exact
pub enum MessageFields {
    /// one of [`Severity`], or a localized translation of one of these, always present
    SeverityLocalized,
    /// this is identical to the S field except that the contents are never localized.
    ///
    /// this is present only in messages generated by PostgreSQL versions 9.6 and later.
    Severity,
    /// the SQLSTATE code for the error. Not localizable. Always present.
    ///
    /// see [Appendix A](https://www.postgresql.org/docs/current/errcodes-appendix.html)
    Code,
    /// the primary human-readable error message. Always present.
    ///
    /// This should be accurate but terse (typically one line).
    Message,
    /// an optional secondary error message carrying more detail about the problem.
    ///
    /// Might run to multiple lines.
    Detail,
    /// an optional suggestion what to do about the problem.
    ///
    /// This is intended to differ from Detail in that it offers advice (potentially inappropriate)
    /// rather than hard facts.
    ///
    /// Might run to multiple lines.
    Hint,
    /// the field value is a decimal ASCII integer, indicating an error cursor position as an index into
    /// the original query string.
    ///
    /// The first character has index 1, and positions are measured in characters not bytes.
    Position,
    /// this is defined the same as the P field, but it is used when the cursor position refers to an internally
    /// generated command rather than the one submitted by the client.
    ///
    /// The q field will always appear when this field appears.
    InternalPosition,
    /// the text of a failed internally-generated command. This could be, for example, an SQL query
    /// issued by a PL/pgSQL function.
    InternalQuery,
    /// an indication of the context in which the error occurred.
    ///
    /// Presently this includes a call stack traceback of active procedural language functions and
    /// internally-generated queries. The trace is one entry per line, most recent first.
    Where,
    /// if the error was associated with a specific database object, the name of the schema containing that object,
    /// if any.
    SchemaName,
    /// if the error was associated with a specific table, the name of the table.
    /// (Refer to the schema name field for the name of the table's schema.)
    TableName,
    /// if the error was associated with a specific table column, the name of the column.
    /// (Refer to the schema and table name fields to identify the table.)
    ColumnName,
    /// if the error was associated with a specific data type, the name of the data type.
    /// (Refer to the schema name field for the name of the data type's schema.)
    DataTypeName,
    /// if the error was associated with a specific constraint, the name of the constraint.
    ///
    /// Refer to fields listed above for the associated table or domain.
    /// (For this purpose, indexes are treated as constraints, even if they weren't created with constraint syntax.)
    ConstraintName,
    /// the file name of the source-code location where the error was reported.
    FileName,
    /// the line number of the source-code location where the error was reported.
    Line,
    /// the name of the source-code routine reporting the error.
    Routine,
}

impl MessageFields {
    pub fn debug(body: &[u8], f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let mut map = f.debug_map();
        let mut iter = body.iter().copied().enumerate();
        loop {
            let Some((i,key)) = iter.next() else {
                break;
            };
            let Some(key) = MessageFields::from_byte(key) else {
                break;
            };
            map.key(&key.as_str());
            match iter.find(|(_,e)|matches!(e,b'\0')) {
                Some((end,_)) => map.value(&body[i + 1..end].lossy()),
                None => map.value(&"<??>"),
            };
        }
        map.finish()
    }
    pub fn display(body: &[u8], f: &mut std::fmt::Formatter) -> std::fmt::Result {
        let mut sevr = None;
        let mut code = None;
        let mut message = None;
        let mut routine = None;
        let mut detail = None;
        let mut hint = None;

        let mut iter = body.iter().copied().enumerate();
        loop {
            let Some((i,key)) = iter.next() else {
                break;
            };
            let Some((end,_)) = iter.find(|(_,e)|matches!(e,b'\0')) else {
                break;
            };
            match MessageFields::from_byte(key) {
                Some(MessageFields::SeverityLocalized) => sevr = Some((i,end)),
                Some(MessageFields::Severity) if sevr.is_none() => sevr = Some((i,end)),
                Some(MessageFields::Code) => code = Some((i,end)),
                Some(MessageFields::Message) => message = Some((i,end)),
                Some(MessageFields::Routine) => routine = Some((i,end)),
                Some(MessageFields::Detail) => detail = Some((i,end)),
                Some(MessageFields::Hint) => hint = Some((i,end)),
                _ => {}
            }
        }

        macro_rules! foo {
            (@ $f:ident,$s:literal;$($tt:tt)*) => {
                'foo: {
                    let Some((i,end)) = $f else {
                        $($tt)*
                        break 'foo
                    };
                    write!(f, $s, body[i + 1..end].lossy())?;
                }
            };
            ($f:ident,$s:literal,?) => {
                foo!(@ $f,$s;();)
            };
            ($f:ident,$s:literal) => {
                foo!(@ $f,$s;write!(f, $s, "??")?;)
            };
        }

        foo!(sevr, "[{}]");
        foo!(message, " {}");
        write!(f, " (")?;
        foo!(routine, "{}, ");
        foo!(code, "{}");
        write!(f, ")")?;
        foo!(detail, ",\n\n{}", ?);
        foo!(hint, ",\n\nHINT: {}", ?);
        Ok(())
    }
}

macro_rules! foo {
    ($($b:literal => $s:ident,)*) => {
        pub fn from_byte(byte: u8) -> Option<MessageFields> {
            Some(match byte {
                $($b => Self::$s,)*
                _ => return None,
            })
        }
        pub fn as_str(&self) -> &'static str {
            match self {
                $(Self::$s => stringify!($s),)*
            }
        }
    };
}

impl MessageFields {
    foo! {
        b'S' => SeverityLocalized,
        b'V' => Severity,
        b'C' => Code,
        b'M' => Message,
        b'D' => Detail,
        b'H' => Hint,
        b'P' => Position,
        b'p' => InternalPosition,
        b'q' => InternalQuery,
        b'W' => Where,
        b's' => SchemaName,
        b't' => TableName,
        b'c' => ColumnName,
        b'd' => DataTypeName,
        b'n' => ConstraintName,
        b'F' => FileName,
        b'L' => Line,
        b'R' => Routine,
    }
}

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

impl std::fmt::Debug for ErrorResponse {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("Error ")?;
        MessageFields::debug(&self.body, f)
    }
}

impl std::fmt::Display for ErrorResponse {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        MessageFields::display(&self.body, f)
    }
}

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

impl std::fmt::Debug for NoticeResponse {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("Notice ")?;
        MessageFields::debug(&self.body, f)
    }
}

impl std::fmt::Display for NoticeResponse {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        MessageFields::display(&self.body, f)
    }
}