///|
/// A lazily produced sequence of validation errors: the counterpart of the
/// generators upstream's keyword functions and `iter_errors` return.
///
/// Nothing is computed until the sequence is consumed, and consumers that
/// stop early (`first`, `is_empty`, a `for`-style loop that breaks) stop the
/// validation work right there, exactly like advancing a Python generator
/// only as far as needed. Errors that are not validation errors (unknown
/// types, unresolvable references, ...) are raised while consuming.
///
/// Each consumption re-runs the computation.
pub struct Errors {
  priv run : ((ValidationError) -> Bool raise) -> Bool raise
}

///|
/// A sequence produced by `run`, which must pass each error to its
/// argument (a "yield" callback returning whether to go on), stopping and
/// returning `false` as soon as that returns `false`; `run` returns `true`
/// once it has produced all its errors.
pub fn Errors::new(
  run : ((ValidationError) -> Bool raise) -> Bool raise,
) -> Errors {
  { run, }
}

///|
/// No errors.
pub fn Errors::empty() -> Errors {
  { run: _ => true, }
}

///|
/// A single error.
pub fn Errors::single(error : ValidationError) -> Errors {
  { run: yield_ => yield_(error), }
}

///|
/// Feed the errors to `yield_`, stopping early when it returns `false`.
/// Returns whether all errors were consumed.
pub fn Errors::run(
  self : Errors,
  yield_ : (ValidationError) -> Bool raise,
) -> Bool raise {
  (self.run)(yield_)
}

///|
/// Call `f` on each error.
pub fn Errors::each(
  self : Errors,
  f : (ValidationError) -> Unit raise,
) -> Unit raise {
  ignore(
    (self.run)(e => {
      f(e)
      true
    }),
  )
}

///|
/// The first error, if any (Python's `next(errors, None)`); stops the
/// computation right after it.
pub fn Errors::first(self : Errors) -> ValidationError? raise {
  let mut found = None
  ignore(
    (self.run)(e => {
      found = Some(e)
      false
    }),
  )
  found
}

///|
/// Whether there are no errors (stops at the first one).
pub fn Errors::is_empty(self : Errors) -> Bool raise {
  self.first() is None
}

///|
/// All the errors (Python's `list(errors)`).
pub fn Errors::to_array(self : Errors) -> Array[ValidationError] raise {
  let out = []
  self.each(e => out.push(e))
  out
}

///|
/// Transform each error as it passes through.
pub fn Errors::map(
  self : Errors,
  f : (ValidationError) -> ValidationError raise,
) -> Errors {
  { run: yield_ => (self.run)(e => yield_(f(e))), }
}