fix(adapter): isolate missing-body error locations between requests

Closed
#543 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

A pull request for this has already been merged.

  • #557 by @yeongseon — merged

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
75/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
python
Domain
backend, testing

Research direction

Read src/azure_functions_validation/adapter.py, especially _missing_body_validation_error() and the shared missing-body template around the referenced lines. First run the reproduction in the issue and find existing isolation tests for missing-body errors; add coverage for nested loc identity, formatter mutation, empty and whitespace-only bodies, and repeated or concurrent calls. Done means every invocation has independent locations while preserving the import-time model construction optimization and stable 422 details.

Written by the indexing model from the issue text.

Description

bug needs-triage priority:medium

Context

Missing-body exceptions share the cached error's mutable loc list. A custom error formatter that edits its exception details changes later responses from other handlers, even when those handlers use separate default adapter instances. Priority is medium: this is persistent cross-request error-state corruption with a small fix, triggered by an otherwise ordinary custom formatting operation.

Reviewed commit: b38d702adb1039ed1eda2ef3799d873af2506f41 (azure-functions-validation 0.16.0). Locally reproduced on Python 3.12.14, Pydantic 2.14.1, and azure-functions 1.25.0, with the reviewed checkout's src on PYTHONPATH.

Run this complete snippet with those dependencies and the reviewed package installed:

import json
import azure.functions as func
from pydantic import BaseModel
from azure_functions_validation import validate_http

class Body(BaseModel):
    value: int

def prefix_locations(exc, status):
    for detail in exc.errors:
        detail["loc"].insert(0, "request")
    return {"detail": exc.errors}

@validate_http(body=Body, error_formatter=prefix_locations)
def custom_handler(req: func.HttpRequest, body: Body):
    return body.model_dump()

@validate_http(body=Body)
def default_handler(req: func.HttpRequest, body: Body):
    return body.model_dump()

req = func.HttpRequest(
    method="POST", url="http://localhost/api/probe", headers={}, params={}, body=b"",
)
for handler in (default_handler, custom_handler, default_handler, custom_handler, default_handler):
    response = handler(req)
    details = json.loads(response.get_body())["detail"]
    print(handler.__name__, response.status_code, details[0]["loc"])

Observed: locations are ['body'], then ['request', 'body'] for both custom and default handlers, then ['request', 'request', 'body'] for both. A direct adapter probe also confirms that successive exceptions' errors[0]['loc'] values are the exact same object.

Expected: each invocation starts with its own ['body'] location. The custom handler can format its response as ['request', 'body'], while every default-handler response remains ['body']. No mutation should accumulate across requests or handler instances.

Root cause / fix direction: _missing_body_validation_error() creates a fresh exception, list, and dictionaries using [dict(entry) for entry in detail], but this is a shallow copy: every dictionary still references the module-level cached loc list. Copy nested mutable error fields, or cache an immutable template and materialize a fresh normalized error structure for each call. Preserve the import-time model/error construction optimization from #255.

Acceptance Checklist

  • Consecutive missing-body errors have distinct nested loc list identities.
  • Editing a custom formatter's error location cannot alter later default responses or another adapter instance.
  • Both empty bytes and whitespace-only bodies retain stable source-prefixed 422 details.
  • Repeated and concurrent invocations cannot accumulate location prefixes.
  • The missing-body model and template remain initialized once, without per-request model-class construction.

Out of scope

  • Making arbitrary caller-owned custom adapter state thread-safe.
  • Changing the missing-body status code or public error envelope.
  • Broad changes to error aggregation semantics.

References

Dominant language
Python
Stars
13
Forks
4
Avg merge
14h 46m
Merged PRs (30d)
89

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.