use std::convert::Infallible; use avril_core::serde_v8::U16String; use avril_core::{Extension, Op, PublicModule, v8}; use serde_fjson::syntax::ValueKind; use serde_fjson::{Document, PathSegment}; enum ErrorClass { Syntax, Type, Range, } fn fail(scope: &mut v8::PinScope<'_, '_>, class: ErrorClass, message: &str) -> Option { let message = v8::String::new(scope, message)?; let exception = match class { ErrorClass::Syntax => v8::Exception::syntax_error(scope, message), ErrorClass::Type => v8::Exception::type_error(scope, message), ErrorClass::Range => v8::Exception::range_error(scope, message), }; scope.throw_exception(exception); None } struct Overrides<'s> { document: Document, path: Vec, ancestors: Vec>, } impl<'s> Overrides<'s> { fn merge( &mut self, scope: &mut v8::PinScope<'s, '_>, value: v8::Local<'s, v8::Value>, ) -> Option<()> { if !value.is_object() || value.is_array() || value.is_function() { return fail(scope, ErrorClass::Type, "fJSON overrides must be objects"); } if self .ancestors .iter() .any(|object| object.strict_equals(value)) { return fail(scope, ErrorClass::Type, "Cannot apply cyclic overrides"); } if self.path.len() > 128 { return fail( scope, ErrorClass::Range, "fJSON nesting exceeds 128 levels below the root", ); } let object = v8::Local::::try_from(value).ok()?; self.ancestors.push(object); let keys = object.get_own_property_names( scope, v8::GetPropertyNamesArgs { key_conversion: v8::KeyConversionMode::ConvertToString, ..Default::default() }, )?; for index in 0..keys.length() { let key = keys.get_index(scope, index)?; let value = object.get(scope, key)?; let units = avril_core::serde_v8::from_v8::(scope, key).ok()?; let key = match String::from_utf16(&units) { Ok(key) => key, Err(_) => return fail(scope, ErrorClass::Type, "Unpaired Unicode surrogate"), }; self.path.push(key); let path: Vec<_> = self.path.iter().map(|key| PathSegment::Key(key)).collect(); let merge = value.is_object() && !value.is_array() && !value.is_function(); let result = if value.is_undefined() { self.document.remove(&path).map(|_| ()) } else if merge { let existing = self .path .iter() .try_fold(self.document.root(), |value, key| { let ValueKind::Object(members) = &value.kind else { return None; }; members .iter() .find(|member| &member.key == key) .map(|member| &member.value) }); if existing.is_some_and(|value| matches!(value.kind, ValueKind::Object(_))) { Ok(()) } else { self.document.set(&path, &serde_json::json!({})) } } else { let json = v8::json::stringify(scope, value)?.to_rust_string_lossy(scope); // V8's JSON API returns the text "undefined" for unsupported roots. if json == "undefined" { Ok(()) } else { serde_fjson::parse(&json).and_then(|value| self.document.set(&path, &value)) } }; if let Err(error) = result { let class = if error.kind == serde_fjson::ErrorKind::Nesting { ErrorClass::Range } else { ErrorClass::Type }; return fail(scope, class, &error.message); } if merge { self.merge(scope, value)?; } self.path.pop(); } self.ancestors.pop(); Some(()) } } fn decode_source(source: &U16String) -> Result { let mut text = String::new(); for character in char::decode_utf16(source.iter().copied()) { match character { Ok(character) => text.push(character), Err(_) => { let error = serde_fjson::Error { kind: serde_fjson::ErrorKind::Unicode, message: "Unpaired Unicode surrogate".into(), span: text.len()..text.len(), previous: None, }; return Err(error.render("", &text)); } } } Ok(text) } pub fn init() -> Extension { Extension { name: "ext:fjson", dependencies: &[], ops: vec![ Op::new("op_fjson_parse", |source: U16String| { let text = match decode_source(&source) { Ok(text) => text, Err(error) => return Ok::<_, Infallible>((None, Some(error))), }; let result = match serde_fjson::parse(&text) { Ok(value) => (Some(serde_json::to_string(&value).unwrap()), None), Err(error) => (None, Some(error.render("", &text))), }; Ok::<_, Infallible>(result) }), Op::new("op_fjson_stringify", |source: String| { let result = match serde_fjson::parse(&source) .and_then(|value| serde_fjson::stringify(&value)) { Ok(text) => (Some(text), None, false), Err(error) => ( None, Some(format!("Cannot stringify fJSON: {}", error.message)), error.kind == serde_fjson::ErrorKind::Nesting, ), }; Ok::<_, Infallible>(result) }), Op::raw("op_fjson_apply", |scope, args| { let source = args.get(0); let source = avril_core::serde_v8::from_v8::(scope, source).ok()?; let text = match decode_source(&source) { Ok(text) => text, Err(error) => return fail(scope, ErrorClass::Syntax, &error), }; let document = match Document::parse_object(text).and_then(|document| { document.to_value()?; Ok(document) }) { Ok(document) => document, Err(error) => { // Only failed parses need a second copy for source diagnostics. let text = decode_source(&source).expect("source was already validated"); return fail(scope, ErrorClass::Syntax, &error.render("", &text)); } }; let values = v8::Local::::try_from(args.get(1)).ok()?; let mut overrides = Overrides { document, path: Vec::new(), ancestors: Vec::new(), }; for index in 0..values.length() { let value = values.get_index(scope, index)?; overrides.merge(scope, value)?; } v8::String::new(scope, &overrides.document.into_source()).map(Into::into) }), ], modules: const { &[PUBLIC_MODULES[0].module] }, entry_point: Some("ext:fjson/main.js"), } } pub const PUBLIC_MODULES: &[PublicModule] = &[avril_core::public_module!( "avril:fjson", "ext:fjson", "main" )];