This is an automated email from the ASF dual-hosted git repository.

jedcunningham pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new d862ad92cea add 404 response for invalid routes (#48545)
d862ad92cea is described below

commit d862ad92ceaa9a96309ad4ee7882d2a6bffe5f1c
Author: Kalyan R <[email protected]>
AuthorDate: Sun Mar 30 22:39:48 2025 +0530

    add 404 response for invalid routes (#48545)
---
 .../api_fastapi/core_api/openapi/v1-generated.yaml | 24 +++++++++++++++++++
 .../api_fastapi/core_api/routes/public/__init__.py | 10 +++++++-
 .../src/airflow/ui/openapi-gen/queries/common.ts   | 17 ++++++++++++++
 .../ui/openapi-gen/queries/ensureQueryData.ts      | 21 +++++++++++++++++
 .../src/airflow/ui/openapi-gen/queries/prefetch.ts | 21 +++++++++++++++++
 .../src/airflow/ui/openapi-gen/queries/queries.ts  | 27 ++++++++++++++++++++++
 .../src/airflow/ui/openapi-gen/queries/suspense.ts | 27 ++++++++++++++++++++++
 .../ui/openapi-gen/requests/services.gen.ts        | 25 ++++++++++++++++++++
 .../airflow/ui/openapi-gen/requests/types.gen.ts   | 21 +++++++++++++++++
 .../api_fastapi/core_api/routes/test_routes.py     |  8 +++++++
 10 files changed, 200 insertions(+), 1 deletion(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v1-generated.yaml 
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
index c7c8ce31833..d82252dc6bf 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
+++ b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
@@ -7304,6 +7304,30 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
+  /api/v2/{rest_of_path}:
+    get:
+      summary: Not Found Handler
+      description: Catch all route to handle invalid endpoints.
+      operationId: not_found_handler
+      parameters:
+      - name: rest_of_path
+        in: path
+        required: true
+        schema:
+          type: string
+          title: Rest Of Path
+      responses:
+        '200':
+          description: Successful Response
+          content:
+            application/json:
+              schema: {}
+        '422':
+          description: Validation Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/HTTPValidationError'
 components:
   schemas:
     AppBuilderMenuItemResponse:
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/__init__.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/__init__.py
index 77fe7a33a18..dc14f724a30 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/__init__.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/__init__.py
@@ -17,7 +17,8 @@
 
 from __future__ import annotations
 
-from fastapi import status
+from fastapi import Request, status
+from starlette.responses import JSONResponse
 
 from airflow.api_fastapi.common.router import AirflowRouter
 from airflow.api_fastapi.core_api.openapi.exceptions import 
create_openapi_http_exception_doc
@@ -57,6 +58,7 @@ authenticated_router = AirflowRouter(
     responses=create_openapi_http_exception_doc([status.HTTP_401_UNAUTHORIZED, 
status.HTTP_403_FORBIDDEN]),
 )
 
+
 authenticated_router.include_router(assets_router)
 authenticated_router.include_router(backfills_router)
 authenticated_router.include_router(connections_router)
@@ -91,3 +93,9 @@ public_router.include_router(authenticated_router)
 public_router.include_router(monitor_router)
 public_router.include_router(version_router)
 public_router.include_router(auth_router)
+
+
+@public_router.get("/{rest_of_path:path}", include_in_schema=False)
+def not_found_handler(request: Request, rest_of_path: str):
+    """Catch all route to handle invalid endpoints."""
+    return JSONResponse(status_code=404, content={"error": "invalid route"})
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
index 135bc03a36d..b3308f30f9e 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -17,6 +17,7 @@ import {
   DagWarningService,
   DagsService,
   DashboardService,
+  DefaultService,
   DependenciesService,
   EventLogService,
   ExtraLinksService,
@@ -1794,6 +1795,22 @@ export const UseLoginServiceLogoutKeyFn = (
   } = {},
   queryKey?: Array<unknown>,
 ) => [useLoginServiceLogoutKey, ...(queryKey ?? [{ next }])];
+export type DefaultServiceNotFoundHandlerDefaultResponse = Awaited<
+  ReturnType<typeof DefaultService.notFoundHandler>
+>;
+export type DefaultServiceNotFoundHandlerQueryResult<
+  TData = DefaultServiceNotFoundHandlerDefaultResponse,
+  TError = unknown,
+> = UseQueryResult<TData, TError>;
+export const useDefaultServiceNotFoundHandlerKey = 
"DefaultServiceNotFoundHandler";
+export const UseDefaultServiceNotFoundHandlerKeyFn = (
+  {
+    restOfPath,
+  }: {
+    restOfPath: string;
+  },
+  queryKey?: Array<unknown>,
+) => [useDefaultServiceNotFoundHandlerKey, ...(queryKey ?? [{ restOfPath }])];
 export type AssetServiceCreateAssetEventMutationResult = Awaited<
   ReturnType<typeof AssetService.createAssetEvent>
 >;
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
index 846819e8c13..db69e50d1c8 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -16,6 +16,7 @@ import {
   DagWarningService,
   DagsService,
   DashboardService,
+  DefaultService,
   DependenciesService,
   EventLogService,
   ExtraLinksService,
@@ -2511,3 +2512,23 @@ export const ensureUseLoginServiceLogoutData = (
     queryKey: Common.UseLoginServiceLogoutKeyFn({ next }),
     queryFn: () => LoginService.logout({ next }),
   });
+/**
+ * Not Found Handler
+ * Catch all route to handle invalid endpoints.
+ * @param data The data for the request.
+ * @param data.restOfPath
+ * @returns unknown Successful Response
+ * @throws ApiError
+ */
+export const ensureUseDefaultServiceNotFoundHandlerData = (
+  queryClient: QueryClient,
+  {
+    restOfPath,
+  }: {
+    restOfPath: string;
+  },
+) =>
+  queryClient.ensureQueryData({
+    queryKey: Common.UseDefaultServiceNotFoundHandlerKeyFn({ restOfPath }),
+    queryFn: () => DefaultService.notFoundHandler({ restOfPath }),
+  });
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
index f49e671be05..99d44a857e1 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -16,6 +16,7 @@ import {
   DagWarningService,
   DagsService,
   DashboardService,
+  DefaultService,
   DependenciesService,
   EventLogService,
   ExtraLinksService,
@@ -2511,3 +2512,23 @@ export const prefetchUseLoginServiceLogout = (
     queryKey: Common.UseLoginServiceLogoutKeyFn({ next }),
     queryFn: () => LoginService.logout({ next }),
   });
+/**
+ * Not Found Handler
+ * Catch all route to handle invalid endpoints.
+ * @param data The data for the request.
+ * @param data.restOfPath
+ * @returns unknown Successful Response
+ * @throws ApiError
+ */
+export const prefetchUseDefaultServiceNotFoundHandler = (
+  queryClient: QueryClient,
+  {
+    restOfPath,
+  }: {
+    restOfPath: string;
+  },
+) =>
+  queryClient.prefetchQuery({
+    queryKey: Common.UseDefaultServiceNotFoundHandlerKeyFn({ restOfPath }),
+    queryFn: () => DefaultService.notFoundHandler({ restOfPath }),
+  });
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
index 5baaf1218cd..9429a0b14bc 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -17,6 +17,7 @@ import {
   DagWarningService,
   DagsService,
   DashboardService,
+  DefaultService,
   DependenciesService,
   EventLogService,
   ExtraLinksService,
@@ -2990,6 +2991,32 @@ export const useLoginServiceLogout = <
     queryFn: () => LoginService.logout({ next }) as TData,
     ...options,
   });
+/**
+ * Not Found Handler
+ * Catch all route to handle invalid endpoints.
+ * @param data The data for the request.
+ * @param data.restOfPath
+ * @returns unknown Successful Response
+ * @throws ApiError
+ */
+export const useDefaultServiceNotFoundHandler = <
+  TData = Common.DefaultServiceNotFoundHandlerDefaultResponse,
+  TError = unknown,
+  TQueryKey extends Array<unknown> = unknown[],
+>(
+  {
+    restOfPath,
+  }: {
+    restOfPath: string;
+  },
+  queryKey?: TQueryKey,
+  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
+) =>
+  useQuery<TData, TError>({
+    queryKey: Common.UseDefaultServiceNotFoundHandlerKeyFn({ restOfPath }, 
queryKey),
+    queryFn: () => DefaultService.notFoundHandler({ restOfPath }) as TData,
+    ...options,
+  });
 /**
  * Create Asset Event
  * Create asset events.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
index 4d9fa9e218c..732c28f8e37 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -16,6 +16,7 @@ import {
   DagWarningService,
   DagsService,
   DashboardService,
+  DefaultService,
   DependenciesService,
   EventLogService,
   ExtraLinksService,
@@ -2967,3 +2968,29 @@ export const useLoginServiceLogoutSuspense = <
     queryFn: () => LoginService.logout({ next }) as TData,
     ...options,
   });
+/**
+ * Not Found Handler
+ * Catch all route to handle invalid endpoints.
+ * @param data The data for the request.
+ * @param data.restOfPath
+ * @returns unknown Successful Response
+ * @throws ApiError
+ */
+export const useDefaultServiceNotFoundHandlerSuspense = <
+  TData = Common.DefaultServiceNotFoundHandlerDefaultResponse,
+  TError = unknown,
+  TQueryKey extends Array<unknown> = unknown[],
+>(
+  {
+    restOfPath,
+  }: {
+    restOfPath: string;
+  },
+  queryKey?: TQueryKey,
+  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
+) =>
+  useSuspenseQuery<TData, TError>({
+    queryKey: Common.UseDefaultServiceNotFoundHandlerKeyFn({ restOfPath }, 
queryKey),
+    queryFn: () => DefaultService.notFoundHandler({ restOfPath }) as TData,
+    ...options,
+  });
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
index 64289b24c18..208adb5e49e 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
@@ -215,6 +215,8 @@ import type {
   LoginResponse,
   LogoutData,
   LogoutResponse,
+  NotFoundHandlerData,
+  NotFoundHandlerResponse,
 } from "./types.gen";
 
 export class AuthLinksService {
@@ -3583,3 +3585,26 @@ export class LoginService {
     });
   }
 }
+
+export class DefaultService {
+  /**
+   * Not Found Handler
+   * Catch all route to handle invalid endpoints.
+   * @param data The data for the request.
+   * @param data.restOfPath
+   * @returns unknown Successful Response
+   * @throws ApiError
+   */
+  public static notFoundHandler(data: NotFoundHandlerData): 
CancelablePromise<NotFoundHandlerResponse> {
+    return __request(OpenAPI, {
+      method: "GET",
+      url: "/api/v2/{rest_of_path}",
+      path: {
+        rest_of_path: data.restOfPath,
+      },
+      errors: {
+        422: "Validation Error",
+      },
+    });
+  }
+}
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index 08183a2bad9..0b17592b022 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -2603,6 +2603,12 @@ export type LogoutData = {
 
 export type LogoutResponse = unknown;
 
+export type NotFoundHandlerData = {
+  restOfPath: string;
+};
+
+export type NotFoundHandlerResponse = unknown;
+
 export type $OpenApiTs = {
   "/ui/auth/links": {
     get: {
@@ -5429,4 +5435,19 @@ export type $OpenApiTs = {
       };
     };
   };
+  "/api/v2/{rest_of_path}": {
+    get: {
+      req: NotFoundHandlerData;
+      res: {
+        /**
+         * Successful Response
+         */
+        200: unknown;
+        /**
+         * Validation Error
+         */
+        422: HTTPValidationError;
+      };
+    };
+  };
 };
diff --git a/airflow-core/tests/unit/api_fastapi/core_api/routes/test_routes.py 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/test_routes.py
index 9ec7b384864..78f1fc80ae7 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/test_routes.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/test_routes.py
@@ -24,6 +24,7 @@ NO_AUTH_PATHS = {
     "/api/v2/auth/logout",
     "/api/v2/version",
     "/api/v2/monitor/health",
+    "/api/v2/{rest_of_path:path}",
 }
 
 
@@ -50,3 +51,10 @@ def test_routes_with_responses():
             # All other routes should have 401 and 403 responses indicating 
they require auth
             assert 401 in route.responses, f"Route {route.path} is missing 401 
response"
             assert 403 in route.responses, f"Route {route.path} is missing 403 
response"
+
+
+def test_invalid_routes_return_404(test_client):
+    """Invalid routes should return a 404."""
+    response = test_client.get("/api/v2/nonexistent")
+    assert response.status_code == 404
+    assert response.json() == {"error": "invalid route"}

Reply via email to