| 1 | use crate::block::{ItemContent, ItemPtr}; |
| 2 | use crate::branch::{Branch, BranchPtr}; |
| 3 | use crate::types::{AsPrelim, ToJson}; |
| 4 | use crate::{ |
| 5 | any, Any, ArrayRef, Doc, GetString, In, MapPrelim, MapRef, ReadTxn, TextRef, XmlElementRef, |
| 6 | XmlFragmentRef, XmlTextRef, |
| 7 | }; |
| 8 | use std::convert::TryFrom; |
| 9 | use std::fmt::Formatter; |
| 10 | use std::sync::Arc; |
| 11 | |
| 12 | /// Value that can be returned by Yrs data types. This includes [Any] which is an extension |
| 13 | /// representation of JSON, but also nested complex collaborative structures specific to Yrs. |
| 14 | #[derive(Debug, Clone, PartialEq)] |
| 15 | pub enum Out { |
| 16 | /// Any value that it treated as a single element in its entirety. |
| 17 | Any(Any), |
| 18 | /// Instance of a [TextRef]. |
| 19 | YText(TextRef), |
| 20 | /// Instance of an [ArrayRef]. |
| 21 | YArray(ArrayRef), |
| 22 | /// Instance of a [MapRef]. |
| 23 | YMap(MapRef), |
| 24 | /// Instance of a [XmlElementRef]. |
| 25 | YXmlElement(XmlElementRef), |
| 26 | /// Instance of a [XmlFragmentRef]. |
| 27 | YXmlFragment(XmlFragmentRef), |
| 28 | /// Instance of a [XmlTextRef]. |
| 29 | YXmlText(XmlTextRef), |
| 30 | /// Subdocument. |
| 31 | YDoc(Doc), |
| 32 | /// Instance of a [WeakRef] or unspecified type (requires manual casting). |
| 33 | #[cfg(feature = "weak")] |
| 34 | YWeakLink(crate::WeakRef<BranchPtr>), |
| 35 | /// Instance of a shared collection of undefined type. Usually happens when it refers to a root |
| 36 | /// type that has not been defined locally. Can also refer to a [WeakRef] if "weak" feature flag |
| 37 | /// was not set. |
| 38 | UndefinedRef(BranchPtr), |
| 39 | } |
| 40 | |
| 41 | impl Default for Out { |
| 42 | fn default() -> Self { |
| 43 | Out::Any(Any::Undefined) |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | impl Out { |
| 48 | /// Attempts to convert current [Out] value directly onto a different type, as along as it |
| 49 | /// implements [TryFrom] trait. If conversion is not possible, the original value is returned. |
| 50 | #[inline] |
| 51 | pub fn cast<T>(self) -> Result<T, Self> |
| 52 | where |
| 53 | T: TryFrom<Self, Error = Self>, |
| 54 | { |
| 55 | T::try_from(self) |
| 56 | } |
| 57 | |
| 58 | /// Converts current value into stringified representation. |
| 59 | pub fn to_string<T: ReadTxn>(self, txn: &T) -> String { |
| 60 | match self { |
| 61 | Out::Any(a) => a.to_string(), |
| 62 | Out::YText(v) => v.get_string(txn), |
| 63 | Out::YArray(v) => v.to_json(txn).to_string(), |
| 64 | Out::YMap(v) => v.to_json(txn).to_string(), |
| 65 | Out::YXmlElement(v) => v.get_string(txn), |
| 66 | Out::YXmlFragment(v) => v.get_string(txn), |
| 67 | Out::YXmlText(v) => v.get_string(txn), |
| 68 | Out::YDoc(v) => v.to_string(), |
| 69 | #[cfg(feature = "weak")] |
| 70 | Out::YWeakLink(v) => { |
| 71 | let text_ref: crate::WeakRef<TextRef> = crate::WeakRef::from(v); |
| 72 | text_ref.get_string(txn) |
| 73 | } |
| 74 | Out::UndefinedRef(_) => "".to_string(), |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | pub fn try_branch(&self) -> Option<&Branch> { |
| 79 | match self { |
| 80 | Out::YText(b) => Some(b.as_ref()), |
| 81 | Out::YArray(b) => Some(b.as_ref()), |
| 82 | Out::YMap(b) => Some(b.as_ref()), |
| 83 | Out::YXmlElement(b) => Some(b.as_ref()), |
| 84 | Out::YXmlFragment(b) => Some(b.as_ref()), |
| 85 | Out::YXmlText(b) => Some(b.as_ref()), |
| 86 | #[cfg(feature = "weak")] |
| 87 | Out::YWeakLink(b) => Some(b.as_ref()), |
| 88 | Out::UndefinedRef(b) => Some(b.as_ref()), |
| 89 | Out::YDoc(_) => None, |
| 90 | Out::Any(_) => None, |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | impl TryFrom<ItemPtr> for Out { |
| 96 | type Error = ItemPtr; |
| 97 | |
| 98 | fn try_from(value: ItemPtr) -> Result<Self, Self::Error> { |
| 99 | match value.content.get_last() { |
| 100 | None => Err(value), |
| 101 | Some(v) => Ok(v), |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | impl AsPrelim for Out { |
| 107 | type Prelim = In; |
| 108 | |
| 109 | fn as_prelim<T: ReadTxn>(&self, txn: &T) -> Self::Prelim { |
| 110 | match self { |
| 111 | Out::Any(any) => In::Any(any.clone()), |
| 112 | Out::YText(v) => In::Text(v.as_prelim(txn)), |
| 113 | Out::YArray(v) => In::Array(v.as_prelim(txn)), |
| 114 | Out::YMap(v) => In::Map(v.as_prelim(txn)), |
| 115 | Out::YXmlElement(v) => In::XmlElement(v.as_prelim(txn)), |
| 116 | Out::YXmlFragment(v) => In::XmlFragment(v.as_prelim(txn)), |
| 117 | Out::YXmlText(v) => In::XmlText(v.as_prelim(txn)), |
| 118 | Out::YDoc(v) => In::Doc(v.clone()), |
| 119 | #[cfg(feature = "weak")] |
| 120 | Out::YWeakLink(v) => In::WeakLink(v.as_prelim(txn)), |
| 121 | Out::UndefinedRef(v) => infer_type_from_content(*v, txn), |
| 122 | } |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | fn infer_type_from_content<T: ReadTxn>(branch: BranchPtr, txn: &T) -> In { |
| 127 | let has_map = !branch.map.is_empty(); |
| 128 | let mut ptr = branch.start; |
| 129 | let has_list = ptr.is_some(); |
| 130 | let mut possible_text = false; |
| 131 | while let Some(curr) = ptr { |
| 132 | if !curr.is_deleted() { |
| 133 | possible_text = match &curr.content { |
| 134 | ItemContent::Embed(_) | ItemContent::Format(_, _) | ItemContent::String(_) => true, |
| 135 | _ => false, |
| 136 | }; |
| 137 | break; |
| 138 | } |
| 139 | ptr = curr.right; |
| 140 | } |
| 141 | |
| 142 | match (has_map, has_list, possible_text) { |
| 143 | (true, false, false) => In::Map(MapRef::from(branch).as_prelim(txn)), |
| 144 | (false, true, false) => In::Array(ArrayRef::from(branch).as_prelim(txn)), |
| 145 | (false, _, true) => In::Text(TextRef::from(branch).as_prelim(txn)), |
| 146 | (true, _, true) => In::XmlText(XmlTextRef::from(branch).as_prelim(txn)), |
| 147 | (true, true, false) => In::XmlElement(XmlElementRef::from(branch).as_prelim(txn)), |
| 148 | _ => In::Map(MapPrelim::default()), // if we have no content, default to map |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | impl<T> From<T> for Out |
| 153 | where |
| 154 | T: Into<Any>, |
| 155 | { |
| 156 | fn from(v: T) -> Self { |
| 157 | let any: Any = v.into(); |
| 158 | Out::Any(any) |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | //FIXME: what we would like to have is an automatic trait implementation of TryFrom<Value> for |
| 163 | // any type that implements TryFrom<Any,Error=Any>, but this causes compiler error. |
| 164 | macro_rules! impl_try_from { |
| 165 | ($t:ty) => { |
| 166 | impl TryFrom<Out> for $t { |
| 167 | type Error = Out; |
| 168 | |
| 169 | fn try_from(value: Out) -> Result<Self, Self::Error> { |
| 170 | use std::convert::TryInto; |
| 171 | match value { |
| 172 | Out::Any(any) => any.try_into().map_err(Out::Any), |
| 173 | other => Err(other), |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | }; |
| 178 | } |
| 179 | |
| 180 | impl_try_from!(bool); |
| 181 | impl_try_from!(f32); |
| 182 | impl_try_from!(f64); |
| 183 | impl_try_from!(i16); |
| 184 | impl_try_from!(i32); |
| 185 | impl_try_from!(u16); |
| 186 | impl_try_from!(u32); |
| 187 | impl_try_from!(i64); |
| 188 | impl_try_from!(isize); |
| 189 | impl_try_from!(String); |
| 190 | impl_try_from!(Arc<str>); |
| 191 | impl_try_from!(Vec<u8>); |
| 192 | impl_try_from!(Arc<[u8]>); |
| 193 | |
| 194 | impl ToJson for Out { |
| 195 | /// Converts current value into [Any] object equivalent that resembles enhanced JSON payload. |
| 196 | /// Rules are: |
| 197 | /// |
| 198 | /// - Primitive types ([Out::Any]) are passed right away, as no transformation is needed. |
| 199 | /// - [Out::YArray] is converted into JSON-like array. |
| 200 | /// - [Out::YMap] is converted into JSON-like object map. |
| 201 | /// - [Out::YText], [Out::YXmlText] and [Out::YXmlElement] are converted into strings |
| 202 | /// (XML types are stringified XML representation). |
| 203 | fn to_json<T: ReadTxn>(&self, txn: &T) -> Any { |
| 204 | match self { |
| 205 | Out::Any(a) => a.clone(), |
| 206 | Out::YText(v) => Any::from(v.get_string(txn)), |
| 207 | Out::YArray(v) => v.to_json(txn), |
| 208 | Out::YMap(v) => v.to_json(txn), |
| 209 | Out::YXmlElement(v) => Any::from(v.get_string(txn)), |
| 210 | Out::YXmlText(v) => Any::from(v.get_string(txn)), |
| 211 | Out::YXmlFragment(v) => Any::from(v.get_string(txn)), |
| 212 | Out::YDoc(doc) => any!({"guid": doc.guid().as_ref()}), |
| 213 | #[cfg(feature = "weak")] |
| 214 | Out::YWeakLink(_) => Any::Undefined, |
| 215 | Out::UndefinedRef(_) => Any::Undefined, |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | impl std::fmt::Display for Out { |
| 221 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| 222 | match self { |
| 223 | Out::Any(v) => std::fmt::Display::fmt(v, f), |
| 224 | Out::YText(_) => write!(f, "TextRef"), |
| 225 | Out::YArray(_) => write!(f, "ArrayRef"), |
| 226 | Out::YMap(_) => write!(f, "MapRef"), |
| 227 | Out::YXmlElement(_) => write!(f, "XmlElementRef"), |
| 228 | Out::YXmlFragment(_) => write!(f, "XmlFragmentRef"), |
| 229 | Out::YXmlText(_) => write!(f, "XmlTextRef"), |
| 230 | #[cfg(feature = "weak")] |
| 231 | Out::YWeakLink(_) => write!(f, "WeakRef"), |
| 232 | Out::YDoc(v) => write!(f, "Doc(guid:{})", v.guid()), |
| 233 | Out::UndefinedRef(_) => write!(f, "UndefinedRef"), |
| 234 | } |
| 235 | } |
| 236 | } |
| 237 | |