///|
/// Apply a transformation function to the validated JSON value.
///
/// The schema is first validated normally. If validation succeeds, `f` is
/// called with the validated JSON. The transform can modify the value or
/// return an error.
///
/// Rules chained after `.transform()` are applied to the transformed value.
///
/// # Example
/// ```mbt nocheck
/// let s = string().transform(fn(json) {
///   match json {
///     String(s) => Ok(Json::string(s + "!"))
///     _ => Err("expected string")
///   }
/// })
/// ```
pub fn Schema::transform(
  self : Schema,
  f : (Json) -> Result[Json, String],
) -> Schema {
  {
    schema_type: TransformType(self, TransformClosure::{ f, }),
    rules: [],
    description: self.description,
    required_error: self.required_error,
    invalid_type_error: self.invalid_type_error,
    name: self.name,
    brand: self.brand,
  }
}

///|
pub fn Schema::parse_transform(
  _self : Schema,
  inner : Schema,
  closure : TransformClosure,
  json : Json,
  path_stack : Array[String],
) -> SchemaResult {
  match parse_inner(inner, json, path_stack) {
    Err(e) => Err(e)
    Ok(validated) =>
      match (closure.f)(validated) {
        Ok(transformed) => {
          let errors : Array[ValidationError] = []
          collect_errors(errors, path_stack, transformed, _self.rules)
          if errors.is_empty() {
            Ok(transformed)
          } else {
            Err(errors)
          }
        }
        Err(msg) => {
          let path = format_path(path_stack)
          Err([ValidationError::{ path, message: msg, got: json }])
        }
      }
  }
}