Skip to content

Commit

Permalink
SerializeError
Browse files Browse the repository at this point in the history
  • Loading branch information
ijl committed Dec 5, 2021
1 parent 8f72579 commit 67d5a3c
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 61 deletions.
60 changes: 56 additions & 4 deletions src/exc.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,59 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

use std::ffi::CStr;
use std::ptr::NonNull;

pub const INVALID_STR: &str = "str is not valid UTF-8: surrogates not allowed";
pub const RECURSION_LIMIT_REACHED: &str = "Recursion limit reached";
pub const DATETIME_LIBRARY_UNSUPPORTED: &str = "datetime's timezone library is not supported: use datetime.timezone.utc, pendulum, pytz, or dateutil";
pub const TIME_HAS_TZINFO: &str = "datetime.time must not have tzinfo set";
pub const KEY_MUST_BE_STR: &str = "Dict key must be str";

pub enum SerializeError {
DatetimeLibraryUnsupported,
DefaultRecursionLimit,
Integer53Bits,
Integer64Bits,
InvalidStr,
KeyMustBeStr,
RecursionLimit,
TimeHasTzinfo,
DictIntegerKey64Bit,
DictKeyInvalidType,
NumpyMalformed,
NumpyNotCContiguous,
NumpyUnsupportedDatatype,
UnsupportedType(NonNull<pyo3::ffi::PyObject>),
}

impl std::fmt::Display for SerializeError {
#[cold]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
SerializeError::DatetimeLibraryUnsupported => write!(f, "datetime's timezone library is not supported: use datetime.timezone.utc, pendulum, pytz, or dateutil"),
SerializeError::DefaultRecursionLimit => {
write!(f, "default serializer exceeds recursion limit")
}
SerializeError::Integer53Bits => write!(f, "Integer exceeds 53-bit range"),
SerializeError::Integer64Bits => write!(f, "Integer exceeds 64-bit range"),
SerializeError::InvalidStr => write!(f, "{}", INVALID_STR),
SerializeError::KeyMustBeStr => write!(f, "Dict key must be str"),
SerializeError::RecursionLimit => write!(f, "Recursion limit reached"),
SerializeError::TimeHasTzinfo => write!(f, "datetime.time must not have tzinfo set"),
SerializeError::DictIntegerKey64Bit => {
write!(f, "Dict integer key must be within 64-bit range")
}
SerializeError::DictKeyInvalidType => {
write!(f, "Dict key must a type serializable with OPT_NON_STR_KEYS")
}
SerializeError::NumpyMalformed => write!(f, "numpy array is malformed"),
SerializeError::NumpyNotCContiguous => write!(
f,
"numpy array is not C contiguous; use ndarray.tolist() in default"
),
SerializeError::NumpyUnsupportedDatatype => {
write!(f, "unsupported datatype in numpy array")
}
SerializeError::UnsupportedType(ptr) => {
let name = unsafe { CStr::from_ptr((*ob_type!(ptr.as_ptr())).tp_name).to_string_lossy() };
write!(f, "Type is not JSON serializable: {}", name)
}
}
}
}
6 changes: 3 additions & 3 deletions src/serialize/dataclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ impl<'p> Serialize for DataclassFastSerializer {
self.default,
);
if unlikely!(unsafe { ob_type!(key) != STR_TYPE }) {
err!(KEY_MUST_BE_STR)
err!(SerializeError::KeyMustBeStr)
}
{
let data = read_utf8_from_str(key, &mut str_size);
if unlikely!(data.is_null()) {
err!(INVALID_STR)
err!(SerializeError::InvalidStr)
}
let key_as_str = str_from_slice!(data, str_size);
if unlikely!(key_as_str.as_bytes()[0] == b'_') {
Expand Down Expand Up @@ -151,7 +151,7 @@ impl<'p> Serialize for DataclassFallbackSerializer {
{
let data = read_utf8_from_str(attr, &mut str_size);
if unlikely!(data.is_null()) {
err!(INVALID_STR);
err!(SerializeError::InvalidStr);
}
let key_as_str = str_from_slice!(data, str_size);
if key_as_str.as_bytes()[0] == b'_' {
Expand Down
2 changes: 1 addition & 1 deletion src/serialize/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ impl<'p> Serialize for DateTime {
{
let mut buf = DateTimeBuffer::new();
if self.write_buf(&mut buf, self.opts).is_err() {
err!(DATETIME_LIBRARY_UNSUPPORTED)
err!(SerializeError::DatetimeLibraryUnsupported)
}
serializer.serialize_str(str_from_slice!(buf.as_ptr(), buf.len()))
}
Expand Down
15 changes: 4 additions & 11 deletions src/serialize/default.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

use crate::exc::*;
use crate::opt::*;
use crate::serialize::serializer::*;

use serde::ser::{Serialize, Serializer};
use std::ffi::CStr;

use std::ptr::NonNull;

#[cold]
#[inline(never)]
fn format_err(ptr: *mut pyo3::ffi::PyObject) -> String {
let name = unsafe { CStr::from_ptr((*ob_type!(ptr)).tp_name).to_string_lossy() };
format_args!("Type is not JSON serializable: {}", name).to_string()
}

pub struct DefaultSerializer {
ptr: *mut pyo3::ffi::PyObject,
opts: Opt,
Expand Down Expand Up @@ -50,15 +43,15 @@ impl<'p> Serialize for DefaultSerializer {
match self.default {
Some(callable) => {
if unlikely!(self.default_calls == RECURSION_LIMIT) {
err!("default serializer exceeds recursion limit")
err!(SerializeError::DefaultRecursionLimit)
}
let default_obj = ffi!(PyObject_CallFunctionObjArgs(
callable.as_ptr(),
self.ptr,
std::ptr::null_mut() as *mut pyo3::ffi::PyObject
));
if unlikely!(default_obj.is_null()) {
err!(format_err(self.ptr))
err!(SerializeError::UnsupportedType(nonnull!(self.ptr)))
} else {
let res = PyObjectSerializer::new(
default_obj,
Expand All @@ -72,7 +65,7 @@ impl<'p> Serialize for DefaultSerializer {
res
}
}
None => err!(format_err(self.ptr)),
None => err!(SerializeError::UnsupportedType(nonnull!(self.ptr))),
}
}
}
44 changes: 13 additions & 31 deletions src/serialize/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ impl<'p> Serialize for Dict {
self.default,
);
if unlikely!(unsafe { ob_type!(key) != STR_TYPE }) {
err!(KEY_MUST_BE_STR)
err!(SerializeError::KeyMustBeStr)
}
{
let data = read_utf8_from_str(key, &mut str_size);
if unlikely!(data.is_null()) {
err!(INVALID_STR)
err!(SerializeError::InvalidStr)
}
map.serialize_key(str_from_slice!(data, str_size)).unwrap();
}
Expand Down Expand Up @@ -136,11 +136,11 @@ impl<'p> Serialize for DictSortedKey {
)
};
if unlikely!(unsafe { ob_type!(key) != STR_TYPE }) {
err!("Dict key must be str")
err!(SerializeError::KeyMustBeStr)
}
let data = read_utf8_from_str(key, &mut str_size);
if unlikely!(data.is_null()) {
err!(INVALID_STR)
err!(SerializeError::InvalidStr)
}
items.push((str_from_slice!(data, str_size), value));
}
Expand All @@ -164,14 +164,6 @@ impl<'p> Serialize for DictSortedKey {
}
}

enum NonStrError {
DatetimeLibraryUnsupported,
IntegerRange,
InvalidStr,
TimeTzinfo,
UnsupportedType,
}

pub struct DictNonStrKey {
ptr: *mut pyo3::ffi::PyObject,
opts: Opt,
Expand Down Expand Up @@ -201,7 +193,7 @@ impl DictNonStrKey {
&self,
key: *mut pyo3::ffi::PyObject,
opts: crate::opt::Opt,
) -> Result<InlinableString, NonStrError> {
) -> Result<InlinableString, SerializeError> {
match pyobject_to_obtype(key, opts) {
ObType::None => Ok(InlinableString::from("null")),
ObType::Bool => {
Expand All @@ -219,7 +211,7 @@ impl DictNonStrKey {
ffi!(PyErr_Clear());
let uval = ffi!(PyLong_AsUnsignedLongLong(key));
if unlikely!(uval == u64::MAX && !ffi!(PyErr_Occurred()).is_null()) {
return Err(NonStrError::IntegerRange);
return Err(SerializeError::DictIntegerKey64Bit);
}
Ok(InlinableString::from(itoa::Buffer::new().format(uval)))
} else {
Expand All @@ -238,7 +230,7 @@ impl DictNonStrKey {
let mut buf = DateTimeBuffer::new();
let dt = DateTime::new(key, opts);
if dt.write_buf(&mut buf, opts).is_err() {
return Err(NonStrError::DatetimeLibraryUnsupported);
return Err(SerializeError::DatetimeLibraryUnsupported);
}
let key_as_str = str_from_slice!(buf.as_ptr(), buf.len());
Ok(InlinableString::from(key_as_str))
Expand All @@ -256,7 +248,7 @@ impl DictNonStrKey {
let key_as_str = str_from_slice!(buf.as_ptr(), buf.len());
Ok(InlinableString::from(key_as_str))
}
Err(TimeError::HasTimezone) => Err(NonStrError::TimeTzinfo),
Err(TimeError::HasTimezone) => Err(SerializeError::TimeHasTzinfo),
},
ObType::Uuid => {
let mut buf: UUIDBuffer = smallvec::SmallVec::with_capacity(64);
Expand All @@ -274,7 +266,7 @@ impl DictNonStrKey {
let mut str_size: pyo3::ffi::Py_ssize_t = 0;
let uni = read_utf8_from_str(key, &mut str_size);
if unlikely!(uni.is_null()) {
Err(NonStrError::InvalidStr)
Err(SerializeError::InvalidStr)
} else {
Ok(InlinableString::from(str_from_slice!(uni, str_size)))
}
Expand All @@ -283,7 +275,7 @@ impl DictNonStrKey {
let mut str_size: pyo3::ffi::Py_ssize_t = 0;
let uni = ffi!(PyUnicode_AsUTF8AndSize(key, &mut str_size)) as *const u8;
if unlikely!(uni.is_null()) {
Err(NonStrError::InvalidStr)
Err(SerializeError::InvalidStr)
} else {
Ok(InlinableString::from(str_from_slice!(uni, str_size)))
}
Expand All @@ -294,7 +286,7 @@ impl DictNonStrKey {
| ObType::Dict
| ObType::List
| ObType::Dataclass
| ObType::Unknown => Err(NonStrError::UnsupportedType),
| ObType::Unknown => Err(SerializeError::DictKeyInvalidType),
}
}
}
Expand Down Expand Up @@ -326,7 +318,7 @@ impl<'p> Serialize for DictNonStrKey {
if is_type!(ob_type!(key), STR_TYPE) {
let data = read_utf8_from_str(key, &mut str_size);
if unlikely!(data.is_null()) {
err!(INVALID_STR)
err!(SerializeError::InvalidStr)
}
items.push((
InlinableString::from(str_from_slice!(data, str_size)),
Expand All @@ -335,17 +327,7 @@ impl<'p> Serialize for DictNonStrKey {
} else {
match self.pyobject_to_string(key, opts) {
Ok(key_as_str) => items.push((key_as_str, value)),
Err(NonStrError::TimeTzinfo) => err!(TIME_HAS_TZINFO),
Err(NonStrError::IntegerRange) => {
err!("Dict integer key must be within 64-bit range")
}
Err(NonStrError::DatetimeLibraryUnsupported) => {
err!(DATETIME_LIBRARY_UNSUPPORTED)
}
Err(NonStrError::InvalidStr) => err!(INVALID_STR),
Err(NonStrError::UnsupportedType) => {
err!("Dict key must a type serializable with OPT_NON_STR_KEYS")
}
Err(err) => err!(err),
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/serialize/int.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

use crate::exc::*;
use crate::opt::*;
use serde::ser::{Serialize, Serializer};

Expand Down Expand Up @@ -34,7 +35,7 @@ impl<'p> Serialize for IntSerializer {
} else if unlikely!(
self.opts & STRICT_INTEGER != 0 && (val > STRICT_INT_MAX || val < STRICT_INT_MIN)
) {
err!("Integer exceeds 53-bit range")
err!(SerializeError::Integer53Bits)
}
serializer.serialize_i64(val)
}
Expand All @@ -60,7 +61,7 @@ impl<'p> Serialize for UIntSerializer {
ffi!(PyErr_Clear());
let val = ffi!(PyLong_AsUnsignedLongLong(self.ptr));
if unlikely!(val == u64::MAX && !ffi!(PyErr_Occurred()).is_null()) {
err!("Integer exceeds 64-bit range")
err!(SerializeError::Integer64Bits)
}
serializer.serialize_u64(val)
}
Expand Down
14 changes: 7 additions & 7 deletions src/serialize/serializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,12 @@ impl<'p> Serialize for PyObjectSerializer {
ObType::Date => Date::new(self.ptr).serialize(serializer),
ObType::Time => match Time::new(self.ptr, self.opts) {
Ok(val) => val.serialize(serializer),
Err(TimeError::HasTimezone) => err!(TIME_HAS_TZINFO),
Err(TimeError::HasTimezone) => err!(SerializeError::TimeHasTzinfo),
},
ObType::Uuid => UUID::new(self.ptr).serialize(serializer),
ObType::Dict => {
if unlikely!(self.recursion == RECURSION_LIMIT) {
err!(RECURSION_LIMIT_REACHED)
err!(SerializeError::RecursionLimit)
}
if unlikely!(unsafe { PyDict_GET_SIZE(self.ptr) } == 0) {
serializer.serialize_map(Some(0)).unwrap().end()
Expand Down Expand Up @@ -229,7 +229,7 @@ impl<'p> Serialize for PyObjectSerializer {
}
ObType::List => {
if unlikely!(self.recursion == RECURSION_LIMIT) {
err!(RECURSION_LIMIT_REACHED)
err!(SerializeError::RecursionLimit)
}
if unlikely!(ffi!(PyList_GET_SIZE(self.ptr)) == 0) {
serializer.serialize_seq(Some(0)).unwrap().end()
Expand All @@ -254,7 +254,7 @@ impl<'p> Serialize for PyObjectSerializer {
.serialize(serializer),
ObType::Dataclass => {
if unlikely!(self.recursion == RECURSION_LIMIT) {
err!(RECURSION_LIMIT_REACHED)
err!(SerializeError::RecursionLimit)
}
let dict = ffi!(PyObject_GetAttr(self.ptr, DICT_STR));
let ob_type = ob_type!(self.ptr);
Expand Down Expand Up @@ -296,7 +296,7 @@ impl<'p> Serialize for PyObjectSerializer {
}
ObType::NumpyArray => match NumpyArray::new(self.ptr, self.opts) {
Ok(val) => val.serialize(serializer),
Err(PyArrayError::Malformed) => err!("numpy array is malformed"),
Err(PyArrayError::Malformed) => err!(SerializeError::NumpyMalformed),
Err(PyArrayError::NotContiguous) | Err(PyArrayError::UnsupportedDataType)
if self.default.is_some() =>
{
Expand All @@ -310,10 +310,10 @@ impl<'p> Serialize for PyObjectSerializer {
.serialize(serializer)
}
Err(PyArrayError::NotContiguous) => {
err!("numpy array is not C contiguous; use ndarray.tolist() in default")
err!(SerializeError::NumpyNotCContiguous)
}
Err(PyArrayError::UnsupportedDataType) => {
err!("unsupported datatype in numpy array")
err!(SerializeError::NumpyUnsupportedDatatype)
}
},
ObType::NumpyScalar => NumpyScalar::new(self.ptr, self.opts).serialize(serializer),
Expand Down
4 changes: 2 additions & 2 deletions src/serialize/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl<'p> Serialize for StrSerializer {
let mut str_size: pyo3::ffi::Py_ssize_t = 0;
let uni = read_utf8_from_str(self.ptr, &mut str_size);
if unlikely!(uni.is_null()) {
err!(INVALID_STR)
err!(SerializeError::InvalidStr)
}
serializer.serialize_str(str_from_slice!(uni, str_size))
}
Expand All @@ -50,7 +50,7 @@ impl<'p> Serialize for StrSubclassSerializer {
let mut str_size: pyo3::ffi::Py_ssize_t = 0;
let uni = ffi!(PyUnicode_AsUTF8AndSize(self.ptr, &mut str_size)) as *const u8;
if unlikely!(uni.is_null()) {
err!(INVALID_STR)
err!(SerializeError::InvalidStr)
}
serializer.serialize_str(str_from_slice!(uni, str_size))
}
Expand Down

0 comments on commit 67d5a3c

Please sign in to comment.