serde_saphyr/long_strings.rs
1use serde_core::de::{Deserialize, Deserializer};
2use serde_core::ser::{Serialize, Serializer};
3use std::ops::Deref;
4
5// Flow hints and block-string hints: we use newtype-struct names.
6// These are consumed by the YAML serializer via Serde's data model.
7pub(crate) const NAME_LIT_STR: &str = "__yaml_lit_str";
8pub(crate) const NAME_FOLD_STR: &str = "__yaml_fold_str";
9
10/// Force a YAML block literal string using the `|` style.
11///
12/// Emits the inner `&str` as a block scalar that preserves newlines exactly
13/// as written. Each line is indented one level deeper than the surrounding
14/// indentation where the value appears.
15///
16/// In short: use [`LitStr`] (|) to preserve line breaks exactly; use [`FoldStr`] (>) when you want
17/// readers to display line breaks as spaces (soft-wrapped paragraphs).
18///
19/// See also: [`FoldStr`], [`LitString`], [`FoldString`].
20///
21/// Behavior
22/// - Uses YAML's literal block style: a leading `|` followed by newline.
23/// - Newlines are preserved verbatim by YAML consumers.
24/// - Indentation is handled automatically by the serializer.
25/// - Works in mapping values, sequence items, and at the top level.
26///
27/// Examples
28///
29/// Top-level literal block string:
30/// ```rust
31/// # #[cfg(feature = "serialize")]
32/// # {
33/// let long = "line 1\nline 2\n".repeat(20);
34/// let out = serde_saphyr::to_string(&serde_saphyr::LitStr(&long)).unwrap();
35/// assert!(out.starts_with("|\n "));
36/// # }
37/// ```
38///
39/// As a mapping value:
40/// ```rust
41/// # #[cfg(feature = "serialize")]
42/// # {
43/// use serde::Serialize;
44/// #[derive(Serialize)]
45/// struct S { note: serde_saphyr::LitStr<'static> }
46/// let s = S { note: serde_saphyr::LitStr("a\nb") };
47/// let out = serde_saphyr::to_string(&s).unwrap();
48/// assert_eq!(out, "note: |-\n a\n b\n");
49/// # }
50/// ```
51#[derive(Clone, Copy)]
52pub struct LitStr<'a>(pub &'a str);
53
54/// Owned-string variant of [`LitStr`] that forces a YAML block literal string using the `|` style.
55///
56/// This works the same as [`LitStr`] but takes ownership of a String. Useful when you already
57/// have an owned String and want to avoid borrowing lifetimes.
58///
59/// See also: [`FoldStr`], [`FoldString`].
60///
61/// Example
62/// ```rust
63/// # #[cfg(feature = "serialize")]
64/// # {
65/// let out = serde_saphyr::to_string(&serde_saphyr::LitString("line 1\nline 2".to_string())).unwrap();
66/// assert_eq!(out, "|-\n line 1\n line 2\n");
67/// # }
68/// ```
69#[derive(Clone, Debug, PartialEq, Eq)]
70pub struct LitString(pub String);
71
72/// Force a YAML folded block string using the `>` style.
73///
74/// Emits the inner `&str` as a block scalar that suggests folding line breaks
75/// to spaces for display by YAML consumers (empty lines are kept as paragraph
76/// breaks). The serializer writes each line on its own; the folding behavior is
77/// applied by consumers of the YAML, not during serialization.
78///
79/// In short: use [`FoldStr`] (>) for human-readable paragraphs that may soft-wrap; use
80/// [`LitStr`] (|) when you need to preserve line breaks exactly as written.
81///
82/// See also: [`LitStr`], [`LitString`], [`FoldString`].
83///
84/// Behavior
85/// - Uses YAML's folded block style: a leading `>` followed by newline.
86/// - Intended for human-readable paragraphs where soft-wrapping is desirable.
87/// - Indentation is handled automatically by the serializer.
88/// - Works in mapping values, sequence items, and at the top level.
89///
90/// Examples
91///
92/// Top-level folded block string:
93/// ```rust
94/// # #[cfg(feature = "serialize")]
95/// # {
96/// let out = serde_saphyr::to_string(&serde_saphyr::FoldStr("line 1\nline 2")).unwrap();
97/// assert_eq!(out, ">\n line 1\n line 2\n");
98/// # }
99/// ```
100///
101/// As a mapping value:
102/// ```rust
103/// # #[cfg(feature = "serialize")]
104/// # {
105/// use serde::Serialize;
106/// #[derive(Serialize)]
107/// struct S { note: serde_saphyr::FoldStr<'static> }
108/// let s = S { note: serde_saphyr::FoldStr("a\nb") };
109/// let out = serde_saphyr::to_string(&s).unwrap();
110/// assert_eq!(out, "note: >\n a\n b\n");
111/// # }
112/// ```
113#[derive(Clone, Copy)]
114pub struct FoldStr<'a>(pub &'a str);
115
116/// Owned-string variant of [`FoldStr`] that forces a YAML folded block string using the `>` style.
117///
118/// Same behavior as [`FoldStr`] but owns a String.
119///
120/// See also: [`LitStr`], [`LitString`].
121///
122/// Example
123/// ```rust
124/// # #[cfg(feature = "serialize")]
125/// # {
126/// let out = serde_saphyr::to_string(&serde_saphyr::FoldString("line 1\nline 2".to_string())).unwrap();
127/// assert_eq!(out, ">\n line 1\n line 2\n");
128/// # }
129/// ```
130#[derive(Clone, Debug, PartialEq, Eq)]
131pub struct FoldString(pub String);
132
133impl Serialize for LitStr<'_> {
134 fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
135 // Always delegate decision to the YAML serializer so it can apply options.
136 s.serialize_newtype_struct(NAME_LIT_STR, &self.0)
137 }
138}
139impl Serialize for LitString {
140 fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
141 s.serialize_newtype_struct(NAME_LIT_STR, &self.0)
142 }
143}
144impl Serialize for FoldStr<'_> {
145 fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
146 s.serialize_newtype_struct(NAME_FOLD_STR, &self.0)
147 }
148}
149impl Serialize for FoldString {
150 fn serialize<S: Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
151 s.serialize_newtype_struct(NAME_FOLD_STR, &self.0)
152 }
153}
154
155// Deserialization for owned block string wrappers: delegate to String
156impl<'de> Deserialize<'de> for LitString {
157 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
158 String::deserialize(deserializer).map(LitString)
159 }
160}
161impl<'de> Deserialize<'de> for FoldString {
162 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
163 String::deserialize(deserializer).map(FoldString)
164 }
165}
166
167// ------------------------------------------------------------
168// Convenience conversions / comparisons for block-string wrappers
169// ------------------------------------------------------------
170
171impl<'a> From<&'a str> for LitStr<'a> {
172 fn from(s: &'a str) -> Self {
173 LitStr(s)
174 }
175}
176
177impl<'a> From<&'a str> for FoldStr<'a> {
178 fn from(s: &'a str) -> Self {
179 FoldStr(s)
180 }
181}
182
183impl From<String> for LitString {
184 fn from(s: String) -> Self {
185 LitString(s)
186 }
187}
188
189impl From<String> for FoldString {
190 fn from(s: String) -> Self {
191 FoldString(s)
192 }
193}
194
195impl Deref for LitString {
196 type Target = str;
197 fn deref(&self) -> &Self::Target {
198 &self.0
199 }
200}
201
202impl Deref for FoldString {
203 type Target = str;
204 fn deref(&self) -> &Self::Target {
205 &self.0
206 }
207}
208
209impl Deref for LitStr<'_> {
210 type Target = str;
211 fn deref(&self) -> &Self::Target {
212 self.0
213 }
214}
215
216impl Deref for FoldStr<'_> {
217 type Target = str;
218 fn deref(&self) -> &Self::Target {
219 self.0
220 }
221}
222
223impl LitString {
224 #[must_use]
225 pub fn into_inner(self) -> String {
226 self.0
227 }
228}
229
230impl FoldString {
231 #[must_use]
232 pub fn into_inner(self) -> String {
233 self.0
234 }
235}
236
237impl PartialEq<FoldString> for LitString {
238 fn eq(&self, other: &FoldString) -> bool {
239 self.0 == other.0
240 }
241}
242
243impl PartialEq<LitString> for FoldString {
244 fn eq(&self, other: &LitString) -> bool {
245 self.0 == other.0
246 }
247}
248
249impl PartialEq<String> for LitString {
250 fn eq(&self, other: &String) -> bool {
251 &self.0 == other
252 }
253}
254
255impl PartialEq<String> for FoldString {
256 fn eq(&self, other: &String) -> bool {
257 &self.0 == other
258 }
259}
260
261impl PartialEq<&str> for LitString {
262 fn eq(&self, other: &&str) -> bool {
263 self.0 == *other
264 }
265}
266
267impl PartialEq<&str> for FoldString {
268 fn eq(&self, other: &&str) -> bool {
269 self.0 == *other
270 }
271}
272
273impl PartialEq<str> for LitString {
274 fn eq(&self, other: &str) -> bool {
275 self.0 == other
276 }
277}
278
279impl PartialEq<str> for FoldString {
280 fn eq(&self, other: &str) -> bool {
281 self.0 == other
282 }
283}