Query / Path / Header Example¶
Overview¶
This example demonstrates validating non-body request sources in one endpoint:
- query parameters
- route path parameters
- HTTP headers
It corresponds to:
examples/profile_validation/function_app.py
This pattern is ideal for read-heavy APIs where request context comes mostly from URL and headers rather than JSON body.
Prerequisites¶
- Python 3.10+
- Azure Functions Python v2 project
- Installed
azure-functions-validationand dependencies
Related baseline
If you are new to body validation first, read Basic Validation before this page.
Complete Working Code¶
import azure.functions as func
from pydantic import BaseModel, ConfigDict, Field
from azure_functions_validation import validate_http
class ProfileQuery(BaseModel):
verbose: bool = False
class ProfilePath(BaseModel):
user_id: int = Field(ge=1)
class ProfileHeaders(BaseModel):
model_config = ConfigDict(populate_by_name=True)
x_request_id: str = Field(alias="x-request-id")
class ProfileResponse(BaseModel):
user_id: int
view: str
request_id: str
app = func.FunctionApp()
@app.function_name(name="get_profile")
@app.route(route="users/{user_id}", methods=["GET"], auth_level=func.AuthLevel.ANONYMOUS)
@validate_http(
query=ProfileQuery,
path=ProfilePath,
headers=ProfileHeaders,
response_model=ProfileResponse,
)
def get_profile(
req: func.HttpRequest,
query: ProfileQuery,
path: ProfilePath,
headers: ProfileHeaders,
) -> ProfileResponse:
view = "detailed" if query.verbose else "summary"
return ProfileResponse(
user_id=path.user_id,
view=view,
request_id=headers.x_request_id,
)
Step-by-step walkthrough¶
Step 1: define source-specific models¶
ProfileQueryvalidates?verbose=truestyle values.ProfilePathvalidates route value{user_id}.ProfileHeadersvalidatesx-request-id.
Each source has its own schema and constraints.
Step 2: map header aliases¶
x-request-id is not a valid Python identifier, so alias mapping is used:
class ProfileHeaders(BaseModel):
model_config = ConfigDict(populate_by_name=True)
x_request_id: str = Field(alias="x-request-id")
Step 3: combine decorator parameters¶
The decorator validates all three inputs before entering your handler logic.
Step 4: consume typed inputs¶
Inside the handler you get typed objects:
query.verboseasboolpath.user_idasintheaders.x_request_idasstr
Source separation
Keeping each source in a dedicated model reduces accidental coupling and makes endpoint behavior easier to test.
Test with curl¶
Valid request¶
Expected response:
HTTP/1.1 200 OK
Content-Type: application/json
{"user_id":42,"view":"detailed","request_id":"req-7f5c8a4a"}
Invalid path parameter¶
Expected response:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{"detail":[{"loc":["path","user_id"],"msg":"Input should be greater than or equal to 1","type":"greater_than_equal"}]}
Missing required header¶
Expected response:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{"detail":[{"loc":["headers","x-request-id"],"msg":"Field required","type":"missing"}]}
Route template alignment
Your path model field names must match route placeholders,
such as {user_id} <-> user_id.
What you learned¶
- How to validate query, path, and headers simultaneously
- How to map headers with aliases
- How route parameter constraints produce predictable
422errors - How response models enforce outbound data structure