sqlstate_inline/
category.rs

1// © 2022 Christoph Grenz <https://grenz-bonn.de>
2//
3// SPDX-License-Identifier: MPL-2.0
4
5use crate::error::ParseError;
6
7/// A general category for a given SQLSTATE code.
8///
9/// The conditions `Success` and `NoData` are normally hidden by database driver abstractions.
10#[derive(Debug, Copy, Clone, PartialEq, Eq)]
11#[repr(u8)]
12pub enum Category {
13	/// Category `S`: Successful completion.
14	Success = b'S',
15	/// Category `W`: Successful completion with a warning.
16	Warning = b'W',
17	/// Category `N`: No data / empty result.
18	NoData = b'N',
19	/// Category `X`: Exceptional condition.
20	Exception = b'X',
21}
22
23impl From<Category> for char {
24	/// Converts the `Category` into the corresponding category abbrevation character.
25	/// ```
26	/// # use sqlstate_inline::SqlState;
27	/// let success = SqlState("00000");
28	/// let category = success.category();
29	/// assert_eq!(char::from(category), 'S');
30	/// ```
31	#[inline]
32	fn from(c: Category) -> Self {
33		c as u8 as char
34	}
35}
36
37impl From<Category> for u8 {
38	/// Converts the `Category` into the corresponding ASCII byte character.
39	/// ```
40	/// # use sqlstate_inline::SqlState;
41	/// let no_data = SqlState("02000");
42	/// let category = no_data.category();
43	/// assert_eq!(u8::from(category), b'N');
44	/// ```
45	#[inline]
46	fn from(c: Category) -> Self {
47		c as u8
48	}
49}
50
51impl TryFrom<char> for Category {
52	type Error = crate::error::ParseError;
53
54	/// Constructs a `Category` from a category abbrevation character.
55	/// ```
56	/// # use sqlstate_inline::Category;
57	/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
58	/// let category: Category = 'X'.try_into()?;
59	/// # Ok(())
60	/// # }
61	/// ```
62	#[inline]
63	fn try_from(char: char) -> Result<Self, Self::Error> {
64		Ok(match char {
65			'S' => Category::Success,
66			'W' => Category::Warning,
67			'N' => Category::NoData,
68			'X' => Category::Exception,
69			_ => {
70				return Err(ParseError::InvalidChar {
71					byte: {
72						let mut buf = [0; 4];
73						char.encode_utf8(&mut buf);
74						buf[0]
75					},
76					position: 0,
77				})
78			}
79		})
80	}
81}
82
83impl TryFrom<u8> for Category {
84	type Error = crate::error::ParseError;
85
86	/// Constructs a `Category` from an ASCII byte character.
87	/// ```
88	/// # use sqlstate_inline::Category;
89	/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
90	/// let category: Category = b'X'.try_into()?;
91	/// # Ok(())
92	/// # }
93	/// ```
94	#[inline]
95	fn try_from(byte: u8) -> Result<Self, Self::Error> {
96		Ok(match byte {
97			b'S' => Category::Success,
98			b'W' => Category::Warning,
99			b'N' => Category::NoData,
100			b'X' => Category::Exception,
101			_ => return Err(ParseError::InvalidChar { byte, position: 0 }),
102		})
103	}
104}
105
106#[cfg(feature = "serde")]
107mod _serde {
108	use super::Category;
109	use core::fmt;
110	use serde::de::{self, Unexpected, Visitor};
111
112	struct CatVisitor;
113
114	impl<'de> Visitor<'de> for CatVisitor {
115		type Value = Category;
116
117		fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
118			write!(f, "one of 'S', 'W', 'N' or 'X'")
119		}
120
121		fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
122			if value.len() == 1 {
123				self.visit_char(value.chars().next().unwrap())
124			} else {
125				Err(E::invalid_length(value.len(), &self))
126			}
127		}
128
129		fn visit_u8<E: de::Error>(self, value: u8) -> Result<Self::Value, E> {
130			value
131				.try_into()
132				.map_err(|_| E::invalid_value(Unexpected::Unsigned(value.into()), &self))
133		}
134
135		fn visit_char<E: de::Error>(self, value: char) -> Result<Self::Value, E> {
136			value
137				.try_into()
138				.map_err(|_| E::invalid_value(Unexpected::Char(value), &self))
139		}
140	}
141
142	impl serde::Serialize for Category {
143		fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
144		where
145			S: serde::Serializer,
146		{
147			serializer.serialize_char(char::from(*self))
148		}
149	}
150}
151
152// Statically assert the intended memory layout (1 byte with multiple niches)
153const _: () = assert!(core::mem::size_of::<Category>() == 1);
154const _: () = assert!(core::mem::size_of::<Option<Category>>() == 1);
155const _: () = assert!(core::mem::size_of::<Option<Option<Category>>>() == 1);