yyjson_rs/
write.rs

1use std::{ffi::CStr, fmt::Display, mem::MaybeUninit};
2
3use num_derive::FromPrimitive;
4use std::ffi::c_void;
5
6use crate::YyjsonAllocator;
7use yyjson_sys as ffi;
8
9#[derive(Debug)]
10pub enum WriteError {
11    OnCreateErr(&'static str),
12    OnWrite { code: WriteCode, msg: &'static str },
13}
14
15impl std::fmt::Display for WriteError {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        use WriteError::*;
18        match self {
19            OnCreateErr(s) => write!(f, "On create error: {}", s),
20            OnWrite { code, msg } => {
21                write!(f, "{:?}: {}", code, msg)
22            }
23        }
24    }
25}
26
27impl std::error::Error for WriteError {}
28
29#[derive(Debug, FromPrimitive, PartialEq)]
30#[repr(u32)]
31pub enum WriteCode {
32    Success = 0,
33    ErrorInvalidParameter = 1,
34    ErrorMemoryAllocation = 2,
35    ErrorInvalidValueType = 3,
36    ErrorNaNOrInf = 4,
37    ErrorFileOpen = 5,
38    ErrorFileWrite = 6,
39    ErrorInvalidString = 7,
40}
41
42impl WriteError {
43    #[inline(always)]
44    pub fn is_mem_allocation_err(&self) -> bool {
45        match self {
46            Self::OnWrite { code, .. } => code == &WriteCode::ErrorMemoryAllocation,
47            _ => false,
48        }
49    }
50}
51
52impl From<&ffi::yyjson_write_err> for WriteError {
53    fn from(v: &ffi::yyjson_write_err) -> Self {
54        use WriteError::*;
55        let code: WriteCode = match num_traits::cast::FromPrimitive::from_u32(v.code) {
56            Some(v) => v,
57            None => return OnCreateErr("yyjson_write_code should be in range 0..=7"),
58        };
59
60        if code == WriteCode::Success {
61            return OnCreateErr("yyjson_write_code is SUCCESS (non-error)");
62        }
63
64        if v.msg.is_null() {
65            return OnCreateErr("error msg string is null yet error occured");
66        }
67
68        // SAFETY: if an error occured (i.e. write_code is not SUCCESS) then
69        // msg is guaranteed to be non-null and point to a statically allocated
70        // c-string if v is non error
71        let msg = {
72            let as_c_str = unsafe { CStr::from_ptr(v.msg) };
73            match as_c_str.to_str() {
74                Ok(s) => s,
75                Err(_) => return OnCreateErr("error msg string is invalid utf-8"),
76            }
77        };
78
79        OnWrite { code, msg }
80    }
81}
82
83#[derive(Debug, Default)]
84// By default (all options set to false), JSON is written in a minified format,
85// inf, NaN, invalid utf-8 values are reported as errors and yyjson does not
86// escape unicode or slashes
87pub struct WriteOptions {
88    // with 4 spaces
89    pub pretty: bool,
90
91    // overrides write_pretty
92    pub pretty_with_two_spaces: bool,
93
94    // makes the output ascii-only
95    pub escape_unicode: bool,
96
97    // escapes forward slash character '/' as '\/'
98    pub escape_slashes: bool,
99
100    pub allow_inf_and_nan: bool,
101
102    // write inf and Nan as null instead of reporting error,
103    // overrides allow_inf_and_nan
104    pub inf_and_nan_as_null: bool,
105
106    // e.g. for ndjson
107    pub add_newline_at_end: bool,
108}
109
110impl WriteOptions {
111    pub fn to_write_flag(&self) -> u32 {
112        let mut flag: u32 = ffi::YYJSON_WRITE_NOFLAG;
113        if self.pretty {
114            flag |= ffi::YYJSON_WRITE_PRETTY;
115        }
116        if self.pretty_with_two_spaces {
117            flag |= ffi::YYJSON_WRITE_PRETTY_TWO_SPACES;
118        }
119        if self.escape_unicode {
120            flag |= ffi::YYJSON_WRITE_ESCAPE_UNICODE;
121        }
122        if self.escape_slashes {
123            flag |= ffi::YYJSON_WRITE_ESCAPE_SLASHES;
124        }
125        if self.allow_inf_and_nan {
126            flag |= ffi::YYJSON_WRITE_ALLOW_INF_AND_NAN;
127        }
128        if self.inf_and_nan_as_null {
129            flag |= ffi::YYJSON_WRITE_INF_AND_NAN_AS_NULL;
130        }
131        if self.add_newline_at_end {
132            flag |= ffi::YYJSON_WRITE_NEWLINE_AT_END;
133        }
134        flag
135    }
136}
137
138pub struct Writer<'a> {
139    write_flag: u32,
140    alc: *mut ffi::yyjson_alc,
141    _alloc_lifetime: std::marker::PhantomData<&'a ()>,
142}
143
144impl<'a> Writer<'a> {
145    pub fn new(allocator: YyjsonAllocator<'a>, options: Option<&WriteOptions>) -> Writer<'a> {
146        let write_flag = if let Some(v) = options {
147            v.to_write_flag()
148        } else {
149            ffi::YYJSON_WRITE_NOFLAG
150        };
151        let alc = allocator.p;
152        Self {
153            write_flag,
154            alc,
155            _alloc_lifetime: std::marker::PhantomData,
156        }
157    }
158
159    #[inline(always)]
160    pub fn write(
161        &'a self,
162        do_write: impl Fn(u32, *mut ffi::yyjson_alc, &mut usize, *mut ffi::yyjson_write_err) -> *mut u8,
163    ) -> Result<WriteOutput<'a>, WriteError> {
164        let mut len: usize = 0; // receives output length, will not include NUL
165        let mut write_err = MaybeUninit::<ffi::yyjson_write_err>::uninit();
166        // SAFETY: it is okay if alc is null. If it is non-null, by construction
167        // it points to a valid ffi::yyjson_alc struct
168        let ptr = do_write(self.write_flag, self.alc, &mut len, write_err.as_mut_ptr());
169        if ptr.is_null() {
170            // This means either something is wrong with the document OR we
171            // passed a null document
172            // SAFETY: if error occurs, yyjson_write_opts will set the fields in
173            // the write_err appropriately
174            let write_err = unsafe { write_err.assume_init() };
175            Err((&write_err).into())
176        } else {
177            Ok(WriteOutput {
178                ptr,
179                len,
180                alc: self.alc,
181                _lifetime: std::marker::PhantomData,
182            })
183        }
184    }
185}
186
187#[derive(Debug)]
188pub struct WriteOutput<'a> {
189    ptr: *mut u8,
190    len: usize,
191    alc: *mut ffi::yyjson_alc,
192    _lifetime: std::marker::PhantomData<&'a ()>,
193}
194
195impl WriteOutput<'_> {
196    #[inline(always)]
197    pub fn as_str(&self) -> &str {
198        let buf = self.as_bytes();
199
200        // SAFETY: when parsing the input, yyjson checks if it is valid utf-8 (
201        // unless compiled with the flag disabling utf-8 checks in which case
202        // we have to do it prior to passing the input to yyjson). Therefore
203        // when writing out, yyjson outputs valid utf-8 from the internal doc
204        // structure. Additionally, DocWriteOutput is only constructed if the
205        // write was successful.
206        let s: &str = unsafe { std::str::from_utf8_unchecked(buf) };
207        s
208    }
209
210    #[inline(always)]
211    pub fn as_bytes(&self) -> &[u8] {
212        // SAFETY: at the point DocWriteOutput is constructed, the ptr value is
213        // assigned only if it is non-null and the len value is set by yyjson if
214        // the write is successful
215        let buf: &[u8] = unsafe { std::slice::from_raw_parts(self.ptr, self.len) };
216        return buf;
217    }
218}
219
220impl Display for WriteOutput<'_> {
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        let s = self.as_str();
223        write!(f, "{}", s)
224    }
225}
226
227impl Drop for WriteOutput<'_> {
228    fn drop(&mut self) {
229        if self.alc.is_null() {
230            // SAFETY: if the output was not allocated into a passed allocator
231            // then yyjson allocates it using malloc hence it should be freed
232            // via a call to libc free
233            unsafe { libc::free(self.ptr.cast::<c_void>()) }
234        } else {
235            // SAFETY: if alc is non-NULL, it holds a validly constructed
236            // Allocator
237            let alc: &ffi::yyjson_alc = unsafe { &*self.alc };
238            let free_fn = alc
239                .free
240                .expect("if alc is non-null, then the free field should be set to Some");
241            // SAFETY: free_fn is not None since we've unwrapped it above. In
242            // all cases where an Allocator is constructed, free field is
243            // initialized to the appropriate memory allocation function
244            unsafe { free_fn(alc.ctx, self.ptr.cast::<c_void>()) };
245        }
246    }
247}