char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somdeslop: consolidate public type and registry test utils7325e8c

main
7.9 KiB209 linesraw
1use std::convert::Infallible;
2
3use avril_core::serde_v8::U16String;
4use avril_core::{Extension, Op, PublicModule, v8};
5use serde_fjson::syntax::ValueKind;
6use serde_fjson::{Document, PathSegment};
7
8enum ErrorClass {
9    Syntax,
10    Type,
11    Range,
12}
13
14fn fail<T>(scope: &mut v8::PinScope<'_, '_>, class: ErrorClass, message: &str) -> Option<T> {
15    let message = v8::String::new(scope, message)?;
16    let exception = match class {
17        ErrorClass::Syntax => v8::Exception::syntax_error(scope, message),
18        ErrorClass::Type => v8::Exception::type_error(scope, message),
19        ErrorClass::Range => v8::Exception::range_error(scope, message),
20    };
21    scope.throw_exception(exception);
22    None
23}
24
25struct Overrides<'s> {
26    document: Document,
27    path: Vec<String>,
28    ancestors: Vec<v8::Local<'s, v8::Object>>,
29}
30
31impl<'s> Overrides<'s> {
32    fn merge(
33        &mut self,
34        scope: &mut v8::PinScope<'s, '_>,
35        value: v8::Local<'s, v8::Value>,
36    ) -> Option<()> {
37        if !value.is_object() || value.is_array() || value.is_function() {
38            return fail(scope, ErrorClass::Type, "fJSON overrides must be objects");
39        }
40        if self
41            .ancestors
42            .iter()
43            .any(|object| object.strict_equals(value))
44        {
45            return fail(scope, ErrorClass::Type, "Cannot apply cyclic overrides");
46        }
47        if self.path.len() > 128 {
48            return fail(
49                scope,
50                ErrorClass::Range,
51                "fJSON nesting exceeds 128 levels below the root",
52            );
53        }
54        let object = v8::Local::<v8::Object>::try_from(value).ok()?;
55        self.ancestors.push(object);
56        let keys = object.get_own_property_names(
57            scope,
58            v8::GetPropertyNamesArgs {
59                key_conversion: v8::KeyConversionMode::ConvertToString,
60                ..Default::default()
61            },
62        )?;
63        for index in 0..keys.length() {
64            let key = keys.get_index(scope, index)?;
65            let value = object.get(scope, key)?;
66            let units = avril_core::serde_v8::from_v8::<U16String>(scope, key).ok()?;
67            let key = match String::from_utf16(&units) {
68                Ok(key) => key,
69                Err(_) => return fail(scope, ErrorClass::Type, "Unpaired Unicode surrogate"),
70            };
71            self.path.push(key);
72            let path: Vec<_> = self.path.iter().map(|key| PathSegment::Key(key)).collect();
73            let merge = value.is_object() && !value.is_array() && !value.is_function();
74            let result = if value.is_undefined() {
75                self.document.remove(&path).map(|_| ())
76            } else if merge {
77                let existing = self
78                    .path
79                    .iter()
80                    .try_fold(self.document.root(), |value, key| {
81                        let ValueKind::Object(members) = &value.kind else {
82                            return None;
83                        };
84                        members
85                            .iter()
86                            .find(|member| &member.key == key)
87                            .map(|member| &member.value)
88                    });
89                if existing.is_some_and(|value| matches!(value.kind, ValueKind::Object(_))) {
90                    Ok(())
91                } else {
92                    self.document.set(&path, &serde_json::json!({}))
93                }
94            } else {
95                let json = v8::json::stringify(scope, value)?.to_rust_string_lossy(scope);
96                // V8's JSON API returns the text "undefined" for unsupported roots.
97                if json == "undefined" {
98                    Ok(())
99                } else {
100                    serde_fjson::parse(&json).and_then(|value| self.document.set(&path, &value))
101                }
102            };
103            if let Err(error) = result {
104                let class = if error.kind == serde_fjson::ErrorKind::Nesting {
105                    ErrorClass::Range
106                } else {
107                    ErrorClass::Type
108                };
109                return fail(scope, class, &error.message);
110            }
111            if merge {
112                self.merge(scope, value)?;
113            }
114            self.path.pop();
115        }
116        self.ancestors.pop();
117        Some(())
118    }
119}
120
121fn decode_source(source: &U16String) -> Result<String, String> {
122    let mut text = String::new();
123    for character in char::decode_utf16(source.iter().copied()) {
124        match character {
125            Ok(character) => text.push(character),
126            Err(_) => {
127                let error = serde_fjson::Error {
128                    kind: serde_fjson::ErrorKind::Unicode,
129                    message: "Unpaired Unicode surrogate".into(),
130                    span: text.len()..text.len(),
131                    previous: None,
132                };
133                return Err(error.render("<fjson>", &text));
134            }
135        }
136    }
137    Ok(text)
138}
139
140pub fn init() -> Extension {
141    Extension {
142        name: "ext:fjson",
143        dependencies: &[],
144        ops: vec![
145            Op::new("op_fjson_parse", |source: U16String| {
146                let text = match decode_source(&source) {
147                    Ok(text) => text,
148                    Err(error) => return Ok::<_, Infallible>((None, Some(error))),
149                };
150                let result = match serde_fjson::parse(&text) {
151                    Ok(value) => (Some(serde_json::to_string(&value).unwrap()), None),
152                    Err(error) => (None, Some(error.render("<fjson>", &text))),
153                };
154                Ok::<_, Infallible>(result)
155            }),
156            Op::new("op_fjson_stringify", |source: String| {
157                let result = match serde_fjson::parse(&source)
158                    .and_then(|value| serde_fjson::stringify(&value))
159                {
160                    Ok(text) => (Some(text), None, false),
161                    Err(error) => (
162                        None,
163                        Some(format!("Cannot stringify fJSON: {}", error.message)),
164                        error.kind == serde_fjson::ErrorKind::Nesting,
165                    ),
166                };
167                Ok::<_, Infallible>(result)
168            }),
169            Op::raw("op_fjson_apply", |scope, args| {
170                let source = args.get(0);
171                let source = avril_core::serde_v8::from_v8::<U16String>(scope, source).ok()?;
172                let text = match decode_source(&source) {
173                    Ok(text) => text,
174                    Err(error) => return fail(scope, ErrorClass::Syntax, &error),
175                };
176                let document = match Document::parse_object(text).and_then(|document| {
177                    document.to_value()?;
178                    Ok(document)
179                }) {
180                    Ok(document) => document,
181                    Err(error) => {
182                        // Only failed parses need a second copy for source diagnostics.
183                        let text = decode_source(&source).expect("source was already validated");
184                        return fail(scope, ErrorClass::Syntax, &error.render("<fjson>", &text));
185                    }
186                };
187                let values = v8::Local::<v8::Array>::try_from(args.get(1)).ok()?;
188                let mut overrides = Overrides {
189                    document,
190                    path: Vec::new(),
191                    ancestors: Vec::new(),
192                };
193                for index in 0..values.length() {
194                    let value = values.get_index(scope, index)?;
195                    overrides.merge(scope, value)?;
196                }
197                v8::String::new(scope, &overrides.document.into_source()).map(Into::into)
198            }),
199        ],
200        modules: const { &[PUBLIC_MODULES[0].module] },
201        entry_point: Some("ext:fjson/main.js"),
202    }
203}
204
205pub const PUBLIC_MODULES: &[PublicModule] = &[avril_core::public_module!(
206    "avril:fjson",
207    "ext:fjson",
208    "main"
209)];