mqi/mq/
strings.rs

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) // = UTF-8 CCSID. str types are _always_ UTF-8
132    }
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        // assert_matches!(basic, Ok("Hello"));
213    }
214}