python-openapi / openapi-core

Openapi-core is a Python library that adds client-side and server-side support for the OpenAPI v3.0 and OpenAPI v3.1 specification.
BSD 3-Clause "New" or "Revised" License
308 stars 132 forks source link

Draft : Adding provision to pass inputs to custom error handler class #919

Open rohan-97 opened 2 months ago

rohan-97 commented 2 months ago

Adding an optional parameter error_handler_params in Flask Decorator to provide data to Error handler class.

The parameter is basically a dictionary which is passed to constructor of error handler class, and if it is not passed, then nothing is passed in the constructor.

This PR implements following feature request https://github.com/python-openapi/openapi-core/issues/910

Consider below example for understanding usage of this flag

#!/usr/bin/python3
"""Test server."""

from flask import Flask, request, jsonify
from openapi_core.contrib.flask.decorators import FlaskOpenAPIViewDecorator, FlaskOpenAPIErrorsHandler
from openapi_core import Spec

# Custom Error Handler block
class MyCustomErrorHandler(FlaskOpenAPIErrorsHandler):
    """"Custom Error Handler"""
    def __init__(self, input_param:dict):
        self.title =  input_param.get("title")

    def handle(self, errors:list):
        response_object =  jsonify({
            "title": self.title,
            "causedBy" : [self.handle_error(error) for error in errors]
        })
        response_object.error_code = 400
        return response_object

    def handle_error(self, error):
        """
        Converts error object into error string message

        :param error: Error object which stores exception message
        :type error: Exception object
        :return: Error message string corresponding to error object
        :rtype: str
        """
        if error.__cause__ is not None:
            error = error.__cause__
        return str(error)

SPEC = "test.yaml"
app = Flask(__name__)

spec_object = Spec.from_file_path(SPEC)
openapi_decorator = FlaskOpenAPIViewDecorator.from_spec(spec_object, openapi_errors_handler=MyCustomErrorHandler, error_handler_params={"title" : "Failed to execute test API"})

@app.route("/test", methods=["POST"])
@openapi_decorator
def read_permission():
    """Test function"""
    temp =  jsonify({
                    "stri_normal_json": request.json.get("flag", 1)
                })
    print(dir(temp))
    return temp

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=345, debug=True)

#Valid request: curl -X POST http://localhost:345/test --data '{"flag":"rohana"}' -H 'Content-Type: application/json'

#Invalid Request: curl -X POST http://localhost:345/test --data '{"flag":"rohan"}' -H 'Content-Type: application/json'

Following is test.yaml

openapi: '3.0.2'
info:
  title: Test Title
  version: '1.0'
servers:
  - url: http://localhost:345/
paths:
  /test:
    post:
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
                - flag
              properties:
                flag:
                  x-pii: true
                  type: string
                  pattern: "^[\\w.-]*$"
                  minLength: 6
                  maxLength: 20
      responses:
        200:
          description: Sample response
          content:
            application/json:
              schema:
                type: object
                properties:
                  stri_normal_json:
                    type: string
                    minLength: 6
                    maxLength: 20

Kindly note the constructor of openapi error handler

def __init__(self, input_param:dict):

whatever custom input is passed is accessed through input_param parameter.
int input_param is a dictionary and stores custom data in custom key/value format.

this custom input can be passed whenever we create new openapi_core decorator object as shown in above example

FlaskOpenAPIViewDecorator.from_spec(spec_object, openapi_errors_handler=MyCustomErrorHandler, error_handler_params={"title" : "Failed to execute test API"})

as we can see the dictionary {"title" : "Failed to execute test API"} is passed to error handler constructor and is accessed within the code

rohan-97 commented 2 months ago

As there were no docstrings present, the new parameter is not documented
do let me know if it needs to be documented somewhere