Skip to main content

rws_server/authz/
rebac.rs

1//! Relationship-based access control (Zanzibar style): tuples, a schema,
2//! a store and the check that walks them.
3//!
4//! A **tuple** says a subject has a relation to an object:
5//! `doc:42#editor@user:alice`, or with a set of subjects
6//! `doc:42#viewer@group:eng#member` (every member of `group:eng`).
7//!
8//! The **schema** says, per object type, how each relation is made up — a
9//! union (`|`) of terms:
10//!
11//! | Term | Means |
12//! |---|---|
13//! | `user` | tuples naming a `user:…` directly |
14//! | `group#member` | tuples naming `group:…#member` — those members |
15//! | `owner` | whoever has `owner` on the same object |
16//! | `parent->viewer` | the viewers of whatever this object's `parent` is |
17//!
18//! ```rust
19//! # extern crate rws_server as rust_web_server;
20//! use rust_web_server::authz::{Authorizer, MemoryStore, Schema};
21//!
22//! let schema = Schema::new()
23//!     .relation("group", "member", "user").unwrap()
24//!     .relation("folder", "viewer", "user | group#member").unwrap()
25//!     .relation("doc", "parent", "folder").unwrap()
26//!     .relation("doc", "owner", "user").unwrap()
27//!     .relation("doc", "viewer", "user | owner | parent->viewer").unwrap();
28//! let authz = Authorizer::new(schema, MemoryStore::new()).unwrap();
29//! for tuple in ["group:eng#member@user:bob", "folder:specs#viewer@group:eng#member", "doc:42#parent@folder:specs"] {
30//!     authz.write_str(tuple).unwrap();
31//! }
32//! assert!(authz.check("user:bob", "viewer", "doc:42").unwrap());
33//! assert!(!authz.check("user:eve", "viewer", "doc:42").unwrap());
34//! ```
35
36use std::collections::{BTreeSet, HashMap, HashSet};
37use std::fmt;
38use std::sync::{Arc, RwLock};
39
40/// The longest chain of relations a check follows.
41pub const MAX_DEPTH: usize = 25;
42/// The most store reads one check makes.
43pub const MAX_READS: usize = 1000;
44/// Longest type name, id or relation name accepted.
45pub const MAX_NAME: usize = 256;
46
47/// What can go wrong.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub enum AuthzError {
50    /// Text that isn't a tuple, object, subject or relation expression.
51    Parse(String),
52    /// Not allowed by the schema (unknown type or relation, a subject the
53    /// relation doesn't take).
54    Schema(String),
55    /// The check hit [`MAX_DEPTH`] or [`MAX_READS`] before an answer.
56    Limit(String),
57    /// The tuple store failed.
58    Store(String),
59}
60
61impl fmt::Display for AuthzError {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            AuthzError::Parse(e) => write!(f, "parse error: {e}"),
65            AuthzError::Schema(e) => write!(f, "schema: {e}"),
66            AuthzError::Limit(e) => write!(f, "limit: {e}"),
67            AuthzError::Store(e) => write!(f, "store: {e}"),
68        }
69    }
70}
71
72impl std::error::Error for AuthzError {}
73
74fn name(text: &str, what: &str) -> Result<String, AuthzError> {
75    let ok = !text.is_empty()
76        && text.len() <= MAX_NAME
77        && text.bytes().all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-' | b'.'));
78    if ok {
79        Ok(text.to_string())
80    } else {
81        Err(AuthzError::Parse(format!("{what} {text:?}: letters, digits, '_', '-' and '.' only, 1–{MAX_NAME} characters")))
82    }
83}
84
85/// An id: anything but `#` (it ends an object), spaces and control
86/// characters — `@` is fine (`user:alice@example.com`): a tuple's object
87/// ends at its `#` and the relation, a name, at the next `@`.
88fn id(text: &str) -> Result<String, AuthzError> {
89    let ok = !text.is_empty() && text.len() <= MAX_NAME && !text.bytes().any(|b| b == b'#' || b == b' ' || b.is_ascii_control());
90    if ok {
91        Ok(text.to_string())
92    } else {
93        Err(AuthzError::Parse(format!("id {text:?}: 1–{MAX_NAME} characters, no '#', spaces or control characters")))
94    }
95}
96
97/// An object: `type:id`.
98#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
99pub struct ObjectRef {
100    pub kind: String,
101    pub id: String,
102}
103
104impl ObjectRef {
105    pub fn parse(text: &str) -> Result<ObjectRef, AuthzError> {
106        let (kind, rest) = text.split_once(':').ok_or_else(|| AuthzError::Parse(format!("object {text:?}: expected type:id")))?;
107        Ok(ObjectRef { kind: name(kind, "type")?, id: id(rest)? })
108    }
109}
110
111impl fmt::Display for ObjectRef {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(f, "{}:{}", self.kind, self.id)
114    }
115}
116
117/// Who a tuple names: an object (`user:alice`) or a set (`group:eng#member`).
118#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
119pub struct Subject {
120    pub object: ObjectRef,
121    pub relation: Option<String>,
122}
123
124impl Subject {
125    pub fn parse(text: &str) -> Result<Subject, AuthzError> {
126        match text.split_once('#') {
127            Some((object, relation)) => Ok(Subject { object: ObjectRef::parse(object)?, relation: Some(name(relation, "relation")?) }),
128            None => Ok(Subject { object: ObjectRef::parse(text)?, relation: None }),
129        }
130    }
131}
132
133impl fmt::Display for Subject {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        match &self.relation {
136            Some(relation) => write!(f, "{}#{relation}", self.object),
137            None => write!(f, "{}", self.object),
138        }
139    }
140}
141
142/// `object#relation@subject`.
143#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
144pub struct Tuple {
145    pub object: ObjectRef,
146    pub relation: String,
147    pub subject: Subject,
148}
149
150impl Tuple {
151    pub fn parse(text: &str) -> Result<Tuple, AuthzError> {
152        let text = text.trim();
153        let malformed = || AuthzError::Parse(format!("tuple {text:?}: expected object#relation@subject"));
154        let (object, rest) = text.split_once('#').ok_or_else(malformed)?;
155        let (relation, subject) = rest.split_once('@').ok_or_else(malformed)?;
156        Ok(Tuple { object: ObjectRef::parse(object)?, relation: name(relation, "relation")?, subject: Subject::parse(subject)? })
157    }
158}
159
160impl fmt::Display for Tuple {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        write!(f, "{}#{}@{}", self.object, self.relation, self.subject)
163    }
164}
165
166/// One term of a relation's definition.
167#[derive(Clone, Debug, PartialEq, Eq)]
168pub enum Term {
169    /// `user`: tuples naming an object of this type.
170    Direct(String),
171    /// `group#member`: tuples naming this type's objects with that relation.
172    DirectSet(String, String),
173    /// `owner`: another relation on the same object.
174    Computed(String),
175    /// `parent->viewer`: `viewer` on the objects this one's `parent` names.
176    TupleToUserset { tupleset: String, computed: String },
177}
178
179/// Parse a relation definition: terms separated by `|`. A bare name is a
180/// type when the schema has such a type, else a relation on the same object
181/// (resolved by [`Schema::relation`]); here it comes back as `Direct`.
182pub fn parse_expression(text: &str) -> Result<Vec<Term>, AuthzError> {
183    if text.len() > 4096 {
184        return Err(AuthzError::Parse("relation definition over 4096 characters".to_string()));
185    }
186    let mut terms = Vec::new();
187    for part in text.split('|') {
188        let part = part.trim();
189        let term = if let Some((tupleset, computed)) = part.split_once("->") {
190            Term::TupleToUserset { tupleset: name(tupleset.trim(), "relation")?, computed: name(computed.trim(), "relation")? }
191        } else if let Some((kind, relation)) = part.split_once('#') {
192            Term::DirectSet(name(kind.trim(), "type")?, name(relation.trim(), "relation")?)
193        } else {
194            Term::Direct(name(part, "type or relation")?)
195        };
196        terms.push(term);
197    }
198    if terms.is_empty() {
199        return Err(AuthzError::Parse("empty relation definition".to_string()));
200    }
201    Ok(terms)
202}
203
204/// Object types and their relations.
205#[derive(Clone, Debug, Default)]
206pub struct Schema {
207    types: HashMap<String, HashMap<String, Vec<Term>>>,
208    /// Definitions as written, in order, for resolving and reporting.
209    written: Vec<(String, String, String)>,
210}
211
212impl Schema {
213    pub fn new() -> Schema {
214        Schema::default()
215    }
216
217    /// Define `relation` on `kind` as `definition` (see [`parse_expression`]).
218    /// Names are resolved when the schema is used ([`Schema::validate`]), so
219    /// definitions may refer to types defined later.
220    pub fn relation(mut self, kind: &str, relation: &str, definition: &str) -> Result<Schema, AuthzError> {
221        let (kind, relation) = (name(kind, "type")?, name(relation, "relation")?);
222        let terms = parse_expression(definition)?;
223        self.types.entry(kind.clone()).or_default().insert(relation.clone(), terms);
224        self.written.push((kind, relation, definition.to_string()));
225        Ok(self)
226    }
227
228    /// A type with no relations of its own (e.g. `user`).
229    pub fn object_type(mut self, kind: &str) -> Result<Schema, AuthzError> {
230        self.types.entry(name(kind, "type")?).or_default();
231        Ok(self)
232    }
233
234    /// The type names, sorted.
235    pub fn types(&self) -> Vec<&str> {
236        let mut types: Vec<&str> = self.types.keys().map(String::as_str).collect();
237        types.sort_unstable();
238        types
239    }
240
241    /// The relation names of `kind`, sorted.
242    pub fn relations(&self, kind: &str) -> Vec<&str> {
243        let mut relations: Vec<&str> = self.types.get(kind).map(|r| r.keys().map(String::as_str).collect()).unwrap_or_default();
244        relations.sort_unstable();
245        relations
246    }
247
248    pub fn has_relation(&self, kind: &str, relation: &str) -> bool {
249        self.types.get(kind).is_some_and(|r| r.contains_key(relation))
250    }
251
252    /// Resolve bare names (a type used as a subject type, or else a relation
253    /// on the same object) and check every reference; returns the resolved
254    /// schema. Subject types that appear only in definitions (`user`) become
255    /// types.
256    pub fn validate(&self) -> Result<Schema, AuthzError> {
257        let mut known: HashSet<String> = self.types.keys().cloned().collect();
258        let mut resolved = self.clone();
259        for (kind, relations) in &self.types {
260            for (relation, terms) in relations {
261                let mut out = Vec::with_capacity(terms.len());
262                for term in terms {
263                    out.push(match term {
264                        Term::Direct(word) if relations.contains_key(word) && !self.types.contains_key(word) => Term::Computed(word.clone()),
265                        Term::Direct(word) => {
266                            known.insert(word.clone());
267                            Term::Direct(word.clone())
268                        }
269                        Term::DirectSet(set_kind, set_relation) => {
270                            if !self.has_relation(set_kind, set_relation) {
271                                return Err(AuthzError::Schema(format!("{kind}#{relation}: {set_kind}#{set_relation} isn't defined")));
272                            }
273                            term.clone()
274                        }
275                        Term::TupleToUserset { tupleset, .. } => {
276                            let Some(set_terms) = relations.get(tupleset) else {
277                                return Err(AuthzError::Schema(format!("{kind}#{relation}: {tupleset} isn't a relation of {kind}")));
278                            };
279                            if !set_terms.iter().all(|t| matches!(t, Term::Direct(_))) {
280                                return Err(AuthzError::Schema(format!("{kind}#{relation}: {tupleset} must name objects directly (only types, no sets or relations)")));
281                            }
282                            term.clone()
283                        }
284                        Term::Computed(other) => {
285                            if !relations.contains_key(other) {
286                                return Err(AuthzError::Schema(format!("{kind}#{relation}: {other} isn't a relation of {kind}")));
287                            }
288                            term.clone()
289                        }
290                    });
291                }
292                resolved.types.get_mut(kind).expect("kind").insert(relation.clone(), out);
293            }
294        }
295        // `parent->viewer`: viewer must exist on every type parent names.
296        for (kind, relations) in &resolved.types {
297            for (relation, terms) in relations {
298                for term in terms {
299                    if let Term::TupleToUserset { tupleset, computed } = term {
300                        for target in relations[tupleset].iter().filter_map(|t| if let Term::Direct(k) = t { Some(k) } else { None }) {
301                            if !resolved.has_relation(target, computed) {
302                                return Err(AuthzError::Schema(format!("{kind}#{relation}: {tupleset}->{computed} needs {computed} on {target}")));
303                            }
304                        }
305                    }
306                }
307            }
308        }
309        for kind in known {
310            resolved.types.entry(kind).or_default();
311        }
312        Ok(resolved)
313    }
314
315    fn terms(&self, kind: &str, relation: &str) -> Option<&[Term]> {
316        self.types.get(kind)?.get(relation).map(Vec::as_slice)
317    }
318
319    /// Whether the schema lets `tuple` be stored: a known relation whose
320    /// definition takes this kind of subject directly.
321    pub fn allows(&self, tuple: &Tuple) -> Result<(), AuthzError> {
322        let terms = self
323            .terms(&tuple.object.kind, &tuple.relation)
324            .ok_or_else(|| AuthzError::Schema(format!("{}#{} isn't defined", tuple.object.kind, tuple.relation)))?;
325        let fits = terms.iter().any(|term| match (term, &tuple.subject.relation) {
326            (Term::Direct(kind), None) => *kind == tuple.subject.object.kind,
327            (Term::DirectSet(kind, relation), Some(r)) => *kind == tuple.subject.object.kind && relation == r,
328            _ => false,
329        });
330        if fits {
331            Ok(())
332        } else {
333            Err(AuthzError::Schema(format!("{}#{} doesn't take {} as a subject", tuple.object.kind, tuple.relation, tuple.subject)))
334        }
335    }
336}
337
338/// Which tuples to list: any field left `None` matches everything.
339#[derive(Clone, Debug, Default)]
340pub struct TupleFilter {
341    pub object: Option<ObjectRef>,
342    pub object_kind: Option<String>,
343    pub relation: Option<String>,
344    pub subject: Option<Subject>,
345}
346
347impl TupleFilter {
348    pub fn matches(&self, tuple: &Tuple) -> bool {
349        self.object.as_ref().is_none_or(|o| *o == tuple.object)
350            && self.object_kind.as_ref().is_none_or(|k| *k == tuple.object.kind)
351            && self.relation.as_ref().is_none_or(|r| *r == tuple.relation)
352            && self.subject.as_ref().is_none_or(|s| *s == tuple.subject)
353    }
354}
355
356/// Where tuples live.
357pub trait TupleStore: Send + Sync {
358    /// Store `tuple`; `true` if it wasn't there.
359    fn write(&self, tuple: &Tuple) -> Result<bool, AuthzError>;
360    /// Remove `tuple`; `true` if it was there.
361    fn delete(&self, tuple: &Tuple) -> Result<bool, AuthzError>;
362    /// The subjects with `relation` on `object`.
363    fn read(&self, object: &ObjectRef, relation: &str) -> Result<Vec<Subject>, AuthzError>;
364    /// Tuples matching `filter`, at most `limit`.
365    fn list(&self, filter: &TupleFilter, limit: usize) -> Result<Vec<Tuple>, AuthzError>;
366}
367
368/// Tuples in memory: for tests, small apps, and tuples seeded from the
369/// config file. Lost on restart.
370#[derive(Default)]
371pub struct MemoryStore {
372    tuples: RwLock<HashMap<(ObjectRef, String), BTreeSet<Subject>>>,
373}
374
375impl MemoryStore {
376    pub fn new() -> MemoryStore {
377        MemoryStore::default()
378    }
379}
380
381impl TupleStore for MemoryStore {
382    fn write(&self, tuple: &Tuple) -> Result<bool, AuthzError> {
383        let mut tuples = self.tuples.write().unwrap_or_else(|e| e.into_inner());
384        Ok(tuples.entry((tuple.object.clone(), tuple.relation.clone())).or_default().insert(tuple.subject.clone()))
385    }
386
387    fn delete(&self, tuple: &Tuple) -> Result<bool, AuthzError> {
388        let mut tuples = self.tuples.write().unwrap_or_else(|e| e.into_inner());
389        let key = (tuple.object.clone(), tuple.relation.clone());
390        let removed = tuples.get_mut(&key).is_some_and(|subjects| subjects.remove(&tuple.subject));
391        if tuples.get(&key).is_some_and(BTreeSet::is_empty) {
392            tuples.remove(&key);
393        }
394        Ok(removed)
395    }
396
397    fn read(&self, object: &ObjectRef, relation: &str) -> Result<Vec<Subject>, AuthzError> {
398        let tuples = self.tuples.read().unwrap_or_else(|e| e.into_inner());
399        Ok(tuples.get(&(object.clone(), relation.to_string())).map(|s| s.iter().cloned().collect()).unwrap_or_default())
400    }
401
402    fn list(&self, filter: &TupleFilter, limit: usize) -> Result<Vec<Tuple>, AuthzError> {
403        let tuples = self.tuples.read().unwrap_or_else(|e| e.into_inner());
404        let mut out: Vec<Tuple> = tuples
405            .iter()
406            .flat_map(|((object, relation), subjects)| {
407                subjects.iter().map(move |subject| Tuple { object: object.clone(), relation: relation.clone(), subject: subject.clone() })
408            })
409            .filter(|t| filter.matches(t))
410            .collect();
411        out.sort();
412        out.truncate(limit);
413        Ok(out)
414    }
415}
416
417/// A schema and a tuple store: writes checked against the schema, and
418/// [`check`](Self::check).
419#[derive(Clone)]
420pub struct Authorizer {
421    schema: Arc<Schema>,
422    store: Arc<dyn TupleStore>,
423}
424
425impl fmt::Debug for Authorizer {
426    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
427        f.debug_struct("Authorizer").field("types", &self.schema.types()).finish_non_exhaustive()
428    }
429}
430
431impl Authorizer {
432    /// `schema` is validated (see [`Schema::validate`]).
433    pub fn new(schema: Schema, store: impl TupleStore + 'static) -> Result<Authorizer, AuthzError> {
434        Authorizer::with_store(schema, Arc::new(store))
435    }
436
437    pub fn with_store(schema: Schema, store: Arc<dyn TupleStore>) -> Result<Authorizer, AuthzError> {
438        Ok(Authorizer { schema: Arc::new(schema.validate()?), store })
439    }
440
441    pub fn schema(&self) -> &Schema {
442        &self.schema
443    }
444
445    pub fn store(&self) -> &Arc<dyn TupleStore> {
446        &self.store
447    }
448
449    /// Store `tuple` if the schema allows it; `true` if it was new.
450    pub fn write(&self, tuple: &Tuple) -> Result<bool, AuthzError> {
451        self.schema.allows(tuple)?;
452        self.store.write(tuple)
453    }
454
455    /// [`write`](Self::write) from text (`doc:42#editor@user:alice`).
456    pub fn write_str(&self, tuple: &str) -> Result<bool, AuthzError> {
457        self.write(&Tuple::parse(tuple)?)
458    }
459
460    pub fn delete(&self, tuple: &Tuple) -> Result<bool, AuthzError> {
461        self.store.delete(tuple)
462    }
463
464    /// Whether `subject` (`user:alice`) has `relation` on `object`
465    /// (`doc:42`).
466    pub fn check(&self, subject: &str, relation: &str, object: &str) -> Result<bool, AuthzError> {
467        Ok(self.explain(subject, relation, object)?.is_some())
468    }
469
470    /// Like [`check`](Self::check), with the path that granted it: each step
471    /// `object#relation`, ending with the tuple that named the subject.
472    pub fn explain(&self, subject: &str, relation: &str, object: &str) -> Result<Option<Vec<String>>, AuthzError> {
473        self.explain_subject(&Subject::parse(subject)?, relation, &ObjectRef::parse(object)?)
474    }
475
476    /// [`check`](Self::check) with a subject and object already parsed —
477    /// for a caller, so an id is never re-read as text.
478    pub fn check_subject(&self, subject: &Subject, relation: &str, object: &ObjectRef) -> Result<bool, AuthzError> {
479        Ok(self.explain_subject(subject, relation, object)?.is_some())
480    }
481
482    /// [`explain`](Self::explain) with parsed parts.
483    pub fn explain_subject(&self, subject: &Subject, relation: &str, object: &ObjectRef) -> Result<Option<Vec<String>>, AuthzError> {
484        if !self.schema.has_relation(&object.kind, relation) {
485            return Err(AuthzError::Schema(format!("{}#{relation} isn't defined", object.kind)));
486        }
487        Walk::new(self).run(subject, object.clone(), relation.to_string())
488    }
489}
490
491/// One check: an explicit stack, a visited set and budgets — no recursion,
492/// so a deep or cyclic tuple graph can't overflow the stack or loop.
493struct Walk<'a> {
494    authz: &'a Authorizer,
495    reads: usize,
496    /// `(object, relation, parent index)`; the stack holds indexes into it.
497    nodes: Vec<(ObjectRef, String, Option<usize>, usize)>,
498}
499
500impl<'a> Walk<'a> {
501    fn new(authz: &'a Authorizer) -> Walk<'a> {
502        Walk { authz, reads: 0, nodes: Vec::new() }
503    }
504
505    fn read(&mut self, object: &ObjectRef, relation: &str) -> Result<Vec<Subject>, AuthzError> {
506        self.reads += 1;
507        if self.reads > MAX_READS {
508            return Err(AuthzError::Limit(format!("more than {MAX_READS} tuple reads")));
509        }
510        self.authz.store.read(object, relation)
511    }
512
513    fn path(&self, mut at: usize, last: String) -> Vec<String> {
514        let mut steps = vec![last];
515        loop {
516            let (object, relation, parent, _) = &self.nodes[at];
517            steps.push(format!("{object}#{relation}"));
518            match parent {
519                Some(p) => at = *p,
520                None => break,
521            }
522        }
523        steps.reverse();
524        steps
525    }
526
527    fn run(mut self, subject: &Subject, object: ObjectRef, relation: String) -> Result<Option<Vec<String>>, AuthzError> {
528        let mut visited: HashSet<(ObjectRef, String)> = HashSet::new();
529        self.nodes.push((object, relation, None, 0));
530        let mut stack = vec![0usize];
531        while let Some(at) = stack.pop() {
532            let (object, relation, _, depth) = self.nodes[at].clone();
533            if !visited.insert((object.clone(), relation.clone())) {
534                continue;
535            }
536            // The goal itself: asking about `group:eng#member` for `group:eng#member`.
537            if subject.relation.as_deref() == Some(relation.as_str()) && subject.object == object {
538                return Ok(Some(self.path(at, format!("{subject}"))));
539            }
540            if depth >= MAX_DEPTH {
541                return Err(AuthzError::Limit(format!("deeper than {MAX_DEPTH} relations")));
542            }
543            let Some(terms) = self.authz.schema.terms(&object.kind, &relation) else { continue };
544            let terms = terms.to_vec();
545            let mut direct_read = false;
546            for term in &terms {
547                match term {
548                    Term::Direct(_) | Term::DirectSet(..) => {
549                        if direct_read {
550                            continue;
551                        }
552                        direct_read = true;
553                        for found in self.read(&object, &relation)? {
554                            if found == *subject {
555                                return Ok(Some(self.path(at, format!("{object}#{relation}@{found}"))));
556                            }
557                            if let Some(set_relation) = &found.relation {
558                                self.nodes.push((found.object.clone(), set_relation.clone(), Some(at), depth + 1));
559                                stack.push(self.nodes.len() - 1);
560                            }
561                        }
562                    }
563                    Term::Computed(other) => {
564                        self.nodes.push((object.clone(), other.clone(), Some(at), depth + 1));
565                        stack.push(self.nodes.len() - 1);
566                    }
567                    Term::TupleToUserset { tupleset, computed } => {
568                        for found in self.read(&object, tupleset)? {
569                            if found.relation.is_none() {
570                                self.nodes.push((found.object.clone(), computed.clone(), Some(at), depth + 1));
571                                stack.push(self.nodes.len() - 1);
572                            }
573                        }
574                    }
575                }
576            }
577        }
578        Ok(None)
579    }
580}
581
582#[cfg(test)]
583mod tests;