///|
/// A location within the data being serialized or deserialized.
///
/// Deserializers thread a `Path` through nested values so that a failure deep
/// inside a document reports *where* it happened, not just what happened.
/// Rendered as a JSONPath-like string: `$.users[0].name`.
pub(all) enum Path {
  Root
  Key(Path, String)
  Index(Path, Int)
} derive(Eq, Debug)

///|
/// Extends the path with a struct field or map key.
pub fn Path::key(self : Path, name : String) -> Path {
  Key(self, name)
}

///|
/// Extends the path with a sequence index.
pub fn Path::index(self : Path, i : Int) -> Path {
  Index(self, i)
}

///|
pub impl Show for Path with fn output(self, logger) {
  match self {
    Root => logger.write_string("$")
    Key(parent, name) => {
      Show::output(parent, logger)
      logger.write_string(".")
      logger.write_string(name)
    }
    Index(parent, i) => {
      Show::output(parent, logger)
      logger.write_string("[")
      logger.write_string(i.to_string())
      logger.write_string("]")
    }
  }
}

///|
/// Failure raised by a `Serializer`.
///
/// Serialization fails far less often than deserialization: the value is
/// already well-typed, so the only real failures are a format that cannot
/// represent part of the data model, or an I/O-style failure in the sink.
pub(all) suberror SerError {
  /// The format cannot represent this part of the data model, e.g. JSON has
  /// no way to write a map with non-string keys.
  UnsupportedType(format~ : String, kind~ : String)
  /// Anything format-specific.
  SerCustom(message~ : String)
}

///|
pub impl Show for SerError with fn output(self, logger) {
  match self {
    UnsupportedType(format~, kind~) =>
      logger.write_string("\{format} cannot serialize \{kind}")
    SerCustom(message~) => logger.write_string(message)
  }
}

///|
/// Failure raised by a `Deserializer`.
///
/// Every variant carries the `Path` at which the failure occurred. The variants
/// mirror serde's `de::Error` constructors so that formats report errors
/// uniformly rather than each inventing its own message strings.
pub(all) suberror DeError {
  /// Found a value of the wrong shape, e.g. a string where an int was expected.
  InvalidType(path~ : Path, expected~ : String, found~ : String)
  /// A value of the right shape but an unacceptable value, e.g. an integer
  /// that does not fit the target width.
  InvalidValue(path~ : Path, expected~ : String, found~ : String)
  /// A sequence or tuple had the wrong number of elements.
  InvalidLength(path~ : Path, expected~ : Int, found~ : Int)
  /// A struct field was absent and had no default.
  MissingField(path~ : Path, field~ : String)
  /// A struct field was present that the target type does not know about.
  UnknownField(path~ : Path, field~ : String, expected~ : Array[String])
  /// An enum variant name that the target type does not know about.
  UnknownVariant(path~ : Path, variant~ : String, expected~ : Array[String])
  /// Input ended in the middle of a value.
  Eof(path~ : Path)
  /// Anything format-specific.
  DeCustom(path~ : Path, message~ : String)
}

///|
/// The path at which this error occurred.
pub fn DeError::path(self : DeError) -> Path {
  match self {
    InvalidType(path~, ..)
    | InvalidValue(path~, ..)
    | InvalidLength(path~, ..)
    | MissingField(path~, ..)
    | UnknownField(path~, ..)
    | UnknownVariant(path~, ..)
    | Eof(path~)
    | DeCustom(path~, ..) => path
  }
}

///|
fn one_of(names : Array[String]) -> String {
  if names.is_empty() {
    "there are no valid values"
  } else {
    "expected one of " + names.join(", ")
  }
}

///|
pub impl Show for DeError with fn output(self, logger) {
  let body = match self {
    InvalidType(expected~, found~, ..) =>
      "invalid type: expected \{expected}, found \{found}"
    InvalidValue(expected~, found~, ..) =>
      "invalid value: expected \{expected}, found \{found}"
    InvalidLength(expected~, found~, ..) =>
      "invalid length: expected \{expected} elements, found \{found}"
    MissingField(field~, ..) => "missing field `\{field}`"
    UnknownField(field~, expected~, ..) =>
      "unknown field `\{field}`, \{one_of(expected)}"
    UnknownVariant(variant~, expected~, ..) =>
      "unknown variant `\{variant}`, \{one_of(expected)}"
    Eof(..) => "unexpected end of input"
    DeCustom(message~, ..) => message
  }
  logger.write_string("\{self.path()}: \{body}")
}