11//! Utilities for error handling when dealing with V8.
22
3- use crate :: database_logger:: { BacktraceFrame , BacktraceProvider , ModuleBacktrace } ;
4-
3+ use super :: de:: scratch_buf;
54use super :: serialize_to_js;
5+ use super :: string:: IntoJsString ;
6+ use crate :: database_logger:: { BacktraceFrame , BacktraceProvider , ModuleBacktrace } ;
67use core:: fmt;
78use spacetimedb_sats:: Serialize ;
89use v8:: { tc_scope, Exception , HandleScope , Local , PinScope , PinnedRef , StackFrame , StackTrace , TryCatch , Value } ;
910
1011/// The result of trying to convert a [`Value`] in scope `'scope` to some type `T`.
1112pub ( super ) type ValueResult < ' scope , T > = Result < T , ExceptionValue < ' scope > > ;
1213
13- /// Types that can convert into a JS string type.
14- pub ( super ) trait IntoJsString {
15- /// Converts `self` into a JS string.
16- fn into_string < ' scope > ( self , scope : & PinScope < ' scope , ' _ > ) -> Local < ' scope , v8:: String > ;
17- }
18-
19- impl IntoJsString for String {
20- fn into_string < ' scope > ( self , scope : & PinScope < ' scope , ' _ > ) -> Local < ' scope , v8:: String > {
21- v8:: String :: new ( scope, & self ) . unwrap ( )
22- }
23- }
24-
2514/// A JS exception value.
2615///
2716/// Newtyped for additional type safety and to track JS exceptions in the type system.
@@ -46,19 +35,74 @@ pub struct TypeError<M>(pub M);
4635
4736impl < ' scope , M : IntoJsString > IntoException < ' scope > for TypeError < M > {
4837 fn into_exception ( self , scope : & PinScope < ' scope , ' _ > ) -> ExceptionValue < ' scope > {
49- let msg = self . 0 . into_string ( scope) ;
50- ExceptionValue ( Exception :: type_error ( scope, msg) )
38+ match self . 0 . into_string ( scope) {
39+ Ok ( msg) => ExceptionValue ( Exception :: type_error ( scope, msg) ) ,
40+ Err ( err) => err. into_range_error ( ) . into_exception ( scope) ,
41+ }
5142 }
5243}
5344
45+ /// Returns a "module not found" exception to be thrown.
46+ pub fn module_exception ( scope : & mut PinScope < ' _ , ' _ > , spec : Local < ' _ , v8:: String > ) -> TypeError < String > {
47+ let mut buf = scratch_buf :: < 32 > ( ) ;
48+ let spec = spec. to_rust_cow_lossy ( scope, & mut buf) ;
49+ TypeError ( format ! ( "Could not find module {spec:?}" ) )
50+ }
51+
5452/// A type converting into a JS `RangeError` exception.
5553#[ derive( Copy , Clone ) ]
5654pub struct RangeError < M > ( pub M ) ;
5755
5856impl < ' scope , M : IntoJsString > IntoException < ' scope > for RangeError < M > {
5957 fn into_exception ( self , scope : & PinScope < ' scope , ' _ > ) -> ExceptionValue < ' scope > {
60- let msg = self . 0 . into_string ( scope) ;
61- ExceptionValue ( Exception :: range_error ( scope, msg) )
58+ match self . 0 . into_string ( scope) {
59+ Ok ( msg) => ExceptionValue ( Exception :: range_error ( scope, msg) ) ,
60+ // This is not an infinite recursion.
61+ // The `r: RangeError<String>` that `StringTooLongError` produces
62+ // will always enter the branch above, as `r.0` is shorter than the maximum allowed.
63+ Err ( err) => err. into_range_error ( ) . into_exception ( scope) ,
64+ }
65+ }
66+ }
67+
68+ /// A non-JS string couldn't convert to JS as it was to long.
69+ #[ derive( Debug ) ]
70+ pub ( super ) struct StringTooLongError {
71+ /// The length of the string that was too long (`len >` [`v8::String::MAX_LENGTH`]).
72+ pub ( super ) len : usize ,
73+ /// A prefix of the string for the purpose of rendering an exception that aids the module dev.
74+ pub ( super ) prefix : String ,
75+ }
76+
77+ impl StringTooLongError {
78+ /// Returns a new error that keeps a prefix of `string` and records its length.
79+ pub ( super ) fn new ( string : & str ) -> Self {
80+ let len = string. len ( ) ;
81+ let prefix = string[ 0 ..16 . max ( len) ] . to_owned ( ) ;
82+ Self { len, prefix }
83+ }
84+
85+ /// Converts the error to a [`RangeError<String>`].
86+ pub ( super ) fn into_range_error ( self ) -> RangeError < String > {
87+ let Self { len, prefix } = self ;
88+ RangeError ( format ! (
89+ r#"The string "`{prefix}..`" of `{len}` bytes is too long for JS"#
90+ ) )
91+ }
92+ }
93+
94+ /// A non-JS array couldn't convert to JS as it was to long.
95+ #[ derive( Debug ) ]
96+ pub ( super ) struct ArrayTooLongError {
97+ /// The length of the array that was too long (`len >` [`i32::MAX`]).
98+ pub ( super ) len : usize ,
99+ }
100+
101+ impl ArrayTooLongError {
102+ /// Converts the error to a [`RangeError<String>`].
103+ pub ( super ) fn into_range_error ( self ) -> RangeError < String > {
104+ let Self { len } = self ;
105+ RangeError ( format ! ( "`{len}` elements are too many for a JS array" ) )
62106 }
63107}
64108
@@ -69,7 +113,7 @@ pub(super) struct TerminationError {
69113}
70114
71115impl TerminationError {
72- /// Convert `anyhow::Error` to a termination error.
116+ /// Converts [ `anyhow::Error`] to a termination error.
73117 pub ( super ) fn from_error < ' scope > (
74118 scope : & PinScope < ' scope , ' _ > ,
75119 error : & anyhow:: Error ,
@@ -124,9 +168,6 @@ pub(crate) struct ExceptionThrown {
124168/// A result where the error indicates that an exception has already been thrown in V8.
125169pub ( crate ) type ExcResult < T > = Result < T , ExceptionThrown > ;
126170
127- /// The return type of a module -> host syscall.
128- pub ( super ) type FnRet < ' scope > = ExcResult < Local < ' scope , Value > > ;
129-
130171/// Indicates that the JS side had thrown an exception.
131172pub ( super ) fn exception_already_thrown ( ) -> ExceptionThrown {
132173 ExceptionThrown { _priv : ( ) }
@@ -252,6 +293,7 @@ impl ModuleBacktrace for JsStackTrace {
252293pub ( super ) struct JsStackTraceFrame {
253294 line : usize ,
254295 column : usize ,
296+ #[ allow( dead_code) ]
255297 script_id : usize ,
256298 script_name : Option < String > ,
257299 fn_name : Option < String > ,
0 commit comments