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 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)]
84pub struct WriteOptions {
88 pub pretty: bool,
90
91 pub pretty_with_two_spaces: bool,
93
94 pub escape_unicode: bool,
96
97 pub escape_slashes: bool,
99
100 pub allow_inf_and_nan: bool,
101
102 pub inf_and_nan_as_null: bool,
105
106 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; let mut write_err = MaybeUninit::<ffi::yyjson_write_err>::uninit();
166 let ptr = do_write(self.write_flag, self.alc, &mut len, write_err.as_mut_ptr());
169 if ptr.is_null() {
170 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 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 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 unsafe { libc::free(self.ptr.cast::<c_void>()) }
234 } else {
235 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 unsafe { free_fn(alc.ctx, self.ptr.cast::<c_void>()) };
245 }
246 }
247}