1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// © 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);