use ariadne::clew::{self, DictEntry, Error, Value}; use serde_json::Number; fn document_with(roots: &str) -> String { format!( r#"{{ "schema":"clew-renpy-ast-v1", "source":"/tmp/game/script.rpyc", "game_root":"/tmp/game", "slot":2, "python_version":"2.7.18", "renpy_version":"8.4.0", "metadata":null, "roots":{roots} }}"# ) } #[test] fn parses_minimal_document() { let document = clew::from_bytes(document_with("null").as_bytes()).unwrap(); assert_eq!(document.schema, "clew-renpy-ast-v1"); assert_eq!(document.slot, 2); assert_eq!(document.roots, Value::Null); } #[test] fn rejects_wrong_schema() { let json = br#"{ "schema":"other", "source":"/tmp/game/script.rpyc", "game_root":"/tmp/game", "slot":2, "python_version":"2.7.18", "renpy_version":null, "metadata":null, "roots":null }"#; assert!(matches!( clew::from_bytes(json).unwrap_err(), Error::SchemaMismatch { expected: "clew-renpy-ast-v1", actual } if actual == "other" )); } #[test] fn parses_primitive_values() { let document = clew::from_bytes( document_with(r#"{"type":"list","items":[null,true,7,1.5,"text"]}"#).as_bytes(), ) .unwrap(); assert_eq!( document.roots, Value::List(vec![ Value::Null, Value::Bool(true), Value::Number(Number::from(7)), Value::Number(Number::from_f64(1.5).unwrap()), Value::String("text".to_string()), ]) ); } #[test] fn parses_tuple_and_dict_values() { let document = clew::from_bytes( document_with(r#"{"type":"dict","items":[{"key":"names","value":{"type":"tuple","items":["a","b"]}}]}"#) .as_bytes(), ) .unwrap(); assert_eq!( document.roots, Value::Dict(vec![DictEntry { key: Value::String("names".to_string()), value: Value::Tuple(vec![ Value::String("a".to_string()), Value::String("b".to_string()) ]), }]) ); } #[test] fn parses_object_and_reference_values() { let document = clew::from_bytes( document_with( r#"{"type":"list","items":[{"type":"object","id":1,"class":"renpy.ast.Label","attributes":{"name":"start","next":{"type":"ref","id":1}}},{"type":"ref","id":1}]}"#, ) .as_bytes(), ) .unwrap(); let Value::List(items) = document.roots else { panic!("roots should be a list"); }; assert_eq!(items.len(), 2); assert_eq!(items[1], Value::Ref { id: 1 }); let Value::Object(object) = &items[0] else { panic!("first item should be an object"); }; assert_eq!(object.id, 1); assert_eq!(object.class_name, "renpy.ast.Label"); assert_eq!( object.attributes["name"], Value::String("start".to_string()) ); } #[test] fn parses_special_tag_values() { let document = clew::from_bytes( document_with( r#"{"type":"list","items":[{"type":"function","name":"f","module":"m","filename":"file.py","firstlineno":9},{"type":"method","name":"g","module":"m","receiver_class":"C"},{"type":"python-type","name":"Label","module":"renpy.ast"},{"type":"repr","class":"C","repr":""},{"type":"bytes","repr":"b'abc'"},{"type":"truncated","class":"C","repr":""},{"type":"recursion","class":"list"},{"type":"attribute-error","class":"Exception","repr":"Exception('x')"}]}"#, ) .as_bytes(), ) .unwrap(); let Value::List(items) = document.roots else { panic!("roots should be a list"); }; assert_eq!(items.len(), 8); assert!(matches!(items[0], Value::Function(_))); assert!(matches!(items[1], Value::Method(_))); assert!(matches!(items[2], Value::PythonType(_))); assert!(matches!(items[3], Value::Repr { .. })); assert!(matches!(items[4], Value::Bytes { .. })); assert!(matches!(items[5], Value::Truncated { .. })); assert!(matches!(items[6], Value::Recursion { .. })); assert!(matches!(items[7], Value::AttributeError { .. })); } #[test] fn rejects_unknown_tags() { let error = clew::from_bytes(document_with(r#"{"type":"future"}"#).as_bytes()).unwrap_err(); match error { Error::Json(error) => assert!( error .to_string() .contains("unknown Clew value type: future") ), Error::SchemaMismatch { .. } => panic!("unknown tags should be JSON shape errors"), } }