// One error type for decoding, carrying where it happened.
//
// The path is what makes a decode failure actionable. A response from
// `getPostThread` is a tree hundreds of nodes deep, and "expected string, got
// integer" without a location is a bug report nobody can act on;
// "thread.post.author.handle" is one somebody can.
//
// It is a plain string rather than a structured list of segments because it is
// only ever read by a person. Building it costs a concatenation per level,
// which is paid only on the failing path -- the helpers in `fields.mbt` pass
// the prefix down and never build a path for a field that decodes.

///|
pub(all) suberror DecodeError {
  DecodeError(path~ : String, reason~ : String)
} derive(Eq, Debug)

///|
/// Dotted for fields, bracketed for indices: `feed[0].post.author.did`. Empty
/// at the root, where the path prefix is dropped rather than shown as a leading
/// dot.
pub fn field_path(prefix : String, field : String) -> String {
  if prefix == "" {
    field
  } else {
    prefix + "." + field
  }
}

///|
pub fn index_path(prefix : String, index : Int) -> String {
  prefix + "[" + index.to_string() + "]"
}

///|
pub fn DecodeError::path(self : Self) -> String {
  match self {
    DecodeError(path~, ..) => path
  }
}

///|
pub fn DecodeError::reason(self : Self) -> String {
  match self {
    DecodeError(reason~, ..) => reason
  }
}

///|
pub fn DecodeError::describe_error(self : Self) -> String {
  match self {
    DecodeError(path~, reason~) =>
      if path == "" {
        reason
      } else {
        "\{path}: \{reason}"
      }
  }
}

///|
pub impl Show for DecodeError with fn output(self, logger) {
  logger.write_string(self.describe_error())
}