use std::sync::Arc;
use monty_types::{CodeLoc, ExcData, JsonErrorData, MontyException, StackFrame, UnicodeErrorData, UnicodeErrorObject};
use crate::{convert::ProtoConvertError, pb};
impl From<&MontyException> for pb::RaisedException {
fn from(exc: &MontyException) -> Self {
Self {
exc_type: exc.exc_type().to_string(),
message: exc.message().map(ToOwned::to_owned),
traceback: exc.traceback().iter().map(pb::StackFrame::from).collect(),
data: pb_exc_data(exc.data()),
}
}
}
impl TryFrom<pb::RaisedException> for MontyException {
type Error = ProtoConvertError;
fn try_from(err: pb::RaisedException) -> Result<Self, ProtoConvertError> {
let exc_type = err
.exc_type
.parse()
.map_err(|_| ProtoConvertError::UnknownExcType(err.exc_type))?;
let traceback = err
.traceback
.into_iter()
.map(StackFrame::try_from)
.collect::<Result<Vec<_>, _>>()?;
let data = match err.data.and_then(|data| data.kind) {
Some(pb::exc_data::Kind::Unicode(unicode)) => {
sanitize_unicode_data(unicode).map_or(ExcData::None, ExcData::Unicode)
}
Some(pb::exc_data::Kind::Json(json)) => sanitize_json_data(json).map_or(ExcData::None, ExcData::Json),
None => ExcData::None,
};
Ok(Self::with_traceback(exc_type, err.message, traceback).with_data(data))
}
}
fn pb_exc_data(data: &ExcData) -> Option<pb::ExcData> {
match data {
ExcData::None => None,
ExcData::Unicode(unicode) => Some(pb::ExcData {
kind: Some(pb::exc_data::Kind::Unicode(pb::UnicodeErrorData::from(
unicode.as_ref(),
))),
}),
ExcData::Json(json) => Some(pb::ExcData {
kind: Some(pb::exc_data::Kind::Json(pb::JsonErrorData::from(json.as_ref()))),
}),
}
}
impl From<&JsonErrorData> for pb::JsonErrorData {
fn from(data: &JsonErrorData) -> Self {
Self {
msg: data.msg.clone(),
doc: data.doc.clone(),
pos: data.pos as u64,
lineno: data.lineno as u64,
colno: data.colno as u64,
}
}
}
fn sanitize_json_data(data: pb::JsonErrorData) -> Option<Box<JsonErrorData>> {
if data.msg.len() > JsonErrorData::MAX_DOC_LEN {
return None;
}
if data
.doc
.as_ref()
.is_some_and(|doc| doc.len() > JsonErrorData::MAX_DOC_LEN)
{
return None;
}
let pos = usize::try_from(data.pos).ok()?;
let lineno = usize::try_from(data.lineno).ok()?;
let colno = usize::try_from(data.colno).ok()?;
if lineno == 0 || colno == 0 {
return None;
}
if let Some(doc) = &data.doc
&& pos > doc.chars().count()
{
return None;
}
Some(Box::new(JsonErrorData {
msg: data.msg,
doc: data.doc,
pos,
lineno,
colno,
}))
}
impl From<&UnicodeErrorData> for pb::UnicodeErrorData {
fn from(data: &UnicodeErrorData) -> Self {
let object = match &data.object {
UnicodeErrorObject::Bytes(bytes) => pb::unicode_error_data::Object::ObjectBytes(bytes.clone()),
UnicodeErrorObject::Str(s) => pb::unicode_error_data::Object::ObjectStr(s.clone()),
};
Self {
encoding: data.encoding.clone(),
object: Some(object),
start: data.start as u64,
end: data.end as u64,
reason: data.reason.clone(),
}
}
}
fn sanitize_unicode_data(data: pb::UnicodeErrorData) -> Option<Box<UnicodeErrorData>> {
if data.encoding.len() > UnicodeErrorData::MAX_OBJECT_LEN || data.reason.len() > UnicodeErrorData::MAX_OBJECT_LEN {
return None;
}
let (object, object_len) = match data.object? {
pb::unicode_error_data::Object::ObjectBytes(bytes) => {
if bytes.len() > UnicodeErrorData::MAX_OBJECT_LEN {
return None;
}
let len = bytes.len();
(UnicodeErrorObject::Bytes(bytes), len)
}
pb::unicode_error_data::Object::ObjectStr(s) => {
if s.len() > UnicodeErrorData::MAX_OBJECT_LEN {
return None;
}
let len = s.chars().count();
(UnicodeErrorObject::Str(s), len)
}
};
let start = usize::try_from(data.start).ok()?;
let end = usize::try_from(data.end).ok()?;
if start >= end || end > object_len {
return None;
}
Some(Box::new(UnicodeErrorData {
encoding: data.encoding,
object,
start,
end,
reason: data.reason,
}))
}
impl From<&StackFrame> for pb::StackFrame {
fn from(frame: &StackFrame) -> Self {
Self {
filename: frame.filename.clone(),
start: Some(frame.start.into()),
end: Some(frame.end.into()),
frame_name: frame.frame_name.clone(),
preview_line: frame.preview_line.as_ref().map(ToString::to_string),
hide_caret: frame.hide_caret,
hide_frame_name: frame.hide_frame_name,
}
}
}
impl TryFrom<pb::StackFrame> for StackFrame {
type Error = ProtoConvertError;
fn try_from(frame: pb::StackFrame) -> Result<Self, ProtoConvertError> {
let start = CodeLoc::from(frame.start.ok_or(ProtoConvertError::MissingField("StackFrame.start"))?);
let end = CodeLoc::from(frame.end.ok_or(ProtoConvertError::MissingField("StackFrame.end"))?);
if let Some(preview) = &frame.preview_line {
if end.column < start.column {
return Err(ProtoConvertError::InvalidValue {
field: "StackFrame.end.column",
reason: format!("{} is before start column {}", end.column, start.column),
});
}
let line_chars = u32::try_from(preview.chars().count()).unwrap_or(u32::MAX);
if end.column > line_chars.saturating_add(2) {
return Err(ProtoConvertError::InvalidValue {
field: "StackFrame.end.column",
reason: format!("{} is beyond the {line_chars}-character preview line", end.column),
});
}
}
Ok(Self {
filename: frame.filename,
start,
end,
frame_name: frame.frame_name,
preview_line: frame.preview_line.map(Arc::from),
hide_caret: frame.hide_caret,
hide_frame_name: frame.hide_frame_name,
})
}
}
impl From<CodeLoc> for pb::CodeLoc {
fn from(loc: CodeLoc) -> Self {
Self {
line: loc.line,
column: loc.column,
}
}
}
impl From<pb::CodeLoc> for CodeLoc {
fn from(loc: pb::CodeLoc) -> Self {
Self {
line: loc.line,
column: loc.column,
}
}
}