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

guan404ming 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 5ac8c33b218 Validate Dag and task IDs in the ts-sdk task registry 
(#69400)
5ac8c33b218 is described below

commit 5ac8c33b21829d6ce5983d0cddefd8057428153b
Author: Guan-Ming Chiu <[email protected]>
AuthorDate: Fri Jul 10 16:04:29 2026 +0800

    Validate Dag and task IDs in the ts-sdk task registry (#69400)
    
    * Validate Dag and task IDs in the ts-sdk task registry
    
    * Validate Dag and task ID length in the ts-sdk task registry
---
 ts-sdk/src/sdk/registry.ts        | 23 ++++++++++++++++------
 ts-sdk/tests/sdk/registry.test.ts | 41 +++++++++++++++++++++++++++++++++++++--
 2 files changed, 56 insertions(+), 8 deletions(-)

diff --git a/ts-sdk/src/sdk/registry.ts b/ts-sdk/src/sdk/registry.ts
index 4d701721dae..122be56e02d 100644
--- a/ts-sdk/src/sdk/registry.ts
+++ b/ts-sdk/src/sdk/registry.ts
@@ -19,6 +19,21 @@
 
 import type { TaskHandler } from "./task.js";
 
+// Mirrors the Python task-SDK KEY_REGEX and validate_key in 
airflow.sdk.definitions._internal.node.
+const KEY_REGEX = /^[\p{L}\p{N}_.-]+$/u;
+const MAX_KEY_LENGTH = 250;
+
+function validateKey(name: string, value: string): void {
+  if (typeof value !== "string" || !KEY_REGEX.test(value)) {
+    throw new Error(
+      `${name} must be made of alphanumeric characters, dashes, dots, and 
underscores`,
+    );
+  }
+  if (value.length > MAX_KEY_LENGTH) {
+    throw new Error(`${name} must be less than ${MAX_KEY_LENGTH} characters, 
not ${value.length}`);
+  }
+}
+
 /** Identifies the Airflow task handled by a TypeScript function. */
 export interface TaskRegistration {
   /** Identifier of the Dag containing this task. */
@@ -39,12 +54,8 @@ export class TaskRegistry {
    */
   register<TReturn = unknown>(registration: TaskRegistration, handler: 
TaskHandler<TReturn>): void {
     const { dagId, taskId } = registration;
-    if (!dagId || typeof dagId !== "string") {
-      throw new Error("dagId must be a non-empty string");
-    }
-    if (!taskId || typeof taskId !== "string") {
-      throw new Error("taskId must be a non-empty string");
-    }
+    validateKey("dagId", dagId);
+    validateKey("taskId", taskId);
     if (typeof handler !== "function") {
       throw new Error(`handler for Dag "${dagId}" task "${taskId}" must be a 
function`);
     }
diff --git a/ts-sdk/tests/sdk/registry.test.ts 
b/ts-sdk/tests/sdk/registry.test.ts
index 8cadc09c4f9..4597f1d0845 100644
--- a/ts-sdk/tests/sdk/registry.test.ts
+++ b/ts-sdk/tests/sdk/registry.test.ts
@@ -73,14 +73,51 @@ describe("registry", () => {
     const registry = new TaskRegistry();
     expect(() =>
       registry.register({ dagId: "", taskId: "my_task" }, async () => 
undefined),
-    ).toThrowError(/dagId must be a non-empty string/);
+    ).toThrowError(/dagId must be made of alphanumeric/);
   });
 
   it("rejects an empty taskId", () => {
     const registry = new TaskRegistry();
     expect(() =>
       registry.register({ dagId: "example_dag", taskId: "" }, async () => 
undefined),
-    ).toThrowError(/taskId must be a non-empty string/);
+    ).toThrowError(/taskId must be made of alphanumeric/);
+  });
+
+  it.each(["   ", "\t", "my dag", "a/b", "task@1"])(
+    "rejects a dagId with characters no Python dag_id allows: %j",
+    (dagId) => {
+      const registry = new TaskRegistry();
+      expect(() =>
+        registry.register({ dagId, taskId: "my_task" }, async () => undefined),
+      ).toThrowError(/dagId must be made of alphanumeric/);
+    },
+  );
+
+  it.each(["   ", "\t", "my task", "a/b", "task@1"])(
+    "rejects a taskId with characters no Python task_id allows: %j",
+    (taskId) => {
+      const registry = new TaskRegistry();
+      expect(() =>
+        registry.register({ dagId: "example_dag", taskId }, async () => 
undefined),
+      ).toThrowError(/taskId must be made of alphanumeric/);
+    },
+  );
+
+  it.each([
+    ["dagId", { dagId: "d".repeat(251), taskId: "my_task" }],
+    ["taskId", { dagId: "example_dag", taskId: "t".repeat(251) }],
+  ])("rejects a %s longer than 250 characters", (name, registration) => {
+    const registry = new TaskRegistry();
+    expect(() => registry.register(registration, async () => 
undefined)).toThrowError(
+      new RegExp(`${name} must be less than 250 characters, not 251`),
+    );
+  });
+
+  it("accepts a Unicode dagId that Python's word-character rule allows", () => 
{
+    const registry = new TaskRegistry();
+    const handler = async () => undefined;
+    registry.register({ dagId: "café_dag", taskId: "任務" }, handler);
+    expect(registry.get("café_dag", "任務")).toBe(handler);
   });
 
   it("rejects non-function handlers", () => {

Reply via email to