1use std::{
2 borrow::Cow,
3 num::{NonZero, NonZeroI32},
4 ptr,
5};
6
7use crate::sys;
8
9use super::MqStruct;
10
11#[derive(Debug, Clone, Copy, Hash)]
12pub struct StringCcsid<T> {
13 pub(crate) ccsid: Option<std::num::NonZeroI32>,
14 pub(crate) le: bool,
15 pub(crate) data: T,
16}
17
18impl<T> StringCcsid<T> {
19 pub const fn new(data: T, ccsid: Option<std::num::NonZeroI32>, le: bool) -> Self {
20 Self { ccsid, le, data }
21 }
22}
23
24pub type StrCcsid<'a> = StringCcsid<&'a [u8]>;
25pub type StrCcsidOwned = StringCcsid<Vec<u8>>;
26pub type StrCcsidCow<'a> = StringCcsid<Cow<'a, [u8]>>;
27
28pub const NATIVE_IS_LE: bool = (sys::MQENC_NATIVE & sys::MQENC_INTEGER_REVERSED) != 0;
29
30#[derive(derive_more::Error, derive_more::Display, derive_more::From, Debug)]
31pub enum FromStringCcsidError {
32 NonUtf8Ccsid(CcsidError),
33 Utf8Convert(std::str::Utf8Error),
34}
35
36#[derive(derive_more::Error, derive_more::Display, Debug)]
37#[display("{} is not a UTF-8 CCSID", str.ccsid.map_or(0, NonZeroI32::get))]
38pub struct CcsidError {
39 str: StrCcsidOwned,
40}
41
42impl StrCcsidOwned {
43 #[must_use]
44 pub const fn from_vec(data: Vec<u8>, ccsid: Option<NonZero<i32>>) -> Self {
45 Self {
46 ccsid,
47 le: NATIVE_IS_LE,
48 data,
49 }
50 }
51}
52
53impl<'a> From<&'a str> for StrCcsid<'a> {
54 fn from(value: &'a str) -> Self {
55 Self {
56 ccsid: NonZero::new(1208),
57 data: value.as_bytes(),
58 le: NATIVE_IS_LE,
59 }
60 }
61}
62
63impl<'a, T: Into<Cow<'a, str>>> From<T> for StrCcsidCow<'a> {
64 fn from(value: T) -> Self {
65 Self {
66 ccsid: NonZero::new(1208),
67 data: match value.into() {
68 Cow::Borrowed(str_val) => Cow::Borrowed(str_val.as_bytes()),
69 Cow::Owned(str_val) => Cow::Owned(str_val.into()),
70 },
71 le: NATIVE_IS_LE,
72 }
73 }
74}
75
76impl<T: ToString> From<T> for StrCcsidOwned {
77 fn from(value: T) -> Self {
78 Self {
79 ccsid: NonZero::new(1208),
80 data: value.to_string().into_bytes(),
81 le: NATIVE_IS_LE,
82 }
83 }
84}
85
86impl<T: Into<Vec<u8>>> TryFrom<StringCcsid<T>> for String {
87 type Error = FromStringCcsidError;
88
89 fn try_from(value: StringCcsid<T>) -> Result<Self, Self::Error> {
90 if value.ccsid != NonZeroI32::new(1208) {
91 return Err(FromStringCcsidError::NonUtf8Ccsid(CcsidError {
92 str: StringCcsid {
93 ccsid: value.ccsid,
94 data: value.data.into(),
95 le: NATIVE_IS_LE,
96 },
97 }));
98 }
99 Self::from_utf8(value.data.into()).map_err(|e| FromStringCcsidError::Utf8Convert(e.utf8_error()))
100 }
101}
102
103impl<'a, T: Into<Cow<'a, [u8]>>> TryFrom<StringCcsid<T>> for Cow<'a, str> {
104 type Error = FromStringCcsidError;
105
106 fn try_from(value: StringCcsid<T>) -> Result<Self, Self::Error> {
107 if value.ccsid != NonZeroI32::new(1208) {
108 return Err(FromStringCcsidError::NonUtf8Ccsid(CcsidError {
109 str: StringCcsid {
110 ccsid: value.ccsid,
111 data: value.data.into().into_owned(),
112 le: NATIVE_IS_LE,
113 },
114 }));
115 }
116
117 Ok(match value.data.into() {
118 Cow::Borrowed(bytes) => Cow::Borrowed(std::str::from_utf8(bytes)?),
119 Cow::Owned(bytes) => Cow::Owned(String::from_utf8(bytes).map_err(|e| e.utf8_error())?),
120 })
121 }
122}
123
124pub trait EncodedString {
125 fn ccsid(&self) -> Option<NonZeroI32>;
126 fn data(&self) -> &[u8];
127}
128
129impl EncodedString for str {
130 fn ccsid(&self) -> Option<NonZeroI32> {
131 NonZeroI32::new(1208) }
133
134 fn data(&self) -> &[u8] {
135 unsafe { &*(std::ptr::from_ref(self) as *const [u8]) }
136 }
137}
138
139impl<T: AsRef<[u8]>> EncodedString for StringCcsid<T> {
140 fn ccsid(&self) -> Option<NonZeroI32> {
141 self.ccsid
142 }
143
144 fn data(&self) -> &[u8] {
145 self.data.as_ref()
146 }
147}
148
149impl<'a> MqStruct<'a, sys::MQCHARV> {
150 pub fn from_encoded_str(value: &'a (impl EncodedString + ?Sized)) -> Self {
151 let data = value.data();
152 let len = data
153 .len()
154 .try_into()
155 .expect("string length exceeds maximum positive MQLONG for MQCHARV");
156 MqStruct::new(sys::MQCHARV {
157 VSPtr: ptr::from_ref(data).cast_mut().cast(),
158 VSLength: len,
159 VSBufSize: len,
160 VSCCSID: value.ccsid().map_or(0, NonZero::into),
161 ..sys::MQCHARV::default()
162 })
163 }
164}
165
166impl<T: Default> Default for StringCcsid<T> {
167 fn default() -> Self {
168 Self {
169 ccsid: NonZero::new(1208),
170 data: Default::default(),
171 le: NATIVE_IS_LE,
172 }
173 }
174}
175
176#[cfg(test)]
177mod test {
178 use std::{borrow::Cow, num::NonZero};
179
180 use crate::{StrCcsid, StrCcsidCow, StringCcsid};
181
182 use super::NATIVE_IS_LE;
183
184 const NON_UTF8_COW: StrCcsidCow = StrCcsidCow {
185 ccsid: NonZero::new(450),
186 data: Cow::Borrowed(b"Hello".as_slice()),
187 le: NATIVE_IS_LE,
188 };
189
190 #[test]
191 fn strccsidcow() {
192 let basic_cow: StrCcsidCow = StringCcsid::from("Hello");
193 let basic_ref: StrCcsid = StringCcsid::from("Hello");
194 assert!(
195 TryInto::<String>::try_into(basic_cow.clone()).is_ok(),
196 "Convert must be successful when CCSID = 1208"
197 );
198 assert!(
199 TryInto::<Cow<str>>::try_into(basic_ref).is_ok(),
200 "Convert must be successful from ref"
201 );
202
203 assert!(
204 TryInto::<String>::try_into(NON_UTF8_COW).is_err(),
205 "Convert must fail when CCSID != 1208"
206 );
207
208 assert!(
209 TryInto::<Cow<str>>::try_into(basic_cow.clone()).is_ok(),
210 "Convert from Cow to Cow"
211 );
212 }
214}