From b0d7728de3e1fd4f0a0d34cfe3b44b98619f32d9 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 7 Nov 2025 08:34:23 +0000 Subject: [PATCH] Merge pull request #29546 from overleaf/mfb-from-joi-to-zod-real-time RE MIGRATE from joi to zod, moving schemas to top level in file GitOrigin-RevId: c1512be2e7d6edf52c3dc01d62f2fc2051b3d9b2 --- package-lock.json | 5 ++- services/real-time/.jenkinsIncludeFile | 1 + services/real-time/Dockerfile | 2 + services/real-time/Makefile | 1 + services/real-time/app/js/Router.js | 42 ++++++++----------- services/real-time/package.json | 5 ++- .../test/acceptance/js/JoinDocTests.js | 2 +- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb5465c5d2..f4e1a921b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52882,6 +52882,7 @@ "@overleaf/o-error": "*", "@overleaf/redis-wrapper": "*", "@overleaf/settings": "*", + "@overleaf/validation-tools": "*", "async": "^3.2.5", "base64id": "0.1.0", "body-parser": "^1.20.3", @@ -52890,12 +52891,12 @@ "cookie-parser": "^1.4.6", "express": "^4.21.2", "express-session": "^1.17.1", - "joi": "^17.12.0", "lodash": "^4.17.21", "proxy-addr": "^2.0.7", "request": "^2.88.2", "socket.io": "github:overleaf/socket.io#0.9.19-overleaf-12", - "socket.io-client": "github:overleaf/socket.io-client#0.9.17-overleaf-5" + "socket.io-client": "github:overleaf/socket.io-client#0.9.17-overleaf-5", + "zod-validation-error": "^4.0.1" }, "devDependencies": { "chai": "^4.3.6", diff --git a/services/real-time/.jenkinsIncludeFile b/services/real-time/.jenkinsIncludeFile index 1f7a178a54..ddd8a686ec 100644 --- a/services/real-time/.jenkinsIncludeFile +++ b/services/real-time/.jenkinsIncludeFile @@ -7,6 +7,7 @@ libraries/metrics/** libraries/o-error/** libraries/redis-wrapper/** libraries/settings/** +libraries/validation-tools/** package-lock.json package.json patches/** diff --git a/services/real-time/Dockerfile b/services/real-time/Dockerfile index 2214eeecdb..2a51a7701e 100644 --- a/services/real-time/Dockerfile +++ b/services/real-time/Dockerfile @@ -19,6 +19,7 @@ COPY libraries/metrics/package.json /overleaf/libraries/metrics/package.json COPY libraries/o-error/package.json /overleaf/libraries/o-error/package.json COPY libraries/redis-wrapper/package.json /overleaf/libraries/redis-wrapper/package.json COPY libraries/settings/package.json /overleaf/libraries/settings/package.json +COPY libraries/validation-tools/package.json /overleaf/libraries/validation-tools/package.json COPY services/real-time/package.json /overleaf/services/real-time/package.json COPY patches/ /overleaf/patches/ @@ -29,6 +30,7 @@ COPY libraries/metrics/ /overleaf/libraries/metrics/ COPY libraries/o-error/ /overleaf/libraries/o-error/ COPY libraries/redis-wrapper/ /overleaf/libraries/redis-wrapper/ COPY libraries/settings/ /overleaf/libraries/settings/ +COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/ COPY services/real-time/ /overleaf/services/real-time/ FROM app diff --git a/services/real-time/Makefile b/services/real-time/Makefile index af410cd6b7..f5ebbefb89 100644 --- a/services/real-time/Makefile +++ b/services/real-time/Makefile @@ -20,6 +20,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \ $(MONOREPO)/libraries/o-error/package.json \ $(MONOREPO)/libraries/redis-wrapper/package.json \ $(MONOREPO)/libraries/settings/package.json \ + $(MONOREPO)/libraries/validation-tools/package.json \ $(MONOREPO)/services/real-time/package.json \ $(MONOREPO)/patches/* \ | sha256sum | cut -d '-' -f1) diff --git a/services/real-time/app/js/Router.js b/services/real-time/app/js/Router.js index 943453bc13..be70beb7f4 100644 --- a/services/real-time/app/js/Router.js +++ b/services/real-time/app/js/Router.js @@ -8,16 +8,23 @@ const WebsocketAddressManager = require('./WebsocketAddressManager') const bodyParser = require('body-parser') const base64id = require('base64id') const { UnexpectedArgumentsError } = require('./Errors') -const Joi = require('joi') +const { z, zz } = require('@overleaf/validation-tools') +const { isZodErrorLike } = require('zod-validation-error') const HOSTNAME = require('node:os').hostname() const SERVER_PING_INTERVAL = 15000 const SERVER_PING_LATENCY_THRESHOLD = 5000 -const JOI_OBJECT_ID = Joi.string() - .required() - .regex(/^[0-9a-f]{24}$/) - .message('invalid id') +const joinDocSchema = z.object({ + doc_id: zz.objectId(), + fromVersion: z.number().int().optional(), + options: z.object(), +}) + +const applyOtUpdateSchema = z.object({ + doc_id: zz.objectId(), + update: z.object(), +}) let Router module.exports = Router = { @@ -29,11 +36,11 @@ module.exports = Router = { attrs.client_id = client.id attrs.err = error attrs.method = method - if (Joi.isError(error)) { + if (isZodErrorLike(error)) { logger.info(attrs, 'validation error') let message = 'invalid' try { - message = error.details[0].message + message = error.issues[0].message } catch (e) { // ignore unexpected errors logger.warn({ error, e }, 'unexpected validation error') @@ -193,7 +200,7 @@ module.exports = Router = { if (!isDebugging) { try { - Joi.assert(projectId, JOI_OBJECT_ID) + zz.objectId().parse(projectId) } catch (error) { metrics.inc('socket-io.connection', 1, { status: client.transport, @@ -442,14 +449,7 @@ module.exports = Router = { return Router._handleInvalidArguments(client, 'joinDoc', arguments) } try { - Joi.assert( - { doc_id: docId, fromVersion, options }, - Joi.object({ - doc_id: JOI_OBJECT_ID, - fromVersion: Joi.number().integer(), - options: Joi.object().required(), - }) - ) + joinDocSchema.parse({ doc_id: docId, fromVersion, options }) } catch (error) { return Router._handleError(callback, error, client, 'joinDoc', { disconnect: 1, @@ -478,7 +478,7 @@ module.exports = Router = { return Router._handleInvalidArguments(client, 'leaveDoc', arguments) } try { - Joi.assert(docId, JOI_OBJECT_ID) + zz.objectId().parse(docId) } catch (error) { return Router._handleError(callback, error, client, 'joinDoc', { disconnect: 1, @@ -563,13 +563,7 @@ module.exports = Router = { ) } try { - Joi.assert( - { doc_id: docId, update }, - Joi.object({ - doc_id: JOI_OBJECT_ID, - update: Joi.object().required(), - }) - ) + applyOtUpdateSchema.parse({ doc_id: docId, update }) } catch (error) { return Router._handleError(callback, error, client, 'applyOtUpdate', { disconnect: 1, diff --git a/services/real-time/package.json b/services/real-time/package.json index 37c8dd9e2b..d416fd099a 100644 --- a/services/real-time/package.json +++ b/services/real-time/package.json @@ -22,6 +22,7 @@ "@overleaf/o-error": "*", "@overleaf/redis-wrapper": "*", "@overleaf/settings": "*", + "@overleaf/validation-tools": "*", "async": "^3.2.5", "base64id": "0.1.0", "body-parser": "^1.20.3", @@ -30,12 +31,12 @@ "cookie-parser": "^1.4.6", "express": "^4.21.2", "express-session": "^1.17.1", - "joi": "^17.12.0", "lodash": "^4.17.21", "proxy-addr": "^2.0.7", "request": "^2.88.2", "socket.io": "github:overleaf/socket.io#0.9.19-overleaf-12", - "socket.io-client": "github:overleaf/socket.io-client#0.9.17-overleaf-5" + "socket.io-client": "github:overleaf/socket.io-client#0.9.17-overleaf-5", + "zod-validation-error": "^4.0.1" }, "devDependencies": { "chai": "^4.3.6", diff --git a/services/real-time/test/acceptance/js/JoinDocTests.js b/services/real-time/test/acceptance/js/JoinDocTests.js index 3381526c59..2616c7c0e6 100644 --- a/services/real-time/test/acceptance/js/JoinDocTests.js +++ b/services/real-time/test/acceptance/js/JoinDocTests.js @@ -328,7 +328,7 @@ describe('joinDoc', function () { }) it('should return an invalid id error', function () { - this.error.message.should.equal('invalid id') + this.error.message.should.equal('invalid Mongo ObjectId') }) return it('should not have joined the doc room', function (done) {