///|
fn prepare_unknown_members(
  shape : @smithy.Shape,
  default_ : UnknownMembers,
) -> UnknownMembers raise PreparationError {
  match shape.trait_value("smithy.rpc#unknownMembers") {
    None => default_
    Some(String("ignore")) if shape.body is Structure(_) => Ignore
    Some(String("reject")) if shape.body is Structure(_) => Reject
    _ =>
      raise InvalidModel(
        shape.id,
        "I expected unknownMembers to be reject or ignore on a structure.",
      )
  }
}

///|
/// Set the default policy. A structure trait overrides this policy.
pub fn Config::with_unknown_members(
  self : Config,
  policy : UnknownMembers,
) -> Config {
  { ..self, unknown_members: policy, }
}

///|
/// Get the config used to prepare this validator.
pub fn PreparedValidator::config(self : PreparedValidator) -> Config {
  self.config
}

///|
/// Get the effective policy for a prepared shape or member target.
pub fn PreparedValidator::unknown_members(
  self : PreparedValidator,
  target : @smithy.ShapeId,
) -> UnknownMembers? {
  self.shapes.get(target).map(shape => shape.unknown_members)
}

///|
fn prepare_critical_paths(
  model : @smithy.Model,
  shape : @smithy.Shape,
  body : @smithy.ShapeBody,
) -> String? raise PreparationError {
  guard shape.trait_value("smithy.rpc#criticalPaths") is Some(value) else {
    return None
  }
  guard shape.body is Member(_) &&
    model.shapes.get(shape.id.root()) is Some(parent) &&
    parent.body is Structure(ids) &&
    ids.contains(shape.id) &&
    body is List(element) &&
    model.shapes.get(element) is Some(element_shape) &&
    element_shape.body is Member(target~) &&
    model.shapes.get(target) is Some(target_shape) &&
    target_shape.body is Simple(String) else {
    raise InvalidModel(
      shape.id,
      "I expected criticalPaths on a structure member with a list-of-string target.",
    )
  }
  guard value is Object(fields) &&
    fields.get("separator") is Some(String(separator)) &&
    !separator.is_empty() else {
    raise InvalidModel(
      shape.id,
      "I expected criticalPaths to have a nonempty string separator.",
    )
  }
  Some(separator)
}

///|
fn PreparedValidator::critical_path_known(
  self : PreparedValidator,
  enclosing : @smithy.ShapeId,
  text : String,
  separator : String,
  budget : Budget,
  path : InstancePath,
) -> Bool {
  let mut current = enclosing
  for part in text.split(separator) {
    if !budget.charge(path, enclosing) {
      return true
    }
    guard !part.is_empty() &&
      self.shapes.get(current) is Some(shape) &&
      shape.body is Structure(_) &&
      shape.by_name.get(part.to_owned()) is Some(member_) else {
      return false
    }
    current = member_.id
  }
  true
}

///|
/// Get the separator for a prepared critical-path list member.
pub fn PreparedValidator::critical_paths(
  self : PreparedValidator,
  target : @smithy.ShapeId,
) -> String? {
  self.shapes.get(target).bind(shape => shape.critical_separator)
}