Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(event_handler): add custom response validation in OpenAPI utility #6189

Merged

Conversation

amin-farjadi
Copy link
Contributor

@amin-farjadi amin-farjadi commented Feb 28, 2025

Issue number: #5888

Summary

Add the option to distinguish between payload validation and response validation.

Changes

  • Optional has_response_validation_error argument has been added to OpenAPIValidationMiddleware.
  • Optional response_validation_error_http_status argument has been added to the ApiGatewayResolver and its subclasses.
  • ResponseValidationError exception class has been added to OpenAPI exceptions
  • Tests have been added for APIGatewayRestResolver response validation.
  • Add Validating responses section in the docs

User experience

Nothing will change. The user will have the option to set a custom response validation http status for Amazon API Gateway REST and HTTP APIs, Application Load Balancer (ALB), Lambda Function URLs, and VPC Lattice.

from http import HTTPStatus
from typing import Optional

import requests
from pydantic import BaseModel, Field

from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.utilities.typing import LambdaContext

tracer = Tracer()
logger = Logger()
app = APIGatewayRestResolver(
    enable_validation=True,
    response_validation_error_http_code=HTTPStatus.INTERNAL_SERVER_ERROR,  # (1)!
)


class Todo(BaseModel):
    userId: int
    id_: Optional[int] = Field(alias="id", default=None)
    title: str
    completed: bool


@app.get("/todos_bad_response/<todo_id>")
@tracer.capture_method
def get_todo_by_id(todo_id: int) -> Todo:
    todo = requests.get(f"https://jsonplaceholder.typicode.com/todos/{todo_id}")
    todo.raise_for_status()

    return todo.json()["title"]  # (2)!


@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP)
@tracer.capture_lambda_handler
def lambda_handler(event: dict, context: LambdaContext) -> dict:
    return app.resolve(event, context)

Checklist

If your change doesn't seem to apply, please leave them unchecked.

Is this a breaking change?

RFC issue number:

Checklist:

  • Migration process documented
  • Implement warnings (if it can live side by side)

Acknowledgment

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.

@amin-farjadi amin-farjadi requested a review from a team as a code owner February 28, 2025 14:37
@boring-cyborg boring-cyborg bot added documentation Improvements or additions to documentation event_handlers tests labels Feb 28, 2025
Copy link

boring-cyborg bot commented Feb 28, 2025

Thanks a lot for your first contribution! Please check out our contributing guidelines and don't hesitate to ask whatever you need.
In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: Invite link

@pull-request-size pull-request-size bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Feb 28, 2025
@leandrodamascena leandrodamascena changed the title feat: add custom response validation feat(openapi): add custom response validation Feb 28, 2025
@github-actions github-actions bot added feature New feature or functionality and removed documentation Improvements or additions to documentation labels Feb 28, 2025
@leandrodamascena
Copy link
Contributor

Hey hey @amin-farjadi! Thanks a lot for this PR! I'm working on fixing something else and will review it by Monday, ok?

@amin-farjadi
Copy link
Contributor Author

Hey hey @amin-farjadi! Thanks a lot for this PR! I'm working on fixing something else and will review it by Monday, ok?

Hi @leandrodamascena 👋 Absolutely fine, thanks

@leandrodamascena
Copy link
Contributor

Hey hey @amin-farjadi! Thanks a lot for this PR! I'm working on fixing something else and will review it by Monday, ok?

Hi @leandrodamascena 👋 Absolutely fine, thanks

I see that our CI is failing with some importing or type annotation. Can you take a look please?

@amin-farjadi
Copy link
Contributor Author

amin-farjadi commented Feb 28, 2025

Why are the tests checked against python 3.9 when the codebase is not compatible with it? Union operator for typing is used extensively in the codebase (which fails the python 3.9 test).

If I turn int | None to Optional[int], formatter will be unhappy. But, happy to do so if needs be

@leandrodamascena
Copy link
Contributor

Why are the tests checked against python 3.9 when the codebase is not compatible with it? Union operator for typing is used extensively in the codebase (which fails the python 3.9 test).

If I turn int | None to Optional[int], formatter will be unhappy. But, happy to do so if needs be

There are some inconsistencies when using Pydantic models and Union operations.. Try to make this Optional and remove future annotations. I think this can solve the problem for now. If not, I can take a look during the review.

@amin-farjadi
Copy link
Contributor Author

I see!

@boring-cyborg boring-cyborg bot added the documentation Improvements or additions to documentation label Feb 28, 2025
@amin-farjadi
Copy link
Contributor Author

amin-farjadi commented Feb 28, 2025

@leandrodamascena added ruff rule FA102 to be ignored for linting as this project uses python >=3.9 . This resolved the issue I was having in precommit, forcing me to import annotations in the test.

Copy link
Contributor

@leandrodamascena leandrodamascena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @amin-farjadi! This is an excellent PR, and I really appreciate your work on this. I've left some comments that I believe we should address before moving to the next round of review.

I'm also curious about potentially enabling this response validation code for individual routes as well. We could have it at both the global level and per route. This isn't a mandatory change, but I'd like to hear your thoughts on this idea.

Let me know what you think, and thanks again for your valuable contribution!

@amin-farjadi
Copy link
Contributor Author

Hi @leandrodamascena, thanks for the review. Will implement suggested changes by the end of the week. Regarding custom http status code on a route-level, I will familiarise myself with the Route implementation and has a crack at it in this PR.

@leandrodamascena
Copy link
Contributor

Hi @leandrodamascena, thanks for the review. Will implement suggested changes by the end of the week. Regarding custom http status code on a route-level, I will familiarise myself with the Route implementation and has a crack at it in this PR.

Thanks @amin-farjadi! It's great that you understand the Route implementation. However, after further consideration, let's keep this PR as is and create a new issue to discuss the implementation of the route-specific functionality. We can then work on that in a separate PR.

@amin-farjadi
Copy link
Contributor Author

Hi @leandrodamascena, I believe all your points have been addressed in the recent commits. I have drafted an implemented for route-specific custom response validation http status code which I can add in a subsequent PR

@leandrodamascena
Copy link
Contributor

Hi @leandrodamascena, I believe all your points have been addressed in the recent commits. I have drafted an implemented for route-specific custom response validation http status code which I can add in a subsequent PR

Hi @amin-farjadi! Thanks a lot for addressing all the points! I'll review it again today and I'll probably have some very small changes to make, especially in the documentation part, but nothing critical and I can push a commit before approving it, ok?

I'm very happy to have this new feature, it will give customers more flexibility when working with data validation.

Regarding to the validation code per route, please open an issue with a pseudo code showing the experience, this will be super nice!

@amin-farjadi
Copy link
Contributor Author

Hi @leandrodamascena, I believe all your points have been addressed in the recent commits. I have drafted an implemented for route-specific custom response validation http status code which I can add in a subsequent PR

Hi @amin-farjadi! Thanks a lot for addressing all the points! I'll review it again today and I'll probably have some very small changes to make, especially in the documentation part, but nothing critical and I can push a commit before approving it, ok?

I'm very happy to have this new feature, it will give customers more flexibility when working with data validation.

Regarding to the validation code per route, please open an issue with a pseudo code showing the experience, this will be super nice!

I'm happy to try and close this PR today. Absolutely fine by me, please do feel free.

I drafted this issue for route-specific custom response validation http status code (that's a mouthful!) #6245

@leandrodamascena
Copy link
Contributor

Hi @amin-farjadi! Just a quick explanation. A while ago you opened this issue and we fixed it in this PR. But this created a regression and I had to revert it while I find the right solution.

So because of this, some tests in the test_openapi_validation_middleware.py file are failing, but this is not caused by you. Can you please disable the those tests? Don't remove, just add the decorator @pytest.mark.skipif(reason="Test temporarily disabled until falsy return is fixed")?

test_validation_error_none_returned_non_optional_type
test_none_returned_for_falsy_return
test_custom_response_validation_error_http_code_invalid_response_none

@amin-farjadi
Copy link
Contributor Author

Hi @amin-farjadi! Just a quick explanation. A while ago you opened this issue and we fixed it in this PR. But this created a regression and I had to revert it while I find the right solution.

So because of this, some tests in the test_openapi_validation_middleware.py file are failing, but this is not caused by you. Can you please disable the those tests? Don't remove, just add the decorator @pytest.mark.skipif(reason="Test temporarily disabled until falsy return is fixed")?

test_validation_error_none_returned_non_optional_type test_none_returned_for_falsy_return test_custom_response_validation_error_http_code_invalid_response_none

Hi @leandrodamascena, this has been added. Please let me know if further changes are required.

Copy link
Contributor

@leandrodamascena leandrodamascena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @amin-farjadi! Thank you very much for working on all the feedback here! This is a super improvement for this utility!

APPROVED!

@github-actions github-actions bot removed the documentation Improvements or additions to documentation label Mar 17, 2025
@leandrodamascena leandrodamascena merged commit 7e56fe1 into aws-powertools:develop Mar 17, 2025
13 checks passed
Copy link

boring-cyborg bot commented Mar 17, 2025

Awesome work, congrats on your first merged pull request and thank you for helping improve everyone's experience!

@amin-farjadi
Copy link
Contributor Author

With pleasure @leandrodamascena 🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
event_handlers feature New feature or functionality size/L Denotes a PR that changes 100-499 lines, ignoring generated files. tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature request: HTTP response of an unsuccessful response validation must be a server-side error
3 participants