cddl-cat 0.4.0

Parse CDDL schemas and validate CBOR or JSON serialized data
Documentation
//! This module contains code to validate serialized data.
//!
//! More precisely, it validates data that can be represented by [`Value`] trees.

use crate::context::LookupContext;
use crate::ivt::*;
use crate::util::{mismatch, ValidateError, ValidateResult};
use crate::value::Value;
use std::collections::BTreeMap; // used in Value::Map
use std::collections::HashMap;
use std::collections::VecDeque;
use std::mem::discriminant;

// A map from generic parameter name to the type being used here.
#[derive(Clone, Debug)]
struct GenericMap<'a> {
    map: HashMap<String, &'a Node>,
    // Because we captured this map at a previous time, we may need to look up
    // generic types from that previous context.  We carry a copy of that
    // Context with us to do those lookups.
    past_ctx: Option<&'a Context<'a>>,
}

// At the top level of a validation, we won't have any past_ctx
// because there cannot be any generic parameter map.
impl Default for GenericMap<'_> {
    fn default() -> Self {
        Self {
            map: HashMap::default(),
            past_ctx: None,
        }
    }
}

#[derive(Clone)]
struct Context<'a> {
    lookup: &'a dyn LookupContext,
    generic_map: GenericMap<'a>,
}

impl std::fmt::Debug for Context<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Context")
            .field("generic_map", &self.generic_map)
            .finish()
    }
}

struct NodeContext<'a> {
    node: &'a Node,
    ctx: Context<'a>,
}

impl<'a> Context<'a> {
    // `lookup_rule` will first do a search for a local generic parameter name
    // first, followed by a lookup for a rule by that name.

    // To give an example:
    //     IP = [u8, u8, u8, u8]
    //     PORT = u16
    //     socket = [IP, PORT]
    //     conn<IP> = [name, socket, IP] # This is the generic parm IP not the rule IP.
    //
    // While validating "conn" we have a Context with a generic mapping for "IP".
    // As soon as we traverse into "socket", we need for there to be a new context,
    // where no "IP" exists (so we'll do the rule lookup instead).
    //
    fn lookup_rule(&'a self, rule: &'a Rule) -> TempResult<NodeContext<'a>> {
        // First, check to see if the "rule name" is actually a generic parameter.
        // TODO: this would be a lot easier if this were pre-processed by the flattener
        // so that "rule lookup" and "generic type lookup" were two separate Node variants.
        let generic_node: Option<&'a Node> = self.generic_map.map.get(&rule.name).cloned();
        if let Some(node) = generic_node {
            // If we stored a past_ctx along with the generic args, then pass that
            // context along with the node that is substituting for this generic
            // parameter name.  Otherwise, return a new blank Context.
            let ctx = self
                .generic_map
                .past_ctx
                .cloned()
                .unwrap_or_else(|| self.blank());
            return Ok(NodeContext { node, ctx });
        }

        let rule_def: &RuleDef = self.lookup.lookup_rule(&rule.name)?;

        // Create a new context containing a new generic parameter map.
        let ctx = self.derive(rule_def, rule)?;

        Ok(NodeContext {
            node: &rule_def.node,
            ctx,
        })
    }

    // Create a new blank Context (with no parameter map)
    fn blank(&self) -> Context<'a> {
        Context {
            lookup: self.lookup,
            generic_map: GenericMap::default(),
        }
    }

    // Create a new Context, with optional generic parameter map.
    fn derive(&'a self, rule_def: &'a RuleDef, rule: &'a Rule) -> TempResult<Context<'a>> {
        let parms_len = rule_def.generic_parms.len();
        let args_len = rule.generic_args.len();
        if parms_len != args_len {
            // Wrong number of generic arguments
            return Err(ValidateError::GenericError);
        }

        // Zip the two Vecs:
        // 1. rule_def.generic_parms
        // 2. rule.generic_args
        // and put them into a HashMap so we can do lookups from parm -> arg.
        let map: HashMap<String, &'a Node> = rule_def
            .generic_parms
            .iter()
            .cloned()
            .zip(rule.generic_args.iter())
            .collect();

        let generic_map = GenericMap {
            map,
            past_ctx: Some(self),
        };

        Ok(Context {
            lookup: self.lookup,
            generic_map,
        })
    }
}

pub(crate) fn do_validate(
    value: &Value,
    rule_def: &RuleDef,
    ctx: &dyn LookupContext,
) -> ValidateResult {
    // If the rule_def passed in requires generic parameters, we should
    // return an error, because we don't have any way to specify them.
    if !rule_def.generic_parms.is_empty() {
        return Err(ValidateError::GenericError);
    }

    let ctx = Context {
        lookup: ctx,
        generic_map: GenericMap::default(),
    };
    let node = &rule_def.node;
    validate(value, node, &ctx)
}

type ValueMap = BTreeMap<Value, Value>;

// A Result that returns some temporary value.
type TempResult<T> = Result<T, ValidateError>;

/// This struct allows us to maintain a map that is consumed during validation.
struct WorkingMap {
    map: ValueMap,
    // A stack of lists; each list contains maybe-discarded elements,
    // in (key, value) form.
    snaps: VecDeque<VecDeque<(Value, Value)>>,
}

impl WorkingMap {
    /// Makes a copy of an existing map's table.
    fn new(value_map: &ValueMap) -> WorkingMap {
        WorkingMap {
            map: value_map.clone(),
            snaps: VecDeque::new(),
        }
    }

    // When we start speculatively matching map elements (e.g. in a Choice
    // or Occur containing groups), we may fail the match partway through, and
    // need to rewind to the most recent snapshot.
    //
    // It's possible for nested snapshots to exist; for example if we have a
    // group-of-choices nested inside a group-of-choices.
    //
    // If one array is nested inside another, the inner array will get its
    // own WorkingArray so snapshots aren't necessary in that case.
    fn snapshot(&mut self) {
        self.snaps.push_back(VecDeque::new());
    }

    // Restore the map to the point when we last called snapshot()
    fn rewind(&mut self) {
        // If validate code is implemented correctly, then unwrap() should
        // never panic.
        let mut top_snap = self.snaps.pop_back().unwrap();
        // Drain the elements (order not important), and insert them back into
        // the working map.
        self.map.extend(top_snap.drain(..));
    }

    // We completed a match, so we can retire the most recent snapshot.
    fn commit(&mut self) {
        // If validate code is implemented correctly, then unwrap() should
        // never panic.
        // This throws away the list that was popped; those values were
        // successfully matched and are no longer needed.
        self.snaps.pop_back().unwrap();
    }

    // Peek at the value correspending to a given key (if any)
    fn peek_at(&self, key: &Value) -> Option<&Value> {
        self.map.get(key)
    }

    // Remove a value from the working map.
    // If there is an active snapshot, stash the key/value pair there until
    // we're certain we've matched the entire group.
    fn remove(&mut self, key: &Value) {
        // If validate code is implemented correctly, then unwrap() should
        // never panic (we've already peeked at this value in order to match
        // it.)
        let value = self.map.remove(&key).unwrap();
        // If there is a current snapshot, preserve this element
        // for later rewind.
        if let Some(snap) = self.snaps.back_mut() {
            snap.push_back((key.clone(), value));
        }
    }
}

/// This struct allows us to maintain a copy of an array that is consumed
/// during validation.
#[derive(Debug)]
struct WorkingArray {
    // The elements in the Value Array
    array: VecDeque<Value>,
    // A stack of lists; each list contains maybe-discarded elements.
    snaps: VecDeque<VecDeque<Value>>,
}

impl WorkingArray {
    /// Makes a copy of an existing map's table.
    fn new(array: &[Value]) -> WorkingArray {
        let deque: VecDeque<Value> = array.iter().cloned().collect();
        WorkingArray {
            array: deque,
            snaps: VecDeque::new(),
        }
    }

    // When we start speculatively matching array elements (e.g. in a Choice
    // or Occur containing groups), we may fail the match partway through, and
    // need to rewind to the most recent snapshot.
    //
    // It's possible for nested snapshots to exist; for example if we have a
    // group-of-choices nested inside a group-of-choices.
    //
    // If one array is nested inside another, the inner array will get its
    // own WorkingArray so snapshots aren't necessary in that case.
    fn snapshot(&mut self) {
        let new_snap: VecDeque<Value> = VecDeque::new();
        self.snaps.push_back(new_snap);
    }

    // Restore the array to the point when we last called snapshot()
    fn rewind(&mut self) {
        // If validate code is implemented correctly, then unwrap() should
        // never panic.
        let mut top_snap = self.snaps.pop_back().unwrap();
        // drain the elements in LIFO order, and push them back into
        // the working array.
        for element in top_snap.drain(..).rev() {
            self.array.push_front(element);
        }
    }

    // We completed a match, so we can retire the most recent snapshot.
    fn commit(&mut self) {
        // If validate code is implemented correctly, then unwrap() should
        // never panic.
        // This throws away the list that was popped; those values were
        // successfully matched and are no longer needed.
        self.snaps.pop_back().unwrap();
    }

    // Peek at the front of the working array.
    fn peek_front(&self) -> Option<&Value> {
        self.array.front()
    }

    // Remove an element from the working array.
    // If there is an active snapshot, stash the element there until we're
    // certain we've matched the entire group.
    fn pop_front(&mut self) {
        // If validate code is implemented correctly, then unwrap() should
        // never panic (we've already peeked at this value in order to match
        // it.)
        let element = self.array.pop_front().unwrap();
        // If there is a current snapshot, preserve this element
        // for later rewind.
        if let Some(snap) = self.snaps.back_mut() {
            snap.push_back(element);
        }
    }
}

// Prevent warnings if both serde_cbor and serde_json are disabled.
#[allow(dead_code)]

// This is the main validation dispatch function.
// It tries to match a Node and a Value, recursing as needed.
fn validate(value: &Value, node: &Node, ctx: &Context) -> ValidateResult {
    match node {
        Node::Literal(l) => validate_literal(l, value),
        Node::PreludeType(p) => validate_prelude_type(*p, value),
        Node::Choice(c) => validate_choice(c, value, ctx),
        Node::Map(m) => validate_map(m, value, ctx),
        Node::Array(a) => validate_array(a, value, ctx),
        Node::Rule(r) => validate_rule(r, value, ctx),
        Node::Group(g) => validate_standalone_group(g, value, ctx),
        Node::KeyValue(_) => Err(ValidateError::Structural("unexpected KeyValue".into())),
        Node::Occur(_) => Err(ValidateError::Structural("unexpected Occur".into())),
        Node::Unwrap(_) => Err(ValidateError::Structural("unexpected Unwrap".into())),
        Node::Range(r) => validate_range(r, value, ctx),
    }
}

// Perform map key search.
// Some keys (literal values) can be found with a fast search, while
// others may require a linear search.
fn validate_map_key<'a>(
    value_map: &'a mut WorkingMap,
    node: &Node,
    ctx: &Context,
) -> TempResult<(Value, &'a Value)> {
    match node {
        Node::Literal(l) => map_search_literal(l, value_map),
        _ => map_search(node, value_map, ctx),
    }
}

/// Validate a `Choice` containing an arbitrary number of "option" nodes.
///
/// If any of the options matches, this validation is successful.
fn validate_choice(choice: &Choice, value: &Value, ctx: &Context) -> ValidateResult {
    for node in &choice.options {
        match validate(value, node, ctx) {
            Ok(()) => {
                return Ok(());
            }
            Err(e) => {
                // Only fail if the error is considered fatal.
                // Otherwise, we'll keep trying other options.
                if e.is_fatal() {
                    return Err(e);
                }
            }
        }
    }
    let expected = format!("choice of {}", choice.options.len());
    Err(mismatch(expected))
}

/// Validate a `Rule` reference
///
/// Seek out the right `Node` and `Context`, and recurse.
fn validate_rule(rule: &Rule, value: &Value, ctx: &Context) -> ValidateResult {
    let answer = ctx.lookup_rule(&rule)?;
    validate(value, &answer.node, &answer.ctx)
}

/// Create a `Value` from a `Literal`.
impl From<&Literal> for Value {
    fn from(l: &Literal) -> Value {
        match l {
            Literal::Bool(b) => Value::Bool(*b),
            Literal::Int(i) => Value::Integer(*i),
            Literal::Float(f) => Value::from_float(*f),
            Literal::Text(t) => Value::Text(t.clone()),
            Literal::Bytes(b) => Value::Bytes(b.clone()),
        }
    }
}

fn validate_literal(literal: &Literal, value: &Value) -> ValidateResult {
    if *value == Value::from(literal) {
        return Ok(());
    }
    Err(mismatch(format!("{}", literal)))
}

// Find a specific key in the map and return that key plus a reference to its value.
fn map_search_literal<'a>(
    literal: &Literal,
    working_map: &'a mut WorkingMap,
) -> TempResult<(Value, &'a Value)> {
    let search_key = Value::from(literal);
    match working_map.peek_at(&search_key) {
        Some(val) => Ok((search_key, val)),
        None => {
            // We didn't find the key; return an error
            Err(mismatch(format!("map{{{}}}", literal)))
        }
    }
}

// Iterate over each key in the working map, looking for a match.
// If we find a match, return a copy of the key, and a reference to the value.
// This is less efficient than map_search_literal.
fn map_search<'a>(
    node: &Node,
    working_map: &'a mut WorkingMap,
    ctx: &Context,
) -> TempResult<(Value, &'a Value)> {
    for (key, value) in working_map.map.iter() {
        let attempt = validate(key, node, ctx);
        if attempt.is_ok() {
            return Ok((key.clone(), value));
        }
    }
    // We searched all the keys without finding a match.  Validation fails.
    Err(mismatch(format!("map{{{}}}", node)))
}

// Note `ty` is passed by value because clippy says it's only 1 byte.
fn validate_prelude_type(ty: PreludeType, value: &Value) -> ValidateResult {
    match (ty, value) {
        (PreludeType::Any, _) => Ok(()),
        (PreludeType::Nil, Value::Null) => Ok(()),
        (PreludeType::Nil, _) => Err(mismatch("nil")),
        (PreludeType::Bool, Value::Bool(_)) => Ok(()),
        (PreludeType::Bool, _) => Err(mismatch("bool")),
        (PreludeType::Int, Value::Integer(_)) => Ok(()),
        (PreludeType::Int, _) => Err(mismatch("int")),
        (PreludeType::Uint, Value::Integer(x)) if *x >= 0 => Ok(()),
        (PreludeType::Uint, _) => Err(mismatch("uint")),
        (PreludeType::Nint, Value::Integer(x)) if *x < 0 => Ok(()),
        (PreludeType::Nint, _) => Err(mismatch("nint")),
        (PreludeType::Float, Value::Float(_)) => Ok(()),
        (PreludeType::Float, _) => Err(mismatch("float")),
        (PreludeType::Tstr, Value::Text(_)) => Ok(()),
        (PreludeType::Tstr, _) => Err(mismatch("tstr")),
        (PreludeType::Bstr, Value::Bytes(_)) => Ok(()),
        (PreludeType::Bstr, _) => Err(mismatch("bstr")),
    }
}

// FIXME: should this be combined with Map handling?
fn validate_array(ar: &Array, value: &Value, ctx: &Context) -> ValidateResult {
    match value {
        Value::Array(a) => validate_array_part2(ar, a, ctx),
        _ => Err(mismatch("array")),
    }
}

fn validate_array_part2(ar: &Array, value_array: &[Value], ctx: &Context) -> ValidateResult {
    // Strategy for validating an array:
    // 1. We assume that the code that constructed the IVT Array placed the
    //    members in matching order (literals first, more general types at the
    //    end) so that we consume IVT Array members in order without worrying
    //    about non-deterministic results.
    // 2. Make a mutable working copy of the Value::Array contents
    // 3. Iterate over the IVT Array, searching the working copy for a
    //    matching key.
    // 4. If a match is found, remove the value from our working copy.
    // 6. If the IVT member can consume multiple values, repeat the search for
    //    this key.
    // 7. If a match is not found and the member is optional (or we've already
    //    consumed an acceptable number of keys), continue to the next IVT
    //    member.
    // 8. If the member is not found and we haven't consumed the expected
    //    number of values, return an error.

    let mut working_array = WorkingArray::new(value_array);

    for member in &ar.members {
        validate_array_member(member, &mut working_array, ctx)?;
    }
    if working_array.array.is_empty() {
        Ok(())
    } else {
        // If the working map isn't empty, that means we had some extra values
        // that didn't match anything.
        // FIXME: Should this be a unique error type?
        Err(mismatch("shorter array"))
    }
}

fn validate_array_member(
    member: &Node,
    working_array: &mut WorkingArray,
    ctx: &Context,
) -> ValidateResult {
    match member {
        // FIXME: does it make sense for this to destructure & dispatch
        // each Node type here?  Is there any way to make this generic?
        Node::Occur(o) => validate_array_occur(o, working_array, ctx),
        Node::KeyValue(kv) => {
            // The key is ignored.  Validate the value only.
            // FIXME: should we try to use the key to provide a more
            // useful error message?
            validate_array_value(&kv.value, working_array, ctx)
        }
        Node::Rule(r) => {
            // FIXME: This seems like a gross hack.  We need to dereference
            // the rule here, because if we drop to the bottom and call
            // validate_array_value() then we lose our ability to "see
            // through" KeyValue and Group nodes while remembering that we are
            // in an array context (with a particular working copy).
            // BUG: Choice nodes will have the same problem.

            let answer = ctx.lookup_rule(r)?;
            validate_array_member(&answer.node, working_array, &answer.ctx)
        }
        Node::Unwrap(r) => {
            // Like Rule, we are dereferencing the Rule by hand here so that
            // we can "see through" to the underlying data without forgetting
            // we were in an array context.
            let answer = ctx.lookup_rule(r)?;
            validate_array_unwrap(&answer.node, working_array, &answer.ctx)
        }
        Node::Choice(c) => {
            // We need to explore each of the possible choices.
            // We can't use validate_array_value() because we'll lose our
            // array context.
            for option in &c.options {
                match validate_array_member(option, working_array, ctx) {
                    Ok(()) => {
                        return Ok(());
                    }
                    Err(e) => {
                        // Only fail if the error is considered fatal.
                        // Otherwise, we'll keep trying other options.
                        if e.is_fatal() {
                            return Err(e);
                        }
                    }
                }
            }
            // None of the choices worked.
            let expected = format!("choice of {}", c.options.len());
            Err(mismatch(expected))
        }
        Node::Group(g) => {
            // As we call validate_array_member, we don't know how many items
            // it might speculatively pop from the list.  So we'll take a snapshot
            // now and commit our changes if we match successfully (and roll them
            // back if it fails).
            working_array.snapshot();

            // Recurse into each member of the group.
            for group_member in &g.members {
                match validate_array_member(group_member, working_array, ctx) {
                    Ok(_) => {
                        // So far so good...
                    }
                    Err(e) => {
                        // Since we failed to validate the entire group, rewind to our most
                        // recent snapshot.  This may put values back into the array,
                        // so they can be matched by whatever we try next (or trigger
                        // an error if they aren't consumed by anything).
                        working_array.rewind();
                        return Err(e);
                    }
                }
            }
            // All group members validated Ok.
            working_array.commit();
            Ok(())
        }
        m => validate_array_value(m, working_array, ctx),
    }
}

fn validate_array_unwrap(
    node: &Node,
    working_array: &mut WorkingArray,
    ctx: &Context,
) -> ValidateResult {
    // After traversing an unwrap from inside an array, the next node must be an
    // Array node too.
    match node {
        Node::Array(a) => {
            // Recurse into each member of the unwrapped array.
            for member in &a.members {
                validate_array_member(member, working_array, ctx)?;
            }
            // All array members validated Ok.
            Ok(())
        }
        _ => Err(mismatch("unwrap array")),
    }
}

/// Validate an occurrence against a mutable working array.
// FIXME: this is pretty similar to validate_map_occur; maybe they can be combined?
fn validate_array_occur(
    occur: &Occur,
    working_array: &mut WorkingArray,
    ctx: &Context,
) -> ValidateResult {
    let (lower_limit, upper_limit) = occur.limits();
    let mut count: usize = 0;

    loop {
        match validate_array_member(&occur.node, working_array, ctx) {
            Ok(_) => (),
            Err(e) => {
                if e.is_mismatch() {
                    // Stop trying to match this occurrence.
                    break;
                }
                // The error is something serious (e.g. MissingRule or
                // Unsupported).  We should fail now and propagate that
                // error upward.
                return Err(e);
            }
        }
        count += 1;
        if count >= upper_limit {
            // Stop matching; we've consumed the maximum number of this key.
            break;
        }
    }
    if count < lower_limit {
        return Err(mismatch(format!("more array element [{}]", occur)));
    }
    Ok(())
}

/// Validate some node against a mutable working array.
fn validate_array_value(
    node: &Node,
    working_array: &mut WorkingArray,
    ctx: &Context,
) -> ValidateResult {
    match working_array.peek_front() {
        Some(val) => {
            validate(val, node, ctx)?;
            // We had a successful match; remove the matched value.
            working_array.pop_front();
            Ok(())
        }
        None => Err(mismatch(format!("array element {}", node))),
    }
}

fn validate_map(m: &Map, value: &Value, ctx: &Context) -> ValidateResult {
    match value {
        Value::Map(vm) => validate_map_part2(m, vm, ctx),
        _ => Err(mismatch("map")),
    }
}

fn validate_map_part2(m: &Map, value_map: &ValueMap, ctx: &Context) -> ValidateResult {
    // Strategy for validating a map:
    // 1. We assume that the code that constructed the IVT Map placed the keys
    //    in matching order (literals first, more general types at the end) so
    //    that we consume IVT Map keys in order without worrying about non-
    //    deterministic results.
    // 2. Make a mutable working copy of the Value::Map contents
    // 3. Iterate over the IVT Map, searching the Value::Map for a matching key.
    // 4. If a match is found, remove the key-value pair from our working copy.
    // 5. Validate the key's corresponding value.
    // 6. If the key can consume multiple values, repeat the search for this key.
    // 7. If the key is not found and the key is optional (or we've already consumed
    //    an acceptable number of keys), continue to the next key.
    // 8. If the key is not found and we haven't consumed the expected number of
    //    keys, return an error.

    let mut working_map = WorkingMap::new(value_map);

    for member in &m.members {
        validate_map_member(member, &mut working_map, ctx).map_err(|e| {
            // If a MapCut error pops out here, change it to a Mismatch, so that
            // it can't cause trouble in nested maps.
            e.erase_mapcut()
        })?;
    }
    if working_map.map.is_empty() {
        Ok(())
    } else {
        // If the working map isn't empty, that means we had some extra values
        // that didn't match anything.
        Err(mismatch("shorter map"))
    }
}

fn validate_map_member(
    member: &Node,
    working_map: &mut WorkingMap,
    ctx: &Context,
) -> ValidateResult {
    match member {
        // FIXME: does it make sense for this to destructure & dispatch
        // each Node type here?  Is there any way to make this generic?
        Node::Occur(o) => validate_map_occur(o, working_map, ctx),
        Node::KeyValue(kv) => validate_map_keyvalue(kv, working_map, ctx),
        Node::Rule(r) => {
            // We can't use the generic validate() here; we would forget that
            // we were in a map context.  We need to punch down a level into
            // the rule and match again.
            let answer = ctx.lookup_rule(r)?;
            validate_map_member(&answer.node, working_map, &answer.ctx)
        }
        Node::Unwrap(r) => {
            // Like Rule, we are dereferencing the Rule by hand here so that
            // we can "see through" to the underlying data without forgetting
            // we were in a map context.
            let answer = ctx.lookup_rule(r)?;
            validate_map_unwrap(&answer.node, working_map, &answer.ctx)
        }
        Node::Group(g) => {
            // As we call validate_array_member, we don't know how many items
            // it might speculatively pop from the list.  So we'll take a
            // snapshot now and commit our changes if we match successfully
            // (and roll them back if it fails).
            working_map.snapshot();

            // Recurse into each member of the group.
            for group_member in &g.members {
                match validate_map_member(group_member, working_map, ctx) {
                    Ok(_) => {
                        // So far so good...
                    }
                    Err(e) => {
                        // Since we failed to validate the entire group,
                        // rewind to our most recent snapshot.  This may put
                        // values back into the map, so they can be matched by
                        // whatever we try next (or trigger an error if they
                        // aren't consumed by anything).
                        working_map.rewind();

                        // Also forget any MapCut errors, so that a sibling
                        // group may succeed where we failed.
                        return Err(e.erase_mapcut());
                    }
                }
            }
            // All group members validated Ok.
            working_map.commit();
            Ok(())
        }
        Node::Choice(c) => {
            // We need to explore each of the possible choices.
            for option in &c.options {
                match validate_map_member(option, working_map, ctx) {
                    Ok(()) => {
                        return Ok(());
                    }
                    Err(e) => {
                        // We can keep trying other options as long as the
                        // error is a Mismatch, not a MapCut or something
                        // fatal.
                        if !e.is_mismatch() {
                            return Err(e);
                        }
                    }
                }
            }
            // None of the choices worked.
            let expected = format!("choice of {}", c.options.len());
            Err(mismatch(expected))
        }
        // I don't think any of these are possible using CDDL grammar.
        Node::Literal(_) => Err(ValidateError::Structural("literal map member".into())),
        Node::PreludeType(_) => Err(ValidateError::Structural("prelude type map member".into())),
        Node::Map(_) => Err(ValidateError::Structural("map as map member".into())),
        Node::Array(_) => Err(ValidateError::Structural("array as map member".into())),
        Node::Range(_) => Err(ValidateError::Structural("range as map member".into())),
    }
}

fn validate_map_unwrap(node: &Node, working_map: &mut WorkingMap, ctx: &Context) -> ValidateResult {
    // After traversing an unwrap from inside a map, the next node must be a
    // Map node too.
    match node {
        Node::Map(m) => {
            // Recurse into each member of the unwrapped array.
            for member in &m.members {
                validate_map_member(member, working_map, ctx)?;
            }
            // All array members validated Ok.
            Ok(())
        }
        _ => Err(mismatch("unwrap map")),
    }
}

/// Validate an occurrence against a mutable working map.
fn validate_map_occur(
    occur: &Occur,
    working_map: &mut WorkingMap,
    ctx: &Context,
) -> ValidateResult {
    let (lower_limit, upper_limit) = occur.limits();
    let mut count: usize = 0;

    loop {
        match validate_map_member(&occur.node, working_map, ctx) {
            Ok(_) => (),
            Err(e) => {
                if e.is_mismatch() {
                    // Stop trying to match this occurrence.
                    break;
                }
                // Either we got a MapCut error, or it's something even more
                // serious (e.g. MissingRule or Unsupported).  We should fail
                // now and propagate that error upward.
                return Err(e);
            }
        }
        count += 1;
        if count >= upper_limit {
            // Stop matching; we've consumed the maximum number of this key.
            break;
        }
    }
    if count < lower_limit {
        // Read this format string as "{{" then "{}" then "}}"
        // The first and last print a single brace; the value is in the
        // middle, e.g "{foo}".
        return Err(mismatch(format!("map{{{}}}]", occur)));
    }
    Ok(())
}

/// Validate a key-value pair against a mutable working map.
fn validate_map_keyvalue(
    kv: &KeyValue,
    working_map: &mut WorkingMap,
    ctx: &Context,
) -> ValidateResult {
    // CDDL syntax reminder:
    //   a => b   ; non-cut
    //   a ^ => b ; cut
    //   a: b     ; cut
    //
    // If we're using "cut" semantics, a partial match (key matches + value
    // mismatch) should force validation failure for the entire map.  We
    // signal this to our caller with a MapCut error.
    // If we're using "non-cut" semantics, a partial match will leave the
    // key-value pair in place, in the hope it may match something else.

    let key_node = &kv.key;
    let val_node = &kv.value;
    let cut = kv.cut;

    // If we fail to validate a key, exit now with an error.
    let (working_key, working_val) = validate_map_key(working_map, key_node, ctx)?;

    // Match the value that was returned.
    match validate(working_val, val_node, ctx) {
        Ok(()) => {
            working_map.remove(&working_key);
            Ok(())
        }
        Err(e) => {
            match (cut, e) {
                (true, ValidateError::Mismatch(m)) => {
                    // If "cut" semantics are in force, then rewrite Mismatch errors.
                    // This allows special handling when nested inside Occur nodes.
                    Err(ValidateError::MapCut(m))
                }
                (_, x) => Err(x),
            }
        }
    }
}

fn validate_standalone_group(g: &Group, value: &Value, ctx: &Context) -> ValidateResult {
    // Since we're not in an array or map context, it's not clear how we should
    // validate a group containing multiple elements.  If we see one, return an
    // error.
    match g.members.len() {
        1 => {
            // Since our group has length 1, validate against that single element.
            validate(value, &g.members[0], ctx)
        }
        _ => Err(ValidateError::Unsupported("standalone group".into())),
    }
}

fn deref_range_rule(node: &Node, ctx: &Context) -> TempResult<Literal> {
    match node {
        Node::Literal(l) => Ok(l.clone()),
        Node::Rule(r) => {
            let answer = ctx.lookup_rule(r)?;
            deref_range_rule(&answer.node, &answer.ctx)
        }
        _ => Err(ValidateError::Structural(
            "confusing type on range operator".into(),
        )),
    }
}

// Returns true if value is within range
fn check_range<T: PartialOrd>(start: T, end: T, value: T, inclusive: bool) -> bool {
    if value < start {
        return false;
    }
    if inclusive {
        value <= end
    } else {
        value < end
    }
}

fn validate_range(range: &Range, value: &Value, ctx: &Context) -> ValidateResult {
    // first dereference rules on start and end, if necessary.
    let start = deref_range_rule(&range.start, ctx)?;
    let end = deref_range_rule(&range.end, ctx)?;

    match (&start, &end, &value) {
        (Literal::Int(i1), Literal::Int(i2), Value::Integer(v)) => {
            if check_range(i1, i2, v, range.inclusive) {
                Ok(())
            } else {
                Err(mismatch(format!("{}", range)))
            }
        }
        (Literal::Float(f1), Literal::Float(f2), Value::Float(v)) => {
            if check_range(f1, f2, &v.0, range.inclusive) {
                Ok(())
            } else {
                Err(mismatch(format!("{}", range)))
            }
        }
        _ => {
            if discriminant(&start) == discriminant(&end) {
                // The range types were the same, so this is just a mismatch.
                Err(mismatch(format!("{}", range)))
            } else {
                // The range types didn't agree; return an error that points the
                // finger at the CDDL instead.
                Err(ValidateError::Structural(
                    "mismatched types on range operator".into(),
                ))
            }
        }
    }
}