///|
/// Deserialise the JSON request body into a user type `T`, `None` when the body
/// is absent, is not valid JSON, or does not shape-match `T`. This is the
/// unchecked, best-effort extractor — the total counterpart of writing
/// `item: Item` on a FastAPI handler when you don't want the framework's 422.
/// `T` describes itself with `derive(@json.FromJson)`; the deserialisation is
/// core's, so it stays faithful to the JSON shape without any reflection.
pub fn[T : @json.FromJson] Context::body(self : Context) -> T? {
match self.body_json() {
None => None
Some(j) => Some(@json.from_json(j)) catch { _ => None }
}
}
///|
/// The validated typed-body extractor — the faithful equivalent of FastAPI
/// declaring a pydantic model parameter: the body is checked against the
/// endpoint's descriptor `schema` (the same tree that emits the OpenAPI body
/// schema), and only if it conforms is it deserialised into `T`. On failure it
/// yields the FastAPI-shaped `ValidationError` list (located under `["body",
/// ...]`), ready for `unprocessable`; on success it yields the built `T`.
///
/// Reusing `validate_schema` here is the point of the descriptor tree: schema
/// emission, request validation, and typed deserialisation are all driven off
/// one source of truth, exactly as pydantic derives all three from one model.
/// Because validation runs first, `@json.from_json` is reached only for a
/// shape-conforming value; the final `catch` keeps the extractor total for the
/// residual cases a scalar schema cannot express (e.g. an out-of-range integer).
pub fn[T : @json.FromJson] Context::body_validated(
self : Context,
schema : Schema,
) -> Result[T, Array[ValidationError]] {
let raw = decode_field(self.request.body)
if raw == "" {
return Err([ValidationError::missing(["body"])])
}
let j = @json.parse(raw) catch {
_ =>
return Err([
ValidationError::type_error(
["body"],
"json_invalid",
"Input should be valid JSON",
),
])
}
let errs : Array[ValidationError] = []
validate_schema(schema, j, ["body"], errs)
if errs.length() > 0 {
return Err(errs)
}
let parsed : T? = Some(@json.from_json(j)) catch { _ => None }
match parsed {
Some(v) => Ok(v)
None =>
Err([
ValidationError::type_error(
["body"],
"model_type",
"Input should be a valid object",
),
])
}
}