diff --git a/package-lock.json b/package-lock.json index 5ecaee9e0e..3b8f9bd847 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "omggif": "^1.0.10", "pako": "^2.1.0", "pixelmatch": "^7.1.0", - "zod": "^3.23.8" + "zod": "^3.25.51" }, "devDependencies": { "@rollup/plugin-alias": "^5.1.1", @@ -11918,9 +11918,9 @@ } }, "node_modules/zod": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", - "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "version": "3.25.51", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.51.tgz", + "integrity": "sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 3ee0dad7ee..ce83c2fce9 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "omggif": "^1.0.10", "pako": "^2.1.0", "pixelmatch": "^7.1.0", - "zod": "^3.23.8" + "zod": "^3.25.51" }, "devDependencies": { "@rollup/plugin-alias": "^5.1.1", diff --git a/src/core/friendly_errors/param_validator.js b/src/core/friendly_errors/param_validator.js index d4fc78830a..1ec2f00354 100644 --- a/src/core/friendly_errors/param_validator.js +++ b/src/core/friendly_errors/param_validator.js @@ -3,7 +3,7 @@ * @requires core */ import * as constants from '../constants.js'; -import * as z from 'zod'; +import { z } from 'zod/v4'; import dataDoc from '../../../docs/parameterData.json'; function validateParams(p5, fn, lifecycles) { @@ -230,6 +230,10 @@ function validateParams(p5, fn, lifecycles) { param = param?.replace(/^\.\.\.(.+)\[\]$/, '$1'); let schema = generateTypeSchema(param); + if (!schema || typeof schema.optional !== 'function') { + schema = z.any(); + } + if (isOptional) { schema = schema.optional(); } @@ -318,7 +322,7 @@ function validateParams(p5, fn, lifecycles) { } const numArgs = args.length; - const schemaItems = schema.items; + const schemaItems = schema.def.items; const numSchemaItems = schemaItems.length; const numRequiredSchemaItems = schemaItems.filter(item => !item.isOptional()).length; @@ -353,11 +357,11 @@ function validateParams(p5, fn, lifecycles) { }; // Default to the first schema, so that we are guaranteed to return a result. - let closestSchema = schema._def.options[0]; + let closestSchema = schema.def.options[0]; // We want to return the schema with the lowest score. let bestScore = Infinity; - const schemaUnion = schema._def.options; + const schemaUnion = schema.def.options; schemaUnion.forEach(schema => { const score = scoreSchema(schema); if (score < bestScore) { @@ -386,7 +390,7 @@ function validateParams(p5, fn, lifecycles) { // (after scoring the schema closeness in `findClosestSchema`). Here, we // always print the first error so that user can work through the errors // one by one. - let currentError = zodErrorObj.errors[0]; + let currentError = zodErrorObj.issues[0]; // Helper function to build a type mismatch message. const buildTypeMismatchMessage = (actualType, expectedTypeStr, position) => { @@ -403,24 +407,27 @@ function validateParams(p5, fn, lifecycles) { const expectedTypes = new Set(); let actualType; - error.unionErrors.forEach(err => { - const issue = err.issues[0]; + error.errors.forEach(err => { + const issue = err[0]; if (issue) { if (!actualType) { - actualType = issue.received; + actualType = issue.message; } if (issue.code === 'invalid_type') { + actualType = issue.message.split(', received ')[1] expectedTypes.add(issue.expected); } // The case for constants. Since we don't want to print out the actual // constant values in the error message, the error message will // direct users to the documentation. - else if (issue.code === 'invalid_literal') { + else if (issue.code === 'invalid_value') { expectedTypes.add("constant (please refer to documentation for allowed values)"); + actualType = args[error.path[0]]; } else if (issue.code === 'custom') { const match = issue.message.match(/Input not instance of (\w+)/); if (match) expectedTypes.add(match[1]); + actualType = undefined } } }); @@ -452,7 +459,7 @@ function validateParams(p5, fn, lifecycles) { break; } case 'invalid_type': { - message += buildTypeMismatchMessage(currentError.received, currentError.expected, currentError.path.join('.')); + message += buildTypeMismatchMessage(currentError.message.split(', received ')[1], currentError.expected, currentError.path.join('.')); break; } case 'too_big': { diff --git a/test/unit/core/param_errors.js b/test/unit/core/param_errors.js index e2bfb7c617..26a9efe4fb 100644 --- a/test/unit/core/param_errors.js +++ b/test/unit/core/param_errors.js @@ -150,7 +150,7 @@ suite('Validate Params', function () { { fn: 'rect', name: 'null, non-trailing, optional parameter', input: [0, 0, 0, 0, null, 0, 0, 0], msg: '🌸 p5.js says: Expected number at the fifth parameter, but received null in p5.rect().' }, { fn: 'color', name: 'too many args + wrong types too', input: ['A', 'A', 0, 0, 0, 0, 0, 0, 0, 0], msg: '🌸 p5.js says: Expected at most 4 arguments, but received more in p5.color(). For more information, see https://p5js.org/reference/p5/color.' }, { fn: 'line', name: 'null string given', input: [1, 2, 4, 'null'], msg: '🌸 p5.js says: Expected number at the fourth parameter, but received string in p5.line().' }, - { fn: 'line', name: 'NaN value given', input: [1, 2, 4, NaN], msg: '🌸 p5.js says: Expected number at the fourth parameter, but received nan in p5.line().' } + { fn: 'line', name: 'NaN value given', input: [1, 2, 4, NaN], msg: '🌸 p5.js says: Expected number at the fourth parameter, but received NaN in p5.line().' } ]; invalidInputs.forEach(({ name, input, fn, msg }) => { @@ -278,4 +278,4 @@ suite('Validate Params', function () { assert.isFalse(result.success); }); }); -}); +}); \ No newline at end of file