Implement StructuredData OSD model and containers
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -272,6 +272,7 @@ dependencies = [
|
|||||||
name = "libremetaverse-structured-data"
|
name = "libremetaverse-structured-data"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
"libremetaverse-types",
|
"libremetaverse-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -73,5 +73,12 @@ hierarchical token-bucket throttling are native Rust as well. Cache and bucket
|
|||||||
tests use injected monotonic clocks, and concurrency invariants run without
|
tests use injected monotonic clocks, and concurrency invariants run without
|
||||||
sleep-based timing. All 45 Types and all 942 mapped members now resolve to
|
sleep-based timing. All 45 Types and all 942 mapped members now resolve to
|
||||||
native implementations; no generated Types shims remain.
|
native implementations; no generated Types shims remain.
|
||||||
|
Milestone 05 now has a native, format-neutral OSD value model with structural
|
||||||
|
equality and hashing, permissive `LibreMetaverse` conversion defaults, explicit
|
||||||
|
integer/date byte order, synchronized snapshot-based arrays and maps, and
|
||||||
|
bounded parser dispatch. OSD map order is intentionally unspecified at the
|
||||||
|
public model boundary; encoders must choose and document any stable ordering
|
||||||
|
they require. Untrusted dispatch limits input bytes, nesting depth, decoded
|
||||||
|
nodes, and aggregate binary allocation before returning a value.
|
||||||
The controlled audit aggregates every expected failure by standardized C#
|
The controlled audit aggregates every expected failure by standardized C#
|
||||||
member ID and rejects unrelated fixture, assertion, compile, or symbol errors.
|
member ID and rejects unrelated fixture, assertion, compile, or symbol errors.
|
||||||
|
|||||||
@@ -24778,7 +24778,7 @@ M:LibreMetaverse.StructuredData.OSDArray.Clear method System.Void LibreMetaverse
|
|||||||
M:LibreMetaverse.StructuredData.OSDArray.Contains(LibreMetaverse.StructuredData.OSD) method System.Boolean LibreMetaverse.StructuredData.OSDArray.Contains(LibreMetaverse.StructuredData.OSD llsd) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::contains_with_osd pub fn contains_with_osd(&self, llsd: libremetaverse_structured_data::OSD) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped
|
M:LibreMetaverse.StructuredData.OSDArray.Contains(LibreMetaverse.StructuredData.OSD) method System.Boolean LibreMetaverse.StructuredData.OSDArray.Contains(LibreMetaverse.StructuredData.OSD llsd) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::contains_with_osd pub fn contains_with_osd(&self, llsd: libremetaverse_structured_data::OSD) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped
|
||||||
M:LibreMetaverse.StructuredData.OSDArray.Contains(System.String) method System.Boolean LibreMetaverse.StructuredData.OSDArray.Contains(System.String element) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::contains_with_string pub fn contains_with_string(&self, element: String) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped
|
M:LibreMetaverse.StructuredData.OSDArray.Contains(System.String) method System.Boolean LibreMetaverse.StructuredData.OSDArray.Contains(System.String element) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::contains_with_string pub fn contains_with_string(&self, element: String) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped
|
||||||
M:LibreMetaverse.StructuredData.OSDArray.Copy method LibreMetaverse.StructuredData.OSD LibreMetaverse.StructuredData.OSDArray.Copy() libremetaverse-structured-data libremetaverse_structured_data::OSDArray::copy pub fn copy(&self) -> Result<libremetaverse_structured_data::OSD, crate::Error> shared_self sync Result<_, crate::Error> direct_snake_case method mapped
|
M:LibreMetaverse.StructuredData.OSDArray.Copy method LibreMetaverse.StructuredData.OSD LibreMetaverse.StructuredData.OSDArray.Copy() libremetaverse-structured-data libremetaverse_structured_data::OSDArray::copy pub fn copy(&self) -> Result<libremetaverse_structured_data::OSD, crate::Error> shared_self sync Result<_, crate::Error> direct_snake_case method mapped
|
||||||
M:LibreMetaverse.StructuredData.OSDArray.CopyTo(LibreMetaverse.StructuredData.OSD[],System.Int32) method System.Void LibreMetaverse.StructuredData.OSDArray.CopyTo(LibreMetaverse.StructuredData.OSD[] array, System.Int32 index) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::copy_to pub fn copy_to(&self, array: Vec<libremetaverse_structured_data::OSD>, index: i32) -> Result<(), crate::Error> shared_self,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
M:LibreMetaverse.StructuredData.OSDArray.CopyTo(LibreMetaverse.StructuredData.OSD[],System.Int32) method System.Void LibreMetaverse.StructuredData.OSDArray.CopyTo(LibreMetaverse.StructuredData.OSD[] array, System.Int32 index) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::copy_to pub fn copy_to(&self, array: &mut [libremetaverse_structured_data::OSD], index: i32) -> Result<(), crate::Error> shared_self,mutable_borrow,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
||||||
M:LibreMetaverse.StructuredData.OSDArray.IndexOf(LibreMetaverse.StructuredData.OSD) method System.Int32 LibreMetaverse.StructuredData.OSDArray.IndexOf(LibreMetaverse.StructuredData.OSD item) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::index_of pub fn index_of(&self, item: libremetaverse_structured_data::OSD) -> Result<i32, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
M:LibreMetaverse.StructuredData.OSDArray.IndexOf(LibreMetaverse.StructuredData.OSD) method System.Int32 LibreMetaverse.StructuredData.OSDArray.IndexOf(LibreMetaverse.StructuredData.OSD item) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::index_of pub fn index_of(&self, item: libremetaverse_structured_data::OSD) -> Result<i32, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
||||||
M:LibreMetaverse.StructuredData.OSDArray.Insert(System.Int32,LibreMetaverse.StructuredData.OSD) method System.Void LibreMetaverse.StructuredData.OSDArray.Insert(System.Int32 index, LibreMetaverse.StructuredData.OSD item) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::insert pub fn insert(&self, index: i32, item: libremetaverse_structured_data::OSD) -> Result<(), crate::Error> shared_self,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
M:LibreMetaverse.StructuredData.OSDArray.Insert(System.Int32,LibreMetaverse.StructuredData.OSD) method System.Void LibreMetaverse.StructuredData.OSDArray.Insert(System.Int32 index, LibreMetaverse.StructuredData.OSD item) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::insert pub fn insert(&self, index: i32, item: libremetaverse_structured_data::OSD) -> Result<(), crate::Error> shared_self,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
||||||
M:LibreMetaverse.StructuredData.OSDArray.Remove(LibreMetaverse.StructuredData.OSD) method System.Boolean LibreMetaverse.StructuredData.OSDArray.Remove(LibreMetaverse.StructuredData.OSD llsd) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::remove pub fn remove(&self, llsd: libremetaverse_structured_data::OSD) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
M:LibreMetaverse.StructuredData.OSDArray.Remove(LibreMetaverse.StructuredData.OSD) method System.Boolean LibreMetaverse.StructuredData.OSDArray.Remove(LibreMetaverse.StructuredData.OSD llsd) libremetaverse-structured-data libremetaverse_structured_data::OSDArray::remove pub fn remove(&self, llsd: libremetaverse_structured_data::OSD) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
||||||
@@ -24843,7 +24843,7 @@ M:LibreMetaverse.StructuredData.OSDMap.Clear method System.Void LibreMetaverse.S
|
|||||||
M:LibreMetaverse.StructuredData.OSDMap.Contains(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}) method System.Boolean LibreMetaverse.StructuredData.OSDMap.Contains(System.Collections.Generic.KeyValuePair<System.String, LibreMetaverse.StructuredData.OSD> kvp) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::contains pub fn contains(&self, kvp: (String, libremetaverse_structured_data::OSD)) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
M:LibreMetaverse.StructuredData.OSDMap.Contains(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}) method System.Boolean LibreMetaverse.StructuredData.OSDMap.Contains(System.Collections.Generic.KeyValuePair<System.String, LibreMetaverse.StructuredData.OSD> kvp) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::contains pub fn contains(&self, kvp: (String, libremetaverse_structured_data::OSD)) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
||||||
M:LibreMetaverse.StructuredData.OSDMap.ContainsKey(System.String) method System.Boolean LibreMetaverse.StructuredData.OSDMap.ContainsKey(System.String key) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::contains_key pub fn contains_key(&self, key: String) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
M:LibreMetaverse.StructuredData.OSDMap.ContainsKey(System.String) method System.Boolean LibreMetaverse.StructuredData.OSDMap.ContainsKey(System.String key) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::contains_key pub fn contains_key(&self, key: String) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
||||||
M:LibreMetaverse.StructuredData.OSDMap.Copy method LibreMetaverse.StructuredData.OSD LibreMetaverse.StructuredData.OSDMap.Copy() libremetaverse-structured-data libremetaverse_structured_data::OSDMap::copy pub fn copy(&self) -> Result<libremetaverse_structured_data::OSD, crate::Error> shared_self sync Result<_, crate::Error> direct_snake_case method mapped
|
M:LibreMetaverse.StructuredData.OSDMap.Copy method LibreMetaverse.StructuredData.OSD LibreMetaverse.StructuredData.OSDMap.Copy() libremetaverse-structured-data libremetaverse_structured_data::OSDMap::copy pub fn copy(&self) -> Result<libremetaverse_structured_data::OSD, crate::Error> shared_self sync Result<_, crate::Error> direct_snake_case method mapped
|
||||||
M:LibreMetaverse.StructuredData.OSDMap.CopyTo(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}[],System.Int32) method System.Void LibreMetaverse.StructuredData.OSDMap.CopyTo(System.Collections.Generic.KeyValuePair<System.String, LibreMetaverse.StructuredData.OSD>[] array, System.Int32 index) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::copy_to pub fn copy_to(&self, array: Vec<(String, libremetaverse_structured_data::OSD)>, index: i32) -> Result<(), crate::Error> shared_self,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
M:LibreMetaverse.StructuredData.OSDMap.CopyTo(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}[],System.Int32) method System.Void LibreMetaverse.StructuredData.OSDMap.CopyTo(System.Collections.Generic.KeyValuePair<System.String, LibreMetaverse.StructuredData.OSD>[] array, System.Int32 index) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::copy_to pub fn copy_to(&self, array: &mut [(String, libremetaverse_structured_data::OSD)], index: i32) -> Result<(), crate::Error> shared_self,mutable_borrow,owned sync Result<_, crate::Error> direct_snake_case method mapped
|
||||||
M:LibreMetaverse.StructuredData.OSDMap.GetEnumerator method System.Collections.IDictionaryEnumerator LibreMetaverse.StructuredData.OSDMap.GetEnumerator() libremetaverse-structured-data libremetaverse_structured_data::OSDMap::get_enumerator pub fn get_enumerator(&self) -> Result<libremetaverse_types::compat::IDictionaryEnumerator, crate::Error> shared_self sync Result<_, crate::Error> direct_snake_case method mapped
|
M:LibreMetaverse.StructuredData.OSDMap.GetEnumerator method System.Collections.IDictionaryEnumerator LibreMetaverse.StructuredData.OSDMap.GetEnumerator() libremetaverse-structured-data libremetaverse_structured_data::OSDMap::get_enumerator pub fn get_enumerator(&self) -> Result<libremetaverse_types::compat::IDictionaryEnumerator, crate::Error> shared_self sync Result<_, crate::Error> direct_snake_case method mapped
|
||||||
M:LibreMetaverse.StructuredData.OSDMap.Remove(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}) method System.Boolean LibreMetaverse.StructuredData.OSDMap.Remove(System.Collections.Generic.KeyValuePair<System.String, LibreMetaverse.StructuredData.OSD> kvp) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::remove_with_key_value_pair pub fn remove_with_key_value_pair(&self, kvp: (String, libremetaverse_structured_data::OSD)) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped
|
M:LibreMetaverse.StructuredData.OSDMap.Remove(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}) method System.Boolean LibreMetaverse.StructuredData.OSDMap.Remove(System.Collections.Generic.KeyValuePair<System.String, LibreMetaverse.StructuredData.OSD> kvp) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::remove_with_key_value_pair pub fn remove_with_key_value_pair(&self, kvp: (String, libremetaverse_structured_data::OSD)) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped
|
||||||
M:LibreMetaverse.StructuredData.OSDMap.Remove(System.String) method System.Boolean LibreMetaverse.StructuredData.OSDMap.Remove(System.String key) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::remove_with_string pub fn remove_with_string(&self, key: String) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped
|
M:LibreMetaverse.StructuredData.OSDMap.Remove(System.String) method System.Boolean LibreMetaverse.StructuredData.OSDMap.Remove(System.String key) libremetaverse-structured-data libremetaverse_structured_data::OSDMap::remove_with_string pub fn remove_with_string(&self, key: String) -> Result<bool, crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped
|
||||||
|
|||||||
|
Can't render this file because it is too large.
|
@@ -12,7 +12,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand.
|
|||||||
| `LibreMetaverse.RLV` | 28 | 499 | callable failure-only shim |
|
| `LibreMetaverse.RLV` | 28 | 499 | callable failure-only shim |
|
||||||
| `LibreMetaverse.Rendering.MeshFoundry` | 1 | 14 | callable failure-only shim |
|
| `LibreMetaverse.Rendering.MeshFoundry` | 1 | 14 | callable failure-only shim |
|
||||||
| `LibreMetaverse.Rendering.Simple` | 1 | 6 | callable failure-only shim |
|
| `LibreMetaverse.Rendering.Simple` | 1 | 6 | callable failure-only shim |
|
||||||
| `LibreMetaverse.StructuredData` | 16 | 295 | callable failure-only shim |
|
| `LibreMetaverse.StructuredData` | 16 | 295 | native implementation: 15 types / 255 members; remaining surface is callable failure-only shims |
|
||||||
| `LibreMetaverse.Types` | 45 | 942 | native implementation: 45 types / 942 members; no generated shims remain |
|
| `LibreMetaverse.Types` | 45 | 942 | native implementation: 45 types / 942 members; no generated shims remain |
|
||||||
| `LibreMetaverse.Utilities` | 3 | 13 | callable failure-only shim |
|
| `LibreMetaverse.Utilities` | 3 | 13 | callable failure-only shim |
|
||||||
| `LibreMetaverse.Voice.Vivox` | 64 | 531 | callable failure-only shim |
|
| `LibreMetaverse.Voice.Vivox` | 64 | 531 | callable failure-only shim |
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ repository.workspace = true
|
|||||||
description = "OSD and LLSD shims for the MetaCrate LibreMetaverse rewrite"
|
description = "OSD and LLSD shims for the MetaCrate LibreMetaverse rewrite"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
base64 = "0.22"
|
||||||
libremetaverse-types = { path = "../libremetaverse-types" }
|
libremetaverse-types = { path = "../libremetaverse-types" }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
194
crates/libremetaverse-structured-data/src/dispatch.rs
Normal file
194
crates/libremetaverse-structured-data/src/dispatch.rs
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
//! Bounded wire-format dispatch for the public `OSDParser.Deserialize` overloads.
|
||||||
|
|
||||||
|
use crate::{Error, OSD, OSDParser};
|
||||||
|
use libremetaverse_types::compat::ReadWrite;
|
||||||
|
use std::io::Read as _;
|
||||||
|
|
||||||
|
const PROBE_LENGTH: usize = 20;
|
||||||
|
const MAX_INPUT_BYTES: usize = OSD::DEFAULT_MAX_BINARY_BYTES;
|
||||||
|
const BINARY_HEADER: &str = "<? llsd/binary ?>";
|
||||||
|
const PROTOBUF_HEADER: &str = "<? llsd/protobuf ?>";
|
||||||
|
const XML_HEADERS: [&str; 3] = ["<llsd>", "<?xml", "<? llsd/xml ?>"];
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
enum WireFormat {
|
||||||
|
Binary,
|
||||||
|
Json,
|
||||||
|
Protobuf,
|
||||||
|
Xml,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn deserialize_bytes(data: Vec<u8>) -> Result<OSD, Error> {
|
||||||
|
if data.len() > MAX_INPUT_BYTES {
|
||||||
|
return Err(Error::Argument);
|
||||||
|
}
|
||||||
|
let format = classify_bytes(&data).ok_or(Error::Argument)?;
|
||||||
|
let value = match format {
|
||||||
|
WireFormat::Binary => OSDParser::deserialize_llsd_binary_with_bytes(data)?,
|
||||||
|
WireFormat::Protobuf => OSDParser::deserialize_llsd_protobuf_with_bytes(data)?,
|
||||||
|
WireFormat::Xml => OSDParser::deserialize_llsd_xml_with_bytes(data)?,
|
||||||
|
WireFormat::Json => OSDParser::deserialize_json_with_string(
|
||||||
|
String::from_utf8(data).map_err(|_| Error::Argument)?,
|
||||||
|
)?,
|
||||||
|
};
|
||||||
|
validate_decoded(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn deserialize_string(data: String) -> Result<OSD, Error> {
|
||||||
|
if data.len() > MAX_INPUT_BYTES {
|
||||||
|
return Err(Error::Argument);
|
||||||
|
}
|
||||||
|
let value = match classify_string(&data) {
|
||||||
|
WireFormat::Binary => OSDParser::deserialize_llsd_binary_with_bytes(data.into_bytes())?,
|
||||||
|
WireFormat::Protobuf => OSDParser::deserialize_llsd_protobuf_with_bytes(data.into_bytes())?,
|
||||||
|
WireFormat::Xml => OSDParser::deserialize_llsd_xml_with_string(data)?,
|
||||||
|
WireFormat::Json => OSDParser::deserialize_json_with_string(data)?,
|
||||||
|
};
|
||||||
|
validate_decoded(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn deserialize_stream(mut stream: Box<dyn ReadWrite + Send>) -> Result<OSD, Error> {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
(&mut *stream)
|
||||||
|
.take((MAX_INPUT_BYTES + 1) as u64)
|
||||||
|
.read_to_end(&mut data)
|
||||||
|
.map_err(|_| Error::Argument)?;
|
||||||
|
deserialize_bytes(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_decoded(value: OSD) -> Result<OSD, Error> {
|
||||||
|
value.validate_limits(
|
||||||
|
OSD::DEFAULT_MAX_DEPTH,
|
||||||
|
OSD::DEFAULT_MAX_NODES,
|
||||||
|
OSD::DEFAULT_MAX_BINARY_BYTES,
|
||||||
|
)?;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classify_bytes(data: &[u8]) -> Option<WireFormat> {
|
||||||
|
let probe = &data[..data.len().min(PROBE_LENGTH)];
|
||||||
|
let ascii = String::from_utf8_lossy(probe);
|
||||||
|
let utf8 = ascii.trim_start_matches('\u{feff}').trim_start();
|
||||||
|
if starts_with_any_ignore_ascii_case(utf8, &XML_HEADERS) {
|
||||||
|
return Some(WireFormat::Xml);
|
||||||
|
}
|
||||||
|
if starts_with_ignore_ascii_case(&ascii, PROTOBUF_HEADER) {
|
||||||
|
return Some(WireFormat::Protobuf);
|
||||||
|
}
|
||||||
|
if starts_with_ignore_ascii_case(&ascii, BINARY_HEADER) {
|
||||||
|
return Some(WireFormat::Binary);
|
||||||
|
}
|
||||||
|
if starts_with_any_ignore_ascii_case(&ascii, &XML_HEADERS) {
|
||||||
|
return Some(WireFormat::Xml);
|
||||||
|
}
|
||||||
|
let first = utf8.as_bytes().first().copied()?;
|
||||||
|
if matches!(first, b'{' | b'[' | b'"' | b'-' | b'0'..=b'9')
|
||||||
|
|| utf8.starts_with("true")
|
||||||
|
|| utf8.starts_with("false")
|
||||||
|
|| utf8.starts_with("null")
|
||||||
|
{
|
||||||
|
Some(WireFormat::Json)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classify_string(data: &str) -> WireFormat {
|
||||||
|
if starts_with_ignore_ascii_case(data, PROTOBUF_HEADER) {
|
||||||
|
WireFormat::Protobuf
|
||||||
|
} else if starts_with_ignore_ascii_case(data, BINARY_HEADER) {
|
||||||
|
WireFormat::Binary
|
||||||
|
} else if starts_with_any_ignore_ascii_case(data, &XML_HEADERS) {
|
||||||
|
WireFormat::Xml
|
||||||
|
} else {
|
||||||
|
WireFormat::Json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn starts_with_any_ignore_ascii_case(value: &str, prefixes: &[&str]) -> bool {
|
||||||
|
prefixes
|
||||||
|
.iter()
|
||||||
|
.any(|prefix| starts_with_ignore_ascii_case(value, prefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn starts_with_ignore_ascii_case(value: &str, prefix: &str) -> bool {
|
||||||
|
value
|
||||||
|
.get(..prefix.len())
|
||||||
|
.is_some_and(|candidate| candidate.eq_ignore_ascii_case(prefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn byte_dispatch_recognizes_headers_bom_whitespace_and_json_tokens() {
|
||||||
|
assert_eq!(
|
||||||
|
classify_bytes(b"<? llsd/binary ?>i"),
|
||||||
|
Some(WireFormat::Binary)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_bytes(b"<? LLSD/PROTOBUF ?>"),
|
||||||
|
Some(WireFormat::Protobuf)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_bytes(b" \n<?xml version='1.0'"),
|
||||||
|
Some(WireFormat::Xml)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
classify_bytes(b"\xef\xbb\xbf <llsd>"),
|
||||||
|
Some(WireFormat::Xml)
|
||||||
|
);
|
||||||
|
assert_eq!(classify_bytes(b" [1,2]"), Some(WireFormat::Json));
|
||||||
|
assert_eq!(classify_bytes(b"Unknown conference"), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_dispatch_matches_reference_fallback_to_json() {
|
||||||
|
assert_eq!(classify_string("<? llsd/binary ?>i"), WireFormat::Binary);
|
||||||
|
assert_eq!(classify_string("<LLSD>"), WireFormat::Xml);
|
||||||
|
assert_eq!(classify_string("plain text"), WireFormat::Json);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_byte_dispatch_rejects_unrecognized_plain_text() {
|
||||||
|
assert_eq!(
|
||||||
|
deserialize_bytes(b"Unknown conference".to_vec()),
|
||||||
|
Err(Error::Argument)
|
||||||
|
);
|
||||||
|
let cases: &[(&[u8], &str)] = &[
|
||||||
|
(
|
||||||
|
b"<? llsd/binary ?>i\0\0\0\0",
|
||||||
|
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDBinary(System.Byte[])",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
b"<? llsd/protobuf ?>",
|
||||||
|
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDProtobuf(System.Byte[])",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
b"<llsd><undef /></llsd>",
|
||||||
|
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeLLSDXml(System.Byte[])",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
b"{}",
|
||||||
|
"M:LibreMetaverse.StructuredData.OSDParser.DeserializeJson(System.String)",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
for (input, expected_member) in cases {
|
||||||
|
assert_eq!(
|
||||||
|
deserialize_bytes(input.to_vec())
|
||||||
|
.expect_err("format decoder is intentionally owned by a later issue")
|
||||||
|
.csharp_member(),
|
||||||
|
Some(*expected_member)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let stream = Box::new(std::io::Cursor::new(b"{}".to_vec()));
|
||||||
|
assert_eq!(
|
||||||
|
deserialize_stream(stream)
|
||||||
|
.expect_err("JSON decoder is intentionally owned by issue #36")
|
||||||
|
.csharp_member(),
|
||||||
|
Some("M:LibreMetaverse.StructuredData.OSDParser.DeserializeJson(System.String)")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
extern crate self as libremetaverse_structured_data;
|
extern crate self as libremetaverse_structured_data;
|
||||||
|
|
||||||
|
mod dispatch;
|
||||||
mod generated;
|
mod generated;
|
||||||
|
mod model;
|
||||||
|
|
||||||
pub mod xml {
|
pub mod xml {
|
||||||
/// Project-owned XML element boundary; external XML APIs are not copied.
|
/// Project-owned XML element boundary; external XML APIs are not copied.
|
||||||
|
|||||||
1855
crates/libremetaverse-structured-data/src/model.rs
Normal file
1855
crates/libremetaverse-structured-data/src/model.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,19 @@ impl<T: std::io::Read + std::io::Write> ReadWrite for T {}
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Object {
|
pub enum Object {
|
||||||
|
Undefined,
|
||||||
|
Boolean(bool),
|
||||||
|
Integer(i32),
|
||||||
|
UInteger(u32),
|
||||||
|
Long(i64),
|
||||||
|
ULong(u64),
|
||||||
|
Real(f64),
|
||||||
Color4(crate::Color4),
|
Color4(crate::Color4),
|
||||||
|
Date(std::time::SystemTime),
|
||||||
|
Bytes(Vec<u8>),
|
||||||
|
Uri(Uri),
|
||||||
|
Array(Vec<Object>),
|
||||||
|
Map(std::collections::HashMap<String, Object>),
|
||||||
Matrix4(crate::Matrix4),
|
Matrix4(crate::Matrix4),
|
||||||
Quaternion(crate::Quaternion),
|
Quaternion(crate::Quaternion),
|
||||||
String(String),
|
String(String),
|
||||||
@@ -36,6 +48,18 @@ pub enum Object {
|
|||||||
impl PartialEq for Object {
|
impl PartialEq for Object {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
|
(Self::Undefined, Self::Undefined) => true,
|
||||||
|
(Self::Boolean(lhs), Self::Boolean(rhs)) => lhs == rhs,
|
||||||
|
(Self::Integer(lhs), Self::Integer(rhs)) => lhs == rhs,
|
||||||
|
(Self::UInteger(lhs), Self::UInteger(rhs)) => lhs == rhs,
|
||||||
|
(Self::Long(lhs), Self::Long(rhs)) => lhs == rhs,
|
||||||
|
(Self::ULong(lhs), Self::ULong(rhs)) => lhs == rhs,
|
||||||
|
(Self::Real(lhs), Self::Real(rhs)) => lhs.to_bits() == rhs.to_bits(),
|
||||||
|
(Self::Date(lhs), Self::Date(rhs)) => lhs == rhs,
|
||||||
|
(Self::Bytes(lhs), Self::Bytes(rhs)) => lhs == rhs,
|
||||||
|
(Self::Uri(lhs), Self::Uri(rhs)) => lhs == rhs,
|
||||||
|
(Self::Array(lhs), Self::Array(rhs)) => lhs == rhs,
|
||||||
|
(Self::Map(lhs), Self::Map(rhs)) => lhs == rhs,
|
||||||
(Self::Color4(lhs), Self::Color4(rhs)) => crate::Color4::eq(*lhs, *rhs),
|
(Self::Color4(lhs), Self::Color4(rhs)) => crate::Color4::eq(*lhs, *rhs),
|
||||||
(Self::Matrix4(lhs), Self::Matrix4(rhs)) => crate::Matrix4::eq(*lhs, *rhs),
|
(Self::Matrix4(lhs), Self::Matrix4(rhs)) => crate::Matrix4::eq(*lhs, *rhs),
|
||||||
(Self::Quaternion(lhs), Self::Quaternion(rhs)) => crate::Quaternion::eq(*lhs, *rhs),
|
(Self::Quaternion(lhs), Self::Quaternion(rhs)) => crate::Quaternion::eq(*lhs, *rhs),
|
||||||
@@ -56,6 +80,22 @@ impl std::hash::Hash for Object {
|
|||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
std::mem::discriminant(self).hash(state);
|
std::mem::discriminant(self).hash(state);
|
||||||
match self {
|
match self {
|
||||||
|
Self::Undefined => {}
|
||||||
|
Self::Boolean(value) => value.hash(state),
|
||||||
|
Self::Integer(value) => value.hash(state),
|
||||||
|
Self::UInteger(value) => value.hash(state),
|
||||||
|
Self::Long(value) => value.hash(state),
|
||||||
|
Self::ULong(value) => value.hash(state),
|
||||||
|
Self::Real(value) => value.to_bits().hash(state),
|
||||||
|
Self::Date(value) => value.hash(state),
|
||||||
|
Self::Bytes(value) => value.hash(state),
|
||||||
|
Self::Uri(value) => value.hash(state),
|
||||||
|
Self::Array(value) => value.hash(state),
|
||||||
|
Self::Map(value) => {
|
||||||
|
let mut entries: Vec<_> = value.iter().collect();
|
||||||
|
entries.sort_unstable_by_key(|(key, _)| *key);
|
||||||
|
entries.hash(state);
|
||||||
|
}
|
||||||
Self::Color4(value) => value.get_hash_code().hash(state),
|
Self::Color4(value) => value.get_hash_code().hash(state),
|
||||||
Self::Matrix4(value) => value.get_hash_code().hash(state),
|
Self::Matrix4(value) => value.get_hash_code().hash(state),
|
||||||
Self::Quaternion(value) => value.get_hash_code().hash(state),
|
Self::Quaternion(value) => value.get_hash_code().hash(state),
|
||||||
|
|||||||
1
tests/api-compile/Cargo.lock
generated
1
tests/api-compile/Cargo.lock
generated
@@ -243,6 +243,7 @@ dependencies = [
|
|||||||
name = "libremetaverse-structured-data"
|
name = "libremetaverse-structured-data"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
"libremetaverse-types",
|
"libremetaverse-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -22,12 +22,9 @@ fn catalog_values_and_inert_layouts_are_real() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serializer_entry_point_returns_its_exact_member_id() {
|
fn parser_dispatch_rejects_empty_input_without_calling_a_decoder() {
|
||||||
let Err(error) = OSDParser::deserialize_with_bytes(Vec::new()) else {
|
let Err(error) = OSDParser::deserialize_with_bytes(Vec::new()) else {
|
||||||
panic!("failure-only serializer unexpectedly returned a value");
|
panic!("empty structured data unexpectedly returned a value");
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(error, libremetaverse_types::Error::Argument);
|
||||||
error.csharp_member(),
|
|
||||||
Some("M:LibreMetaverse.StructuredData.OSDParser.Deserialize(System.Byte[])")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,5 +20,5 @@
|
|||||||
"LibreMetaverse.Tests/TypeTests.cs::TypeTests.VectorCasting::test",
|
"LibreMetaverse.Tests/TypeTests.cs::TypeTests.VectorCasting::test",
|
||||||
"LibreMetaverse.Tests/UtilsConversionsTests.cs::UtilsConversionsTests.StringToBytes::test"
|
"LibreMetaverse.Tests/UtilsConversionsTests.cs::UtilsConversionsTests.StringToBytes::test"
|
||||||
],
|
],
|
||||||
"support_passes": 62
|
"support_passes": 73
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,21 @@ TARGETS = {
|
|||||||
# implementations. The generated module keeps catalog markers and re-exports
|
# implementations. The generated module keeps catalog markers and re-exports
|
||||||
# the hand-written type so coverage remains deterministic.
|
# the hand-written type so coverage remains deterministic.
|
||||||
NATIVE_TYPES = {
|
NATIVE_TYPES = {
|
||||||
|
"T:LibreMetaverse.StructuredData.OSD": "crate::model::OSD",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDArray": "crate::model::OSDArray",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDBinary": "crate::model::OSDBinary",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDBoolean": "crate::model::OSDBoolean",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDDate": "crate::model::OSDDate",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDException": "crate::model::OSDException",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDFormat": "crate::model::OSDFormat",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDInteger": "crate::model::OSDInteger",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDLlsdXml": "crate::model::OSDLlsdXml",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDMap": "crate::model::OSDMap",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDReal": "crate::model::OSDReal",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDString": "crate::model::OSDString",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDType": "crate::model::OSDType",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDUUID": "crate::model::OSDUUID",
|
||||||
|
"T:LibreMetaverse.StructuredData.OSDUri": "crate::model::OSDUri",
|
||||||
"T:LibreMetaverse.CacheDictionary`2": "crate::collections::CacheDictionary",
|
"T:LibreMetaverse.CacheDictionary`2": "crate::collections::CacheDictionary",
|
||||||
"T:LibreMetaverse.CacheDictionary`2.CacheDictionaryEnumerator": "crate::collections::CacheDictionaryCacheDictionaryEnumerator",
|
"T:LibreMetaverse.CacheDictionary`2.CacheDictionaryEnumerator": "crate::collections::CacheDictionaryCacheDictionaryEnumerator",
|
||||||
"T:LibreMetaverse.Color4": "crate::color4::Color4",
|
"T:LibreMetaverse.Color4": "crate::color4::Color4",
|
||||||
@@ -64,6 +79,12 @@ NATIVE_TYPES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NATIVE_MEMBER_BODIES = {
|
NATIVE_MEMBER_BODIES = {
|
||||||
|
"M:LibreMetaverse.StructuredData.OSDParser.Deserialize(System.Byte[])":
|
||||||
|
"crate::dispatch::deserialize_bytes(data)",
|
||||||
|
"M:LibreMetaverse.StructuredData.OSDParser.Deserialize(System.IO.Stream)":
|
||||||
|
"crate::dispatch::deserialize_stream(stream)",
|
||||||
|
"M:LibreMetaverse.StructuredData.OSDParser.Deserialize(System.String)":
|
||||||
|
"crate::dispatch::deserialize_string(data)",
|
||||||
"M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32)":
|
"M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32)":
|
||||||
"crate::byte_order::read_single_little_endian(src, pos)",
|
"crate::byte_order::read_single_little_endian(src, pos)",
|
||||||
"M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single)":
|
"M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single)":
|
||||||
|
|||||||
@@ -189,6 +189,14 @@ MEMBER_TYPE_OVERRIDES = {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
MEMBER_SIGNATURE_OVERRIDES = {
|
MEMBER_SIGNATURE_OVERRIDES = {
|
||||||
|
"M:LibreMetaverse.StructuredData.OSDArray.CopyTo(LibreMetaverse.StructuredData.OSD[],System.Int32)": (
|
||||||
|
"pub fn copy_to(&self, array: &mut [libremetaverse_structured_data::OSD], index: i32) -> Result<(), crate::Error>",
|
||||||
|
"shared_self,mutable_borrow,owned",
|
||||||
|
),
|
||||||
|
"M:LibreMetaverse.StructuredData.OSDMap.CopyTo(System.Collections.Generic.KeyValuePair{System.String,LibreMetaverse.StructuredData.OSD}[],System.Int32)": (
|
||||||
|
"pub fn copy_to(&self, array: &mut [(String, libremetaverse_structured_data::OSD)], index: i32) -> Result<(), crate::Error>",
|
||||||
|
"shared_self,mutable_borrow,owned",
|
||||||
|
),
|
||||||
"M:LibreMetaverse.CacheDictionary`2.CopyTo(System.Collections.Generic.KeyValuePair{`0,`1}[],System.Int32)": (
|
"M:LibreMetaverse.CacheDictionary`2.CopyTo(System.Collections.Generic.KeyValuePair{`0,`1}[],System.Int32)": (
|
||||||
"pub fn copy_to(&self, array: &mut [(TKey, Option<TValue>)], array_index: i32) -> Result<(), crate::Error>",
|
"pub fn copy_to(&self, array: &mut [(TKey, Option<TValue>)], array_index: i32) -> Result<(), crate::Error>",
|
||||||
"shared_self,mutable_borrow,owned",
|
"shared_self,mutable_borrow,owned",
|
||||||
@@ -972,6 +980,7 @@ def validate_generated_shims() -> None:
|
|||||||
"not_implemented(",
|
"not_implemented(",
|
||||||
"NotImplemented::new(",
|
"NotImplemented::new(",
|
||||||
"crate::byte_order::",
|
"crate::byte_order::",
|
||||||
|
"crate::dispatch::",
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
raise ValueError(f"generated shim function does not use the standardized failure: {path.relative_to(ROOT)}")
|
raise ValueError(f"generated shim function does not use the standardized failure: {path.relative_to(ROOT)}")
|
||||||
|
|||||||
Reference in New Issue
Block a user