|
1 | 1 | use std::collections::HashMap; |
2 | 2 |
|
| 3 | +use vm::compiler::TypeSchema; |
3 | 4 | use vm::{ |
4 | 5 | ArgInfo, Assembler, BuiltinFunction, BytecodeBuilder, CallableKind, CallablePrototype, |
5 | 6 | CallableTarget, DebugFunction, DebugInfo, DisassembleOptions, HostImport, LineInfo, LocalInfo, |
6 | 7 | Program, ScriptFunction, TypeMap, ValidationError, Value, ValueType, WireError, |
7 | 8 | builtin_call_index, decode_program, disassemble_vmbc, disassemble_vmbc_with_options, |
8 | | - encode_program, infer_local_count, validate_program, |
| 9 | + encode_program, infer_local_count, validate_program, HostApiCatalog, ResourceTypeKey, |
9 | 10 | }; |
10 | 11 |
|
11 | 12 | #[test] |
@@ -787,3 +788,46 @@ fn call_script_no_script_program_code_bytes_unchanged_by_version_bump() { |
787 | 788 | assert_eq!(decoded.code, program.code); |
788 | 789 | assert_eq!(decoded.constants, program.constants); |
789 | 790 | } |
| 791 | + |
| 792 | +#[test] |
| 793 | +fn schema_round_trip_resource_wire_nominally() { |
| 794 | + // A nominal host resource must round-trip through the shared wire |
| 795 | + // type-schema encoding (tag 17), preserving its identity as a resource |
| 796 | + // rather than being flattened into a structural type. |
| 797 | + let key = vm::ResourceTypeKey::new("io.file").expect("valid key"); |
| 798 | + let schema = vm::compiler::TypeSchema::Resource(key.clone()); |
| 799 | + let program = vm::Program::new(Vec::new(), Vec::new()).with_type_map(vm::TypeMap { |
| 800 | + strict_types: false, |
| 801 | + local_types: vec![vm::ValueType::Int], |
| 802 | + local_schemas: vec![Some(schema.clone())], |
| 803 | + callable_slots: vec![false], |
| 804 | + optional_slots: vec![false], |
| 805 | + operand_types: HashMap::new(), |
| 806 | + }); |
| 807 | + |
| 808 | + let encoded = vm::encode_program(&program).expect("encode should succeed"); |
| 809 | + let decoded = vm::decode_program(&encoded).expect("decode should succeed"); |
| 810 | + assert_eq!(decoded.type_map, program.type_map); |
| 811 | + assert_eq!( |
| 812 | + decoded.type_map.as_ref().and_then(|tm| tm.local_schemas[0].as_ref()), |
| 813 | + Some(&TypeSchema::Resource(key)) |
| 814 | + ); |
| 815 | +} |
| 816 | + |
| 817 | +#[test] |
| 818 | +fn malformed_resource_key_is_rejected_on_read() { |
| 819 | + // A resource key that violates the resource-key grammar must be rejected by the |
| 820 | + // key's own Deserialize impl. |
| 821 | + assert!(ResourceTypeKey::new("has space").is_err()); |
| 822 | + assert!(ResourceTypeKey::new("").is_err()); |
| 823 | + assert!(ResourceTypeKey::new(".leading").is_err()); |
| 824 | + |
| 825 | + // ... and a malformed key hiding inside a catalog's resources must also fail |
| 826 | + // catalog deserialization (serde runs the same validation). |
| 827 | + let mut v = serde_json::json!({ |
| 828 | + "resources": [{ "key": "io.file", "description": "file" }], |
| 829 | + "functions": [] |
| 830 | + }); |
| 831 | + v["resources"][0]["key"] = serde_json::json!("bad key"); |
| 832 | + assert!(serde_json::from_value::<HostApiCatalog>(v).is_err()); |
| 833 | +} |
0 commit comments