ron_crdt/
op.rs

1//! Op
2
3use std::fmt;
4
5use smallvec::SmallVec;
6use uuid::UUID;
7
8use crate::Atom;
9
10/// Op terminator
11#[derive(Clone, Copy, PartialEq, Eq)]
12pub enum Terminator {
13    /// Raw ops are stand-alone within a frame.
14    Raw,
15    /// Query ops, as well as reduced ops following them, create a chunk in a frame.
16    Query,
17    /// Header ops, as well as reduced ops following them, create a chunk in a frame.
18    Header,
19    /// Reduced ops belong to the query/header op before them.
20    Reduced,
21}
22
23impl fmt::Debug for Terminator {
24    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25        write!(f, "{}", self)
26    }
27}
28
29impl fmt::Display for Terminator {
30    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31        match self {
32            Terminator::Raw => f.write_str(";"),
33            Terminator::Reduced => f.write_str(","),
34            Terminator::Header => f.write_str("!"),
35            Terminator::Query => f.write_str("?"),
36        }
37    }
38}
39
40impl Default for Terminator {
41    fn default() -> Terminator {
42        Terminator::Reduced
43    }
44}
45
46impl Terminator {
47    /// Parse a terminator from `inp`.
48    pub fn from_string(inp: &str) -> Result<Terminator, &'static str> {
49        match &inp {
50            &";" => Ok(Terminator::Raw),
51            &"?" => Ok(Terminator::Query),
52            &"!" => Ok(Terminator::Header),
53            &"," => Ok(Terminator::Reduced),
54            _ => Err("invalid terminator"),
55        }
56    }
57
58    /// Parse a terminator from `inp`.
59    pub fn from_char(inp: char) -> Result<Terminator, &'static str> {
60        Terminator::from_string(&inp.to_string())
61    }
62}
63
64/// An Op (operation) in RON describes part of the initial state of an object, or a specific
65/// action on an object, or some other part related to the Swarm protocol (such as a query or
66/// handshake).
67///
68/// Every op consists of four UUIDs (type, object, event and re), a (possibly empty) sequence of
69/// atoms, and a terminator.
70#[derive(Clone, PartialEq)]
71pub struct Op {
72    /// Op type.
73    pub ty: UUID,
74    /// Object this Op modifies.
75    pub object: UUID,
76    /// Event timestamp
77    pub event: UUID,
78    /// Location/field inside the object.
79    pub location: UUID,
80    /// Op payload.
81    pub atoms: SmallVec<[Atom; 3]>,
82    /// Op terminator.
83    pub term: Terminator,
84}
85
86impl Op {
87    /// Parse a single Op from string `input` into `prev`. If `prev` is not `None` the Op may be
88    /// compressed against `prev`.
89    pub fn parse_inplace<'a>(
90        prev: &mut Option<Op>, input: &'a str,
91    ) -> Option<&'a str> {
92        let mut ret = input;
93        let mut ty = None;
94        let mut event = None;
95        let mut object = None;
96        let mut location = None;
97
98        {
99            let mut prev_uu = prev.as_ref().map(|p| &p.location);
100
101            // type
102            ret = ret.trim_start();
103            if ret.starts_with('*') {
104                let ctx = prev.as_ref().map(|p| (&p.ty, prev_uu.unwrap()));
105
106                match UUID::parse(&ret[1..], ctx) {
107                    Some((t, rest)) => {
108                        ret = rest;
109                        ty = Some(t);
110                        prev_uu = ty.as_ref();
111                    }
112                    None => {
113                        ret = &ret[1..];
114                        ty = prev.as_ref().map(|p| p.ty.clone());
115                        prev_uu = prev.as_ref().map(|p| &p.ty);
116                    }
117                }
118            } else {
119                prev_uu = prev.as_ref().map(|p| &p.ty);
120            }
121
122            // object
123            ret = ret.trim_start();
124            if ret.starts_with('#') {
125                let ctx = prev.as_ref().map(|p| (&p.object, prev_uu.unwrap()));
126
127                match UUID::parse(&ret[1..], ctx) {
128                    Some((obj, rest)) => {
129                        ret = rest;
130                        object = Some(obj);
131                        prev_uu = object.as_ref();
132                    }
133                    None => {
134                        ret = &ret[1..];
135                        object = prev.as_ref().map(|p| p.object.clone());
136                        prev_uu = prev.as_ref().map(|p| &p.object);
137                    }
138                }
139            } else {
140                prev_uu = prev.as_ref().map(|p| &p.object);
141            }
142
143            // event
144            ret = ret.trim_start();
145            if ret.starts_with('@') {
146                let ctx = prev.as_ref().map(|p| (&p.event, prev_uu.unwrap()));
147
148                match UUID::parse(&ret[1..], ctx) {
149                    Some((ev, rest)) => {
150                        ret = rest;
151                        event = Some(ev);
152                        prev_uu = event.as_ref();
153                    }
154                    None => {
155                        ret = &ret[1..];
156                        event = prev.as_ref().map(|p| p.event.clone());
157                        prev_uu = prev.as_ref().map(|p| &p.event);
158                    }
159                }
160            } else {
161                prev_uu = prev.as_ref().map(|p| &p.event);
162            }
163
164            // location
165            ret = ret.trim_start();
166            if ret.starts_with(':') {
167                let ctx =
168                    prev.as_ref().map(|p| (&p.location, prev_uu.unwrap()));
169
170                match UUID::parse(&ret[1..], ctx) {
171                    Some((loc, rest)) => {
172                        ret = rest;
173                        location = Some(loc);
174                    }
175                    None => {
176                        ret = &ret[1..];
177                        location = prev.as_ref().map(|p| p.location.clone());
178                    }
179                }
180            }
181        }
182
183        let no_spec = ty.is_none()
184            && event.is_none()
185            && object.is_none()
186            && location.is_none();
187
188        let prev = match prev {
189            &mut Some(ref mut prev) => {
190                if let Some(ty) = ty {
191                    prev.ty = ty;
192                }
193                if let Some(event) = event {
194                    prev.event = event;
195                }
196                if let Some(object) = object {
197                    prev.object = object;
198                }
199                if let Some(location) = location {
200                    prev.location = location;
201                }
202
203                prev
204            }
205            &mut None
206                if ty.is_some() && object.is_some() && event.is_some() =>
207            {
208                *prev = Some(Op {
209                    ty: ty.unwrap(),
210                    object: object.unwrap(),
211                    event: event.unwrap(),
212                    location: location.unwrap_or_else(UUID::zero),
213                    atoms: Default::default(),
214                    term: Terminator::Header,
215                });
216                prev.as_mut().unwrap()
217            }
218            &mut None => {
219                return None;
220            }
221        };
222
223        // atoms
224        prev.atoms.clear();
225
226        {
227            let mut prev_uu = prev.object.clone();
228
229            while let Some((atm, rest)) =
230                Atom::parse(ret, Some((&prev.object, &prev_uu)))
231            {
232                match &atm {
233                    &Atom::UUID(ref uu) => {
234                        prev_uu = uu.clone();
235                    }
236                    _ => {}
237                }
238
239                ret = rest;
240                prev.atoms.push(atm);
241            }
242        }
243
244        // Terminator
245        ret = ret.trim_start();
246        match ret.chars().next() {
247            Some('!') => {
248                prev.term = Terminator::Header;
249                ret = &ret[1..];
250            }
251            Some('?') => {
252                prev.term = Terminator::Query;
253                ret = &ret[1..];
254            }
255            Some(',') => {
256                prev.term = Terminator::Reduced;
257                ret = &ret[1..];
258            }
259            Some(';') => {
260                prev.term = Terminator::Raw;
261                ret = &ret[1..];
262            }
263            _ if !no_spec || !prev.atoms.is_empty() => {
264                prev.term = Terminator::Reduced;
265            }
266            _ => {
267                return None;
268            }
269        }
270
271        Some(ret)
272    }
273
274    /// Serialize Op to text optionally compressing it against `context`.
275    pub fn compress(&self, context: Option<&Op>) -> String {
276        let mut ret = String::default();
277
278        if context.map(|o| o.ty != self.ty).unwrap_or(true) {
279            let ctx = context.map(|p| (&p.ty, &p.ty));
280
281            ret += "*";
282            ret += &self.ty.compress(ctx);
283        }
284
285        if context.map(|o| o.object != self.object).unwrap_or(true) {
286            let ctx = context.map(|p| (&p.object, &p.ty));
287
288            ret += "#";
289            ret += &self.object.compress(ctx);
290        }
291
292        if context.map(|o| o.event != self.event).unwrap_or(true) {
293            let ctx = context.map(|p| (&p.event, &p.object));
294
295            ret += "@";
296            ret += &self.event.compress(ctx);
297        }
298
299        if context.map(|o| o.location != self.location).unwrap_or(true) {
300            let ctx = context.map(|p| (&p.location, &p.event));
301
302            ret += ":";
303            ret += &self.location.compress(ctx);
304        }
305
306        if ret.is_empty() {
307            ret = "@".to_string();
308        }
309
310        let mut prev = &self.object;
311        for atm in self.atoms.iter() {
312            match atm {
313                &Atom::Integer(i) => {
314                    ret += &format!("={}", i);
315                }
316                &Atom::String(ref i) => {
317                    ret += &format!("'{}'", i);
318                }
319                &Atom::Float(i) => {
320                    ret += &format!("^{}", i);
321                }
322                &Atom::UUID(ref i) => {
323                    ret += ">";
324                    ret += &i.compress(Some((prev, prev)));
325                    prev = i;
326                }
327            }
328        }
329
330        ret + &format!("{}", self.term)
331    }
332}
333impl fmt::Debug for Op {
334    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
335        write!(f, "{}", self)
336    }
337}
338
339impl fmt::Display for Op {
340    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
341        write!(
342            f,
343            "*{}#{}@{}:{}",
344            self.ty, self.object, self.event, self.location
345        )?;
346
347        match self.atoms.len() {
348            0 => write!(f, "{}", self.term),
349            1 => write!(f, "{}{}", self.atoms[0], self.term),
350            _ => {
351                write!(f, "{}", self.atoms[0])?;
352                for atm in self.atoms[1..].iter() {
353                    write!(f, ", {}", atm)?;
354                }
355                write!(f, "{}", self.term)
356            }
357        }
358    }
359}
360
361#[test]
362fn compress_roundtrip() {
363    use crate::Frame;
364
365    let f = Frame::parse(
366        "
367        *set#mice@1YKDXO3201+1YKDXO!
368                 @>mouse$1YKDXO
369                 @(WBF901(WBY>mouse$1YKDWBY
370                 @[67H01[6>mouse$1YKDW6
371                 @(Uh4j01(Uh>mouse$1YKDUh
372                 @(S67V01(S6>mouse$1YKDS6
373                 @(Of(N3:1YKDN3DS01+1YKDN3,
374                 @(MvBV01(IuJ:0>mouse$1YKDIuJ
375                 @(LF:1YKDIuEY01+1YKDIuJ,
376                 :{A601,
377                 @(Io5l01[oA:0>mouse$1YKDIoA
378                 @[l7_01[l>mouse$1YKDIl
379                 @(57(4B:1YKD4B3f01+1YKD4B,
380                 @(0bB401+1YKCsd:0>mouse$1Y",
381    );
382
383    let ops = f.into_iter().collect::<Vec<_>>();
384    for win in ops.windows(2) {
385        let ctx = &win[0];
386        let op = &win[1];
387        let txt = op.compress(Some(ctx));
388        let mut back = Some(ctx.clone());
389
390        Op::parse_inplace(&mut back, &txt[..]);
391
392        eprintln!("ctx: {}", ctx);
393        eprintln!("op : {}", op);
394        eprintln!("txt: {}", txt);
395        eprintln!("rt : {}", back.as_ref().unwrap());
396        eprintln!("");
397        assert_eq!(back.unwrap(), *op);
398    }
399}