use libremetaverse_structured_data::{OSD, OSDMap}; use crate::Error; use crate::message_codec::MessageValue; fn empty_map() -> OSDMap { OSDMap::new_with_constructor().expect("empty OSD map construction is infallible") } fn map_value(map: &OSDMap, key: &str) -> Option { map.get(key) } /// Payload-preserving representation of the C# abstract upload response block. pub struct AssetUploaderBlock { pub state: String, payload: OSDMap, } impl AssetUploaderBlock { pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.state = map_value(&map, "state") .map(|value| value.as_string()) .transpose()? .unwrap_or_default(); self.payload = map; Ok(()) } pub fn serialize(&self) -> Result { if self.payload.count() == 0 { let map = empty_map(); if !self.state.is_empty() { map.add_with_string_osd("state".into(), OSD::from_string(self.state.clone())?)?; } Ok(map) } else { Ok(self.payload.clone()) } } } impl MessageValue for AssetUploaderBlock { fn message_default() -> Self { Self { state: String::new(), payload: empty_map(), } } fn to_message_osd(&self) -> Result { self.serialize()?.copy() } fn from_message_osd(value: &OSD) -> Result { let OSD::Map(values) = value else { return Err(Error::Argument); }; let map = OSDMap::new_with_dictionary(values.clone())?; let mut block = Self::message_default(); block.deserialize(map)?; Ok(block) } } pub struct ChatSessionRequestBlock { pub method: String, payload: OSDMap, } impl ChatSessionRequestBlock { pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.method = map_value(&map, "method") .ok_or(Error::Argument)? .as_string()?; self.payload = map; Ok(()) } pub fn serialize(&self) -> Result { if self.payload.count() == 0 { let map = empty_map(); map.add_with_string_osd("method".into(), OSD::from_string(self.method.clone())?)?; Ok(map) } else { Ok(self.payload.clone()) } } } impl MessageValue for ChatSessionRequestBlock { fn message_default() -> Self { Self { method: String::new(), payload: empty_map(), } } fn to_message_osd(&self) -> Result { self.serialize()?.copy() } fn from_message_osd(value: &OSD) -> Result { let OSD::Map(values) = value else { return Err(Error::Argument); }; let mut block = Self::message_default(); block.deserialize(OSDMap::new_with_dictionary(values.clone())?)?; Ok(block) } } pub struct MapLayerMessageBase { pub flags: i32, payload: OSDMap, } impl MapLayerMessageBase { pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.flags = if let Some(value) = map.get("Flags") { value.as_integer()? } else if let Some(OSD::Map(agent)) = map.get("AgentData") { agent.get("Flags").ok_or(Error::Argument)?.as_integer()? } else { return Err(Error::Argument); }; self.payload = map; Ok(()) } pub fn serialize(&self) -> Result { if self.payload.count() == 0 { let map = empty_map(); map.add_with_string_osd("Flags".into(), OSD::from_integer_with_int32(self.flags)?)?; Ok(map) } else { Ok(self.payload.clone()) } } } impl MessageValue for MapLayerMessageBase { fn message_default() -> Self { Self { flags: 0, payload: empty_map(), } } fn to_message_osd(&self) -> Result { self.serialize()?.copy() } fn from_message_osd(value: &OSD) -> Result { let OSD::Map(values) = value else { return Err(Error::Argument); }; let mut block = Self::message_default(); block.deserialize(OSDMap::new_with_dictionary(values.clone())?)?; Ok(block) } } pub struct MapLayerRequestVariant { pub flags: i32, } impl MapLayerRequestVariant { pub fn new() -> Result { Ok(Self { flags: 0 }) } pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.flags = map.get("Flags").ok_or(Error::Argument)?.as_integer()?; Ok(()) } pub fn serialize(&self) -> Result { let map = empty_map(); map.add_with_string_osd("Flags".into(), OSD::from_integer_with_int32(self.flags)?)?; Ok(map) } } impl MessageValue for MapLayerRequestVariant { fn message_default() -> Self { Self { flags: 0 } } fn to_message_osd(&self) -> Result { self.serialize()?.copy() } fn from_message_osd(value: &OSD) -> Result { let OSD::Map(values) = value else { return Err(Error::Argument); }; let mut block = Self::message_default(); block.deserialize(OSDMap::new_with_dictionary(values.clone())?)?; Ok(block) } } macro_rules! opaque_block { ($name:ident) => { pub struct $name { payload: OSDMap, } impl $name { pub fn deserialize(&mut self, map: OSDMap) -> Result<(), Error> { self.payload = map; Ok(()) } pub fn serialize(&self) -> Result { Ok(self.payload.clone()) } } impl MessageValue for $name { fn message_default() -> Self { Self { payload: empty_map(), } } fn to_message_osd(&self) -> Result { self.serialize()?.copy() } fn from_message_osd(value: &OSD) -> Result { let OSD::Map(values) = value else { return Err(Error::Argument); }; Ok(Self { payload: OSDMap::new_with_dictionary(values.clone())?, }) } } }; } opaque_block!(ObjectMediaBlock); opaque_block!(RemoteParcelRequestBlock); opaque_block!(SearchStatRequestBlock); #[cfg(test)] mod tests { use super::*; #[test] fn dynamic_upload_block_preserves_unmodeled_fields() { let map = empty_map(); map.add_with_string_osd("state".into(), OSD::from_string("complete".into()).unwrap()) .unwrap(); map.add_with_string_osd( "new_asset".into(), OSD::from_string("asset".into()).unwrap(), ) .unwrap(); let mut block = AssetUploaderBlock::message_default(); block.deserialize(map).unwrap(); assert_eq!(block.state, "complete"); assert_eq!( block.serialize().unwrap().get("new_asset"), Some(OSD::String("asset".into())) ); } #[test] fn map_layer_base_reads_both_request_and_reply_shapes() { let request = empty_map(); request .add_with_string_osd("Flags".into(), OSD::from_integer_with_int32(7).unwrap()) .unwrap(); let mut block = MapLayerMessageBase::message_default(); block.deserialize(request).unwrap(); assert_eq!(block.flags, 7); let reply = empty_map(); reply .add_with_string_osd( "AgentData".into(), OSD::Map(std::collections::HashMap::from([( "Flags".into(), OSD::Integer(9), )])), ) .unwrap(); block.deserialize(reply).unwrap(); assert_eq!(block.flags, 9); } }