diff --git a/docs/configuration.md b/docs/configuration.md index e12c9be..6b72173 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,5 +1,13 @@ # Configuration +Validation injects configured models by parameter name: `body`, `query`, `path` +and `headers` (`req_model` for the deprecated shorthand). A required handler +parameter annotated with a configured model but named differently, such as +`payload: Input` for `body=Input`, raises a decoration-time configuration error. +Rename it to `body`; there is no implicit injection by type. Independent Azure +binding parameters and handlers that validate without consuming a model remain +supported. + This page documents every parameter of `validate_http` and how to combine them in production handlers. diff --git a/src/azure_functions_validation/decorator.py b/src/azure_functions_validation/decorator.py index afcf9df..5de4211 100644 --- a/src/azure_functions_validation/decorator.py +++ b/src/azure_functions_validation/decorator.py @@ -307,6 +307,26 @@ def _validate_no_conflicts( f"conflicts with a @validate_http injected parameter of the same name. " f"Rename it (e.g. to 'req' or 'http_request')." ) + parameters = inspect.signature(func).parameters + try: + annotations = get_type_hints(func) + except (NameError, TypeError): + annotations = getattr(func, "__annotations__", {}) + for name, parameter in parameters.items(): + if name == request_param_name or name in _injected: + continue + if parameter.default is not inspect.Parameter.empty: + continue + annotation = annotations.get(name) + expected = [ + key for key, model in _injected.items() if model is not None and annotation is model + ] + if expected: + raise ValueError( + f"Function {func.__name__}: parameter '{name}' matches a configured model; " + f"expected injected parameter name(s): {', '.join(expected)}. " + "Validation injects by name, not by type." + ) _MISSING: Any = object() # sentinel for absent req argument diff --git a/tests/test_injected_parameter_diagnostic.py b/tests/test_injected_parameter_diagnostic.py new file mode 100644 index 0000000..11bb107 --- /dev/null +++ b/tests/test_injected_parameter_diagnostic.py @@ -0,0 +1,25 @@ +from azure.functions import HttpRequest +from pydantic import BaseModel +import pytest + +from azure_functions_validation import validate_http + + +class Input(BaseModel): + name: str + + +def test_mismatched_body_model_parameter_fails_during_decoration() -> None: + with pytest.raises(ValueError, match="payload.*expected.*body"): + + @validate_http(body=Input) + def handler(req: HttpRequest, payload: Input) -> Input: + return payload + + +def test_correctly_named_model_and_independent_bindings_remain_supported() -> None: + @validate_http(body=Input) + def handler(req: HttpRequest, body: Input, context: str) -> Input: + return body + + assert callable(handler)