sqlstate-inline 0.1.2

Memory efficient const-friendly types for SQLSTATE codes
Documentation
// © 2022 Christoph Grenz <https://grenz-bonn.de>
//
// SPDX-License-Identifier: MPL-2.0

use crate::error::ParseError;

/// A general category for a given SQLSTATE code.
///
/// The conditions `Success` and `NoData` are normally hidden by database driver abstractions.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum Category {
	/// Category `S`: Successful completion.
	Success = b'S',
	/// Category `W`: Successful completion with a warning.
	Warning = b'W',
	/// Category `N`: No data / empty result.
	NoData = b'N',
	/// Category `X`: Exceptional condition.
	Exception = b'X',
}

impl From<Category> for char {
	/// Converts the `Category` into the corresponding category abbrevation character.
	/// ```
	/// # use sqlstate_inline::SqlState;
	/// let success = SqlState("00000");
	/// let category = success.category();
	/// assert_eq!(char::from(category), 'S');
	/// ```
	#[inline]
	fn from(c: Category) -> Self {
		c as u8 as char
	}
}

impl From<Category> for u8 {
	/// Converts the `Category` into the corresponding ASCII byte character.
	/// ```
	/// # use sqlstate_inline::SqlState;
	/// let no_data = SqlState("02000");
	/// let category = no_data.category();
	/// assert_eq!(u8::from(category), b'N');
	/// ```
	#[inline]
	fn from(c: Category) -> Self {
		c as u8
	}
}

impl TryFrom<char> for Category {
	type Error = crate::error::ParseError;

	/// Constructs a `Category` from a category abbrevation character.
	/// ```
	/// # use sqlstate_inline::Category;
	/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
	/// let category: Category = 'X'.try_into()?;
	/// # Ok(())
	/// # }
	/// ```
	#[inline]
	fn try_from(char: char) -> Result<Self, Self::Error> {
		Ok(match char {
			'S' => Category::Success,
			'W' => Category::Warning,
			'N' => Category::NoData,
			'X' => Category::Exception,
			_ => {
				return Err(ParseError::InvalidChar {
					byte: {
						let mut buf = [0; 4];
						char.encode_utf8(&mut buf);
						buf[0]
					},
					position: 0,
				})
			}
		})
	}
}

impl TryFrom<u8> for Category {
	type Error = crate::error::ParseError;

	/// Constructs a `Category` from an ASCII byte character.
	/// ```
	/// # use sqlstate_inline::Category;
	/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
	/// let category: Category = b'X'.try_into()?;
	/// # Ok(())
	/// # }
	/// ```
	#[inline]
	fn try_from(byte: u8) -> Result<Self, Self::Error> {
		Ok(match byte {
			b'S' => Category::Success,
			b'W' => Category::Warning,
			b'N' => Category::NoData,
			b'X' => Category::Exception,
			_ => return Err(ParseError::InvalidChar { byte, position: 0 }),
		})
	}
}

#[cfg(feature = "serde")]
mod _serde {
	use super::Category;
	use core::fmt;
	use serde::de::{self, Unexpected, Visitor};

	struct CatVisitor;

	impl<'de> Visitor<'de> for CatVisitor {
		type Value = Category;

		fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
			write!(f, "one of 'S', 'W', 'N' or 'X'")
		}

		fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
			if value.len() == 1 {
				self.visit_char(value.chars().next().unwrap())
			} else {
				Err(E::invalid_length(value.len(), &self))
			}
		}

		fn visit_u8<E: de::Error>(self, value: u8) -> Result<Self::Value, E> {
			value
				.try_into()
				.map_err(|_| E::invalid_value(Unexpected::Unsigned(value.into()), &self))
		}

		fn visit_char<E: de::Error>(self, value: char) -> Result<Self::Value, E> {
			value
				.try_into()
				.map_err(|_| E::invalid_value(Unexpected::Char(value), &self))
		}
	}

	impl serde::Serialize for Category {
		fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
		where
			S: serde::Serializer,
		{
			serializer.serialize_char(char::from(*self))
		}
	}
}

// Statically assert the intended memory layout (1 byte with multiple niches)
const _: () = assert!(core::mem::size_of::<Category>() == 1);
const _: () = assert!(core::mem::size_of::<Option<Category>>() == 1);
const _: () = assert!(core::mem::size_of::<Option<Option<Category>>>() == 1);