-
Notifications
You must be signed in to change notification settings - Fork 161
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Safe parse with zod * Fix zod issue * Fix all validation errors * nit * Add tests * Add color fields * Passthrough
- Loading branch information
Showing
9 changed files
with
1,383 additions
and
14 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { z } from 'zod'; | ||
import { fromZodError } from 'zod-validation-error'; | ||
|
||
const zComfyLink = z.tuple([ | ||
z.number(), // Link id | ||
z.number(), // Node id of source node | ||
z.number(), // Output slot# of source node | ||
z.number(), // Node id of destination node | ||
z.number(), // Input slot# of destination node | ||
z.string(), // Data type | ||
]); | ||
|
||
const zNodeOutput = z.object({ | ||
name: z.string(), | ||
type: z.string(), | ||
links: z.array(z.number()).nullable(), | ||
slot_index: z.number().optional(), | ||
}).passthrough(); | ||
|
||
const zNodeInput = z.object({ | ||
name: z.string(), | ||
type: z.string(), | ||
link: z.number().nullable(), | ||
slot_index: z.number().optional(), | ||
}).passthrough(); | ||
|
||
const zFlags = z.object({ | ||
collapsed: z.boolean().optional(), | ||
pinned: z.boolean().optional(), | ||
allow_interaction: z.boolean().optional(), | ||
horizontal: z.boolean().optional(), | ||
skip_repeated_outputs: z.boolean().optional(), | ||
}).passthrough(); | ||
|
||
const zProperties = z.object({ | ||
["Node name for S&R"]: z.string().optional(), | ||
}).passthrough(); | ||
|
||
const zVector2 = z.union([ | ||
z.object({ 0: z.number(), 1: z.number() }), | ||
z.tuple([z.number(), z.number()]), | ||
]); | ||
|
||
const zComfyNode = z.object({ | ||
id: z.number(), | ||
type: z.string(), | ||
pos: z.tuple([z.number(), z.number()]), | ||
size: zVector2, | ||
flags: zFlags, | ||
order: z.number(), | ||
mode: z.number(), | ||
inputs: z.array(zNodeInput).optional(), | ||
outputs: z.array(zNodeOutput).optional(), | ||
properties: zProperties, | ||
widgets_values: z.array(z.any()).optional(), // This could contain mixed types | ||
color: z.string().optional(), | ||
bgcolor: z.string().optional(), | ||
}).passthrough(); | ||
|
||
const zGroup = z.object({ | ||
title: z.string(), | ||
bounding: z.tuple([z.number(), z.number(), z.number(), z.number()]), | ||
color: z.string(), | ||
font_size: z.number(), | ||
locked: z.boolean(), | ||
}).passthrough(); | ||
|
||
const zInfo = z.object({ | ||
name: z.string(), | ||
author: z.string(), | ||
description: z.string(), | ||
version: z.string(), | ||
created: z.string(), | ||
modified: z.string(), | ||
software: z.string(), | ||
}).passthrough(); | ||
|
||
const zDS = z.object({ | ||
scale: z.number(), | ||
offset: zVector2, | ||
}).passthrough(); | ||
|
||
const zConfig = z.object({ | ||
links_ontop: z.boolean().optional(), | ||
align_to_grid: z.boolean().optional(), | ||
}).passthrough(); | ||
|
||
const zExtra = z.object({ | ||
ds: zDS.optional(), | ||
info: zInfo.optional(), | ||
}).passthrough(); | ||
|
||
const zComfyWorkflow = z.object({ | ||
last_node_id: z.number(), | ||
last_link_id: z.number(), | ||
nodes: z.array(zComfyNode), | ||
links: z.array(zComfyLink), | ||
groups: z.array(zGroup).optional(), | ||
config: zConfig.optional().nullable(), | ||
extra: zExtra.optional().nullable(), | ||
version: z.number(), | ||
}).passthrough(); | ||
|
||
export type NodeInput = z.infer<typeof zNodeInput>; | ||
export type NodeOutput = z.infer<typeof zNodeOutput>; | ||
export type ComfyLink = z.infer<typeof zComfyLink>; | ||
export type ComfyNode = z.infer<typeof zComfyNode>; | ||
export type ComfyWorkflow = z.infer<typeof zComfyWorkflow>; | ||
|
||
|
||
export async function parseComfyWorkflow(data: string): Promise<ComfyWorkflow> { | ||
// Validate | ||
const result = await zComfyWorkflow.safeParseAsync(JSON.parse(data)); | ||
if (!result.success) { | ||
throw fromZodError(result.error); | ||
} | ||
return result.data; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { parseComfyWorkflow } from "../../src/types/comfyWorkflow"; | ||
import { defaultGraph } from "../../src/scripts/defaultGraph"; | ||
import fs from "fs"; | ||
|
||
const WORKFLOW_DIR = "tests-ui/workflows"; | ||
|
||
describe("parseComfyWorkflow", () => { | ||
it("parses valid workflow", async () => { | ||
fs.readdirSync(WORKFLOW_DIR).forEach(async (file) => { | ||
if (file.endsWith(".json")) { | ||
const data = fs.readFileSync(`${WORKFLOW_DIR}/${file}`, "utf-8"); | ||
await expect(parseComfyWorkflow(data)).resolves.not.toThrow(); | ||
} | ||
}); | ||
}); | ||
|
||
it("workflow.nodes", async () => { | ||
const workflow = JSON.parse(JSON.stringify(defaultGraph)); | ||
workflow.nodes = undefined; | ||
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow(); | ||
|
||
workflow.nodes = null; | ||
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow(); | ||
|
||
workflow.nodes = []; | ||
await expect(parseComfyWorkflow(JSON.stringify(workflow))).resolves.not.toThrow(); | ||
}); | ||
|
||
it("workflow.version", async () => { | ||
const workflow = JSON.parse(JSON.stringify(defaultGraph)); | ||
workflow.version = undefined; | ||
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow(); | ||
|
||
workflow.version = "1.0.1"; // Invalid format. | ||
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow(); | ||
|
||
workflow.version = 1; | ||
await expect(parseComfyWorkflow(JSON.stringify(workflow))).resolves.not.toThrow(); | ||
}); | ||
|
||
it("workflow.extra", async () => { | ||
const workflow = JSON.parse(JSON.stringify(defaultGraph)); | ||
workflow.extra = undefined; | ||
await expect(parseComfyWorkflow(JSON.stringify(workflow))).resolves.not.toThrow(); | ||
|
||
workflow.extra = null; | ||
await expect(parseComfyWorkflow(JSON.stringify(workflow))).resolves.not.toThrow(); | ||
|
||
workflow.extra = {}; | ||
await expect(parseComfyWorkflow(JSON.stringify(workflow))).resolves.not.toThrow(); | ||
|
||
workflow.extra = { foo: "bar" }; // Should accept extra fields. | ||
await expect(parseComfyWorkflow(JSON.stringify(workflow))).resolves.not.toThrow(); | ||
}); | ||
}); |
Oops, something went wrong.