NAME

PAGI::FastAPI::ResponseModel - Response Shape Validation and Filtering for PAGI::FastAPI

VERSION

Version v1.2.5

SYNOPSIS

use PAGI::FastAPI::ResponseModel qw(with_response_model);
use Types::Standard qw(Str Int);

# HashRef form: validates AND filters to just these fields.
$app->get('/users/{id}',
    handler => with_response_model(
        { id => Int, name => Str, email => Str },   # note: no 'password_hash' here
        async sub ($c) {
            my $row = My::DB->find_user($c->path_param('id'));
            return $row;   # even if $row also has password_hash, last_login, etc.,
                           # only id/name/email reach the client
        }
    ),
);

# Type::Tiny object form: validates the whole return value as-is.
use Types::Standard qw(ArrayRef);
$app->get('/tags',
    handler => with_response_model(
        ArrayRef[Str],
        async sub ($c) { return My::DB->all_tags }
    ),
);

DESCRIPTION

Python FastAPI's response_model does two things: validates that a handler's return value matches a declared shape, and filters the output to only the declared fields (so accidentally returning an ORM row with extra internal columns doesn't leak them). This module reproduces both, using the exact same Type::Tiny-based schema conventions PAGI::FastAPI's own body validation already uses, so it should feel native rather than bolted-on.

Failure semantics: if a handler's actual return value doesn't match its declared response_model, that's treated as a server bug (HTTP 500), not a client error, mirroring Python FastAPI's ResponseValidationError behavior. The specific validation failure is logged via warn() server-side but not exposed in the response body, to avoid leaking internal shape details to the client.

Response objects (anything isa(PAGI::FastAPI::Response) or implementing dispatch the way SSE does) pass through untouched, a data schema doesn't apply to an already-fully-formed response.

FUNCTIONS

with_response_model($schema, $handler)

Returns a new coderef suitable for a route's handler option.

$schema is either:

  • A Type::Tiny object/constraint, validates the entire returned value against it. No filtering happens in this form since there's no set of "declared fields" to filter to.

  • A HashRef of field => Type::Tiny, requires the handler to return a HashRef, validates each declared field, and returns a new HashRef containing only the declared fields. Undeclared keys in the handler's actual return value are silently dropped.

CAVEATS

Filtering is shallow, nested HashRefs/ArrayRefs inside a field's value are passed through as-is, not recursively filtered. For a field holding a nested structure, validate/filter it explicitly inside your own handler, or pass a Type::Tiny constraint (e.g. from Types::Standard's Dict) that itself models the nested shape.

SEE ALSO

Type::Tiny, Types::Standard

AUTHOR

Mohammad Sajid Anwar, <mohammad.anwar at yahoo.com>

REPOSITORY

https://github.com/manwar/PAGI-FastAPI

BUGS

Please report any bugs or feature requests through the web interface at https://github.com/manwar/PAGI-FastAPI/issues. I will be notified and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc PAGI::FastAPI::ResponseModel

You can also look for information at:

LICENSE AND COPYRIGHT

Copyright (C) 2026 Mohammad Sajid Anwar.

This program is free software; you can redistribute it and/or modify it under the terms of the Artistic License (2.0).