// response_model — validate and filter a handler's return value through a
// declared model before it goes on the wire (FastAPI's `response_model=`). The
// return value is checked against the same `Schema` descriptor that drives
// request validation and OpenAPI emission, then projected down to exactly the
// model's declared fields: anything extra (an internal id, a hashed password) is
// dropped, so a route can return a richer object than it exposes. A value that
// doesn't conform is a server bug, so it surfaces as a 500, matching FastAPI's
// `ResponseValidationError`.

///|
/// Keep only what `model` declares, recursively: for an object, drop keys the
/// model doesn't list and project each declared field through its own schema;
/// for an array, project every element through the item schema; a scalar passes
/// through unchanged. Assumes `value` already validated against `model`, so a
/// declared field that's missing here simply isn't emitted.
fn project_through(model : Schema, value : Json) -> Json {
  match (model, value) {
    (SObject(os), Object(m)) => {
      let out : Map[String, Json] = Map([])
      for f in os.fields {
        match m.get(f.name) {
          Some(v) => out[f.name] = project_through(f.schema, v)
          None => ()
        }
      }
      out.to_json()
    }
    (SArray(item), Array(a)) => {
      let out : Array[Json] = []
      for v in a {
        out.push(project_through(item, v))
      }
      out.to_json()
    }
    _ => value
  }
}

///|
/// Validate `value` against `model` and, if it conforms, return it filtered down
/// to the model's declared fields (extras dropped, nested objects and arrays
/// projected too). On a mismatch return the located `ValidationError`s under
/// `["response"]` — the same shape request validation produces. This is FastAPI's
/// `response_model`: the outgoing shape is the model, not whatever the handler
/// happened to build.
pub fn filter_response(
  model : Schema,
  value : Json,
) -> Result[Json, Array[ValidationError]] {
  let errs : Array[ValidationError] = []
  validate_schema(model, value, ["response"], errs)
  if errs.length() > 0 {
    Err(errs)
  } else {
    Ok(project_through(model, value))
  }
}

///|
/// A JSON response whose body is `value` filtered through `model`. On success a
/// `status` response carrying only the model's declared fields; on a model
/// mismatch a `500` whose body lists the response-validation errors — the return
/// value didn't match what the route promised, which is a server-side fault.
pub fn json_model(
  status : Int,
  model : Schema,
  value : Json,
) -> @moonasgi.Response {
  match filter_response(model, value) {
    Ok(filtered) => json(status, filtered)
    Err(errs) => json(500, validation_error_body(errs))
  }
}