1use derive_more::Display;
2use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
3use std::{
4 borrow::{Borrow, Cow},
5 convert::Infallible,
6 ffi::OsStr,
7 fmt::{self, Write as FmtWrite},
8 iter::FromIterator,
9 ops::Deref,
10 path::Path,
11 str::FromStr,
12 string::FromUtf8Error,
13};
14
15use crate::stack_string::StackString;
16
17#[cfg(feature = "postgres_types")]
18use bytes::BytesMut;
19#[cfg(feature = "postgres_types")]
20use postgres_types::{FromSql, IsNull, ToSql, Type};
21
22#[cfg(feature = "utoipa_types")]
23use utoipa::{PartialSchema, ToSchema};
24
25#[cfg(feature = "axum_types")]
26use axum::response::IntoResponse;
27
28#[cfg(feature = "axum_types")]
29use axum::body::Body;
30
31#[derive(Display, Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
32pub enum StackCow<'a> {
33 Borrowed(&'a str),
34 Owned(StackString),
35}
36
37impl Default for StackCow<'_> {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl<'a> StackCow<'a> {
44 #[must_use]
45 pub fn new() -> Self {
46 Self::Owned(StackString::new())
47 }
48
49 #[must_use]
50 pub fn to_owned(&self) -> StackCow<'static> {
51 self.clone().into_owned()
52 }
53
54 #[must_use]
55 pub fn into_owned(self) -> StackCow<'static> {
56 match self {
57 Self::Borrowed(b) => StackCow::Owned(b.into()),
58 Self::Owned(o) => StackCow::Owned(o),
59 }
60 }
61
62 #[must_use]
63 pub fn is_borrowed(&self) -> bool {
64 match self {
65 Self::Borrowed(_) => true,
66 Self::Owned(_) => false,
67 }
68 }
69
70 #[must_use]
71 pub fn is_owned(&self) -> bool {
72 !self.is_borrowed()
73 }
74
75 #[must_use]
76 pub fn as_str(&self) -> &str {
77 match self {
78 Self::Borrowed(s) => s,
79 Self::Owned(o) => o.as_str(),
80 }
81 }
82
83 pub fn from_utf8(vec: Vec<u8>) -> Result<Self, FromUtf8Error> {
88 String::from_utf8(vec).map(Into::into)
89 }
90
91 #[must_use]
92 pub fn from_utf8_lossy(v: &'a [u8]) -> Self {
93 StackString::from_utf8_lossy(v).into()
94 }
95
96 pub fn from_display(buf: impl fmt::Display) -> Self {
101 let mut s = StackString::new();
102 write!(s, "{buf}").unwrap();
103 s.into()
104 }
105}
106
107impl Deref for StackCow<'_> {
108 type Target = str;
109
110 fn deref(&self) -> &Self::Target {
111 match self {
112 Self::Borrowed(b) => b,
113 Self::Owned(o) => o,
114 }
115 }
116}
117
118impl Serialize for StackCow<'_> {
119 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120 where
121 S: Serializer,
122 {
123 serializer.serialize_str(self.as_str())
124 }
125}
126
127impl<'de> Deserialize<'de> for StackCow<'_> {
128 fn deserialize<D>(deserializer: D) -> Result<StackCow<'static>, D::Error>
129 where
130 D: Deserializer<'de>,
131 {
132 StackString::deserialize(deserializer).map(Into::into)
133 }
134}
135
136impl From<StackString> for StackCow<'_> {
137 fn from(item: StackString) -> Self {
138 Self::Owned(item)
139 }
140}
141
142impl<'a> From<StackCow<'a>> for StackString {
143 fn from(item: StackCow<'a>) -> Self {
144 match item {
145 StackCow::Borrowed(s) => s.into(),
146 StackCow::Owned(s) => s,
147 }
148 }
149}
150
151impl<'a> From<Cow<'a, str>> for StackCow<'a> {
152 fn from(item: Cow<'a, str>) -> Self {
153 match item {
154 Cow::Borrowed(s) => Self::Borrowed(s),
155 Cow::Owned(s) => Self::Owned(s.into()),
156 }
157 }
158}
159
160impl From<StackCow<'_>> for String {
161 fn from(item: StackCow) -> Self {
162 match item {
163 StackCow::Borrowed(s) => s.into(),
164 StackCow::Owned(s) => s.into(),
165 }
166 }
167}
168
169impl From<&StackCow<'_>> for String {
170 fn from(item: &StackCow) -> Self {
171 item.as_str().into()
172 }
173}
174
175impl From<String> for StackCow<'_> {
176 fn from(item: String) -> Self {
177 Self::Owned(item.into())
178 }
179}
180
181impl<'a> From<&'a String> for StackCow<'a> {
182 fn from(item: &'a String) -> Self {
183 Self::Borrowed(item.as_str())
184 }
185}
186
187impl<'a> From<&'a str> for StackCow<'a> {
188 fn from(item: &'a str) -> Self {
189 StackCow::Borrowed(item)
190 }
191}
192
193impl<'a> From<&'a StackCow<'a>> for &'a str {
194 fn from(item: &'a StackCow) -> &'a str {
195 item.as_str()
196 }
197}
198
199impl Borrow<str> for StackCow<'_> {
200 fn borrow(&self) -> &str {
201 self.as_str()
202 }
203}
204
205impl AsRef<str> for StackCow<'_> {
206 fn as_ref(&self) -> &str {
207 self.as_str()
208 }
209}
210
211impl AsRef<[u8]> for StackCow<'_> {
212 fn as_ref(&self) -> &[u8] {
213 self.as_str().as_bytes()
214 }
215}
216
217impl AsRef<OsStr> for StackCow<'_> {
218 fn as_ref(&self) -> &OsStr {
219 self.as_str().as_ref()
220 }
221}
222
223impl AsRef<Path> for StackCow<'_> {
224 fn as_ref(&self) -> &Path {
225 Path::new(self)
226 }
227}
228
229impl FromStr for StackCow<'_> {
230 type Err = Infallible;
231 fn from_str(s: &str) -> Result<Self, Self::Err> {
232 Ok(Self::Owned(s.into()))
233 }
234}
235
236impl<'a> PartialEq<Cow<'a, str>> for StackCow<'_> {
237 #[inline]
238 fn eq(&self, other: &Cow<'a, str>) -> bool {
239 PartialEq::eq(&self[..], &other[..])
240 }
241}
242
243impl<'a> PartialOrd<Cow<'a, str>> for StackCow<'_> {
244 fn partial_cmp(&self, other: &Cow<'a, str>) -> Option<std::cmp::Ordering> {
245 PartialOrd::partial_cmp(&self[..], &other[..])
246 }
247}
248
249impl PartialEq<String> for StackCow<'_> {
250 #[inline]
251 fn eq(&self, other: &String) -> bool {
252 PartialEq::eq(&self[..], &other[..])
253 }
254}
255
256impl PartialOrd<String> for StackCow<'_> {
257 fn partial_cmp(&self, other: &String) -> Option<std::cmp::Ordering> {
258 PartialOrd::partial_cmp(&self[..], &other[..])
259 }
260}
261
262impl PartialEq<str> for StackCow<'_> {
263 #[inline]
264 fn eq(&self, other: &str) -> bool {
265 let s: &str = self.as_ref();
266 PartialEq::eq(s, other)
267 }
268}
269
270impl PartialEq<&str> for StackCow<'_> {
271 #[inline]
272 fn eq(&self, other: &&str) -> bool {
273 PartialEq::eq(&self[..], &other[..])
274 }
275}
276
277impl<'a> PartialEq<StackCow<'a>> for &str {
278 fn eq(&self, other: &StackCow<'a>) -> bool {
279 self.eq(&other.as_str())
280 }
281}
282
283impl FromIterator<char> for StackCow<'_> {
284 fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
285 Self::Owned(StackString::from_iter(iter))
286 }
287}
288
289impl PartialOrd<str> for StackCow<'_> {
290 fn partial_cmp(&self, other: &str) -> Option<std::cmp::Ordering> {
291 self.as_str().partial_cmp(other)
292 }
293}
294
295impl PartialOrd<&str> for StackCow<'_> {
296 fn partial_cmp(&self, other: &&str) -> Option<std::cmp::Ordering> {
297 self.as_str().partial_cmp(*other)
298 }
299}
300
301impl<'a> PartialOrd<StackCow<'a>> for &str {
302 fn partial_cmp(&self, other: &StackCow<'a>) -> Option<std::cmp::Ordering> {
303 self.partial_cmp(&other.as_str())
304 }
305}
306
307#[cfg(feature = "postgres_types")]
308impl<'a> FromSql<'a> for StackCow<'a> {
309 fn from_sql(
310 ty: &Type,
311 raw: &'a [u8],
312 ) -> Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
313 let s = <&'a str as FromSql>::from_sql(ty, raw)?;
314 Ok(s.into())
315 }
316
317 fn accepts(ty: &Type) -> bool {
318 <&'a str as FromSql>::accepts(ty)
319 }
320}
321
322#[cfg(feature = "postgres_types")]
323impl<'a> ToSql for StackCow<'a> {
324 fn to_sql(
325 &self,
326 ty: &Type,
327 out: &mut BytesMut,
328 ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>>
329 where
330 Self: Sized,
331 {
332 ToSql::to_sql(&self.as_str(), ty, out)
333 }
334
335 fn accepts(ty: &Type) -> bool
336 where
337 Self: Sized,
338 {
339 <String as ToSql>::accepts(ty)
340 }
341
342 fn to_sql_checked(
343 &self,
344 ty: &Type,
345 out: &mut BytesMut,
346 ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>> {
347 self.as_str().to_sql_checked(ty, out)
348 }
349}
350
351#[cfg(feature = "utoipa_types")]
352impl<'a> PartialSchema for StackCow<'a> {
353 fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
354 str::schema()
355 }
356}
357
358#[cfg(feature = "utoipa_types")]
359impl<'a> ToSchema for StackCow<'a> {
360 fn name() -> Cow<'static, str> {
361 str::name()
362 }
363}
364
365#[cfg(feature = "axum_types")]
366impl<'a> IntoResponse for StackCow<'a> {
367 fn into_response(self) -> axum::response::Response {
368 let s: String = self.into();
369 s.into_response()
370 }
371}
372
373#[cfg(feature = "axum_types")]
374impl<'a> From<StackCow<'a>> for Body {
375 fn from(value: StackCow<'a>) -> Self {
376 let s: String = value.into();
377 s.into()
378 }
379}
380
381#[cfg(test)]
382mod tests {
383 use rand::{rng as thread_rng, Rng};
384 use serde::Deserialize;
385
386 use crate::{StackCow, StackString};
387
388 #[test]
389 fn test_default() {
390 assert_eq!(StackCow::new(), StackCow::default());
391 }
392
393 #[test]
394 fn test_from_utf8() {
395 let mut rng = thread_rng();
396 let v: Vec<_> = (0..20).map(|_| rng.random::<u8>() & 0x7f).collect();
397 let s0 = String::from_utf8(v.clone()).unwrap();
398 let s1 = StackCow::from_utf8(v).unwrap();
399 assert_eq!(s0.as_str(), s1.as_str());
400
401 let v: Vec<_> = (0..20).map(|_| rng.random::<u8>()).collect();
402 let s0 = String::from_utf8(v.clone());
403 let s1 = StackCow::from_utf8(v);
404
405 match s0 {
406 Ok(s) => assert_eq!(s.as_str(), s1.unwrap().as_str()),
407 Err(e) => assert_eq!(e, s1.unwrap_err()),
408 }
409 }
410
411 #[test]
412 fn test_string_from_stack_cow() {
413 let s0 = StackCow::from("Hello there");
414 let s1: String = s0.clone().into();
415 assert_eq!(s0.as_str(), s1.as_str());
416 }
417
418 #[test]
419 fn test_stack_cow_from_string() {
420 let s0 = String::from("Hello there");
421 let s1: StackCow = s0.clone().into();
422 assert_eq!(s0.as_str(), s1.as_str());
423 let s1: StackCow = (&s0).into();
424 assert_eq!(s0.as_str(), s1.as_str());
425 }
426
427 #[test]
428 fn test_borrow() {
429 use std::borrow::Borrow;
430 let s = StackCow::from("Hello");
431 let st: &str = s.borrow();
432 assert_eq!(st, "Hello");
433 }
434
435 #[test]
436 fn test_as_ref() {
437 use std::path::Path;
438
439 let s = StackCow::from("Hello");
440 let st: &str = s.as_ref();
441 assert_eq!(st, s.as_str());
442 let bt: &[u8] = s.as_ref();
443 assert_eq!(bt, s.as_bytes());
444 let pt: &Path = s.as_ref();
445 assert_eq!(pt, Path::new("Hello"));
446 }
447
448 #[test]
449 fn test_from_str() {
450 let s = StackCow::from("Hello");
451 assert_eq!(s, StackCow::Borrowed("Hello"));
452 }
453
454 #[test]
455 fn test_partialeq_cow() {
456 use std::path::Path;
457 let p = Path::new("Hello");
458 let ps = p.to_string_lossy();
459 let s = StackCow::from("Hello");
460 assert_eq!(s, ps);
461 let p = Path::new("alpha");
462 let ps: StackCow<'_> = p.to_string_lossy().into();
463 let s = StackCow::from("beta");
464 assert!(s > ps);
465 }
466
467 #[test]
468 fn test_partial_eq_string() {
469 assert_eq!(StackCow::from("Hello"), String::from("Hello"));
470 assert_eq!(StackCow::from("Hello"), "Hello");
471 assert_eq!(&StackCow::from("Hello"), "Hello");
472 assert!(StackCow::from("alpha") < "beta");
473 assert!("beta" > StackCow::from("alpha"));
474 }
475
476 #[test]
477 fn test_from_iterator_char() {
478 let mut rng = thread_rng();
479 let v: Vec<char> = (0..20).map(|_| rng.random::<char>()).collect();
480 let s0: StackCow = v.iter().map(|x| *x).collect();
481 let s1: String = v.iter().map(|x| *x).collect();
482 assert_eq!(s0, s1);
483 }
484
485 #[test]
486 fn test_contains_stack_cow() {
487 let a: StackCow = "hey there".into();
488 let b: StackCow = "hey".into();
489 assert!(a.contains(b.as_str()));
490 }
491
492 #[test]
493 fn test_contains_char() {
494 let a: StackCow = "hey there".into();
495 assert!(a.contains(' '));
496 }
497
498 #[test]
499 fn test_equality() {
500 let s: StackCow = "hey".into();
501 assert_eq!(Some(&s).map(Into::into), Some("hey"));
502 }
503
504 #[cfg(feature = "postgres_types")]
505 use bytes::BytesMut;
506 #[cfg(feature = "postgres_types")]
507 use postgres_types::{FromSql, IsNull, ToSql, Type};
508
509 #[cfg(feature = "postgres_types")]
510 #[test]
511 fn test_from_sql() {
512 let raw = b"Hello There";
513 let t = Type::TEXT;
514 let s = StackCow::from_sql(&t, raw).unwrap();
515 assert_eq!(s, StackCow::from("Hello There"));
516
517 assert!(<StackCow as FromSql>::accepts(&t));
518 }
519
520 #[cfg(feature = "postgres_types")]
521 #[test]
522 fn test_to_sql() {
523 let s = StackCow::from("Hello There");
524 let t = Type::TEXT;
525 assert!(<StackCow as ToSql>::accepts(&t));
526 let mut buf = BytesMut::new();
527 match s.to_sql(&t, &mut buf).unwrap() {
528 IsNull::Yes => assert!(false),
529 IsNull::No => {}
530 }
531 assert_eq!(buf.as_ref(), b"Hello There");
532 }
533
534 #[test]
535 fn test_from_display() {
536 use std::fmt::Display;
537
538 struct Test {}
539
540 impl Display for Test {
541 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
542 f.write_str("THIS IS A TEST")
543 }
544 }
545
546 let t = Test {};
547 let s = StackCow::from_display(t);
548 assert_eq!(s, StackCow::from(StackString::from("THIS IS A TEST")));
549 }
550
551 #[test]
552 fn test_from_utf8_lossy() {
553 let mut v = Vec::new();
554 v.extend_from_slice("this is a test".as_bytes());
555 v.push(0xff);
556 v.extend_from_slice("yes".as_bytes());
557 let s = StackCow::from_utf8_lossy(&v);
558 assert_eq!(s.len(), 20);
559 assert_eq!(s.is_owned(), true);
560 let s: StackString = s.into();
561 assert_eq!(s.len(), 20);
562 assert_eq!(s.is_inline(), true);
563 }
564
565 #[test]
566 fn test_serde() {
567 let s = StackCow::from("HELLO");
568 let t = "HELLO";
569 let s = serde_json::to_vec(&s).unwrap();
570 let t = serde_json::to_vec(t).unwrap();
571 assert_eq!(s, t);
572
573 let s = r#"{"a": "b"}"#;
574
575 #[derive(Deserialize)]
576 struct A<'a> {
577 a: StackCow<'a>,
578 }
579
580 #[derive(Deserialize)]
581 struct B {
582 a: String,
583 }
584
585 let a: A = serde_json::from_str(s).unwrap();
586 let b: B = serde_json::from_str(s).unwrap();
587 assert_eq!(a.a.as_str(), b.a.as_str());
588 }
589}