Skip to content

Commit

Permalink
JSON Response is returned for invalid API requests (#12305)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zedmor authored Nov 18, 2020
1 parent 763b40d commit 966ee7d
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 2 deletions.
1 change: 1 addition & 0 deletions airflow/api_connexion/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
EXCEPTIONS_LINK_MAP = {
400: f"{doc_link}#section/Errors/BadRequest",
404: f"{doc_link}#section/Errors/NotFound",
405: f"{doc_link}#section/Errors/MethodNotAllowed",
401: f"{doc_link}#section/Errors/Unauthenticated",
409: f"{doc_link}#section/Errors/AlreadyExists",
403: f"{doc_link}#section/Errors/PermissionDenied",
Expand Down
9 changes: 9 additions & 0 deletions airflow/api_connexion/openapi/v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ info:
framing, or deceptive request routing). To resolve this, please ensure that your syntax is correct.
## NotFound
This client error response indicates that the server cannot find the requested resource.
## MethodNotAllowed
Indicates that the request method is known by the server but is not supported by the target resource.
## NotAcceptable
The target resource does not have a current representation that would be acceptable to the user
agent, according to the proactive negotiation header fields received in the request, and the
Expand Down Expand Up @@ -2796,6 +2798,13 @@ components:
application/json:
schema:
$ref: '#/components/schemas/Error'
# 405
'MethodNotAllowed':
description: Request method is known by the server but is not supported by the target resource.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
# 406
'NotAcceptable':
description: A specified Accept header is not allowed.
Expand Down
20 changes: 18 additions & 2 deletions airflow/www/extensions/init_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import connexion
from connexion import ProblemException
from flask import Flask
from flask import Flask, request

from airflow.api_connexion.exceptions import common_error_handler
from airflow.security import permissions
Expand Down Expand Up @@ -135,11 +135,27 @@ def init_error_handlers(app: Flask):

def init_api_connexion(app: Flask) -> None:
"""Initialize Stable API"""
base_path = '/api/v1'

from airflow.www import views

@app.errorhandler(404)
@app.errorhandler(405)
def _handle_api_error(ex):
if request.path.startswith(base_path):
# 404 errors are never handled on the blueprint level
# unless raised from a view func so actual 404 errors,
# i.e. "no route for it" defined, need to be handled
# here on the application level
return common_error_handler(ex)
else:
return views.circles(ex)

spec_dir = path.join(ROOT_APP_DIR, 'api_connexion', 'openapi')
connexion_app = connexion.App(__name__, specification_dir=spec_dir, skip_error_handlers=True)
connexion_app.app = app
api_bp = connexion_app.add_api(
specification='v1.yaml', base_path='/api/v1', validate_responses=True, strict_validation=True
specification='v1.yaml', base_path=base_path, validate_responses=True, strict_validation=True
).blueprint
app.register_error_handler(ProblemException, common_error_handler)
app.extensions['csrf'].exempt(api_bp)
Expand Down
52 changes: 52 additions & 0 deletions tests/api_connexion/test_error_handling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import unittest

from airflow.www import app


class TestErrorHandling(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.app = app.create_app(testing=True) # type:ignore

def setUp(self) -> None:
self.client = self.app.test_client() # type:ignore

def test_incorrect_endpoint_should_return_json(self):

# Given we have application with Connexion added
# When we hitting incorrect endpoint in API path

resp_json = self.client.get("/api/v1/incorrect_endpoint").json

# Then we have parsable JSON as output

self.assertEqual(404, resp_json["status"])

# When we are hitting non-api incorrect enpoint

resp_json = self.client.get("/incorrect_endpoint").json

# Then we do not have JSON as response, rather standard HTML

self.assertIsNone(resp_json)

resp_json = self.client.put("/api/v1/variables").json

self.assertEqual('Method Not Allowed', resp_json["title"])

0 comments on commit 966ee7d

Please sign in to comment.