///|
/// Errors from type generation (upstream `typify_impl::Error`).
///
/// `Panic` stands for the places where upstream typify panics (`panic!`,
/// `todo!`, `unimplemented!`, failed `unwrap`s). It is kept distinct so that
/// code which deliberately ignores ordinary errors (upstream's `.ok()`) does
/// not swallow what upstream would treat as a crash.
pub(all) suberror TypifyError {
  BadValue(String, @serde_json.Value)
  InvalidTypeId
  InvalidValue
  InvalidSchema(type_name~ : String?, reason~ : String)
  Panic(String)
} derive(Debug)

///|
pub impl Show for TypifyError with fn output(self, logger) {
  match self {
    BadValue(_, _) => logger.write_string("unexpected value type")
    InvalidTypeId => logger.write_string("invalid TypeId")
    InvalidValue =>
      logger.write_string("value does not conform to the given schema")
    InvalidSchema(type_name~, reason~) => {
      let name = type_name.unwrap_or("")
      logger.write_string("invalid schema for \{name}: \{reason}")
    }
    Panic(msg) => logger.write_string("internal error: \{msg}")
  }
}

///|
/// Upstream `panic!`.
fn panic_with(msg : String) -> TypifyError {
  Panic(msg)
}

///|
/// Run `f`, turning ordinary errors into `None` (upstream `.ok()`), but
/// propagating `Panic`.
fn[T] ok(f : () -> T raise TypifyError) -> T? raise TypifyError {
  try f() catch {
    Panic(_) as e => raise e
    _ => None
  } noraise {
    v => Some(v)
  }
}