[docstore] replace remaining Joi usage with zod (#34603)

Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
GitOrigin-RevId: 41ca0c35d59bf6accd619f6b5149be14a69e1078
This commit is contained in:
Copilot
2026-06-16 08:07:22 +00:00
committed by Copybot
co-authored by Jakob Ackermann
parent 6f9b7031f4
commit f40cebf60b
7 changed files with 37 additions and 51 deletions
+2
View File
@@ -29,6 +29,7 @@ COPY libraries/object-persistor/package.json /overleaf/libraries/object-persisto
COPY libraries/promise-utils/package.json /overleaf/libraries/promise-utils/package.json
COPY libraries/settings/package.json /overleaf/libraries/settings/package.json
COPY libraries/stream-utils/package.json /overleaf/libraries/stream-utils/package.json
COPY libraries/validation-tools/package.json /overleaf/libraries/validation-tools/package.json
COPY services/docstore/package.json /overleaf/services/docstore/package.json
COPY tools/migrations/package.json /overleaf/tools/migrations/package.json
COPY .yarn/patches/ /overleaf/.yarn/patches/
@@ -45,6 +46,7 @@ COPY libraries/object-persistor/ /overleaf/libraries/object-persistor/
COPY libraries/promise-utils/ /overleaf/libraries/promise-utils/
COPY libraries/settings/ /overleaf/libraries/settings/
COPY libraries/stream-utils/ /overleaf/libraries/stream-utils/
COPY libraries/validation-tools/ /overleaf/libraries/validation-tools/
COPY services/docstore/ /overleaf/services/docstore/
COPY tools/migrations/ /overleaf/tools/migrations/
+1
View File
@@ -26,6 +26,7 @@ IMAGE_CACHE ?= $(IMAGE_REPO):cache-$(shell cat \
$(MONOREPO)/libraries/promise-utils/package.json \
$(MONOREPO)/libraries/settings/package.json \
$(MONOREPO)/libraries/stream-utils/package.json \
$(MONOREPO)/libraries/validation-tools/package.json \
$(MONOREPO)/services/docstore/package.json \
$(MONOREPO)/tools/migrations/package.json \
$(MONOREPO)/.yarn/patches/* \
+2 -13
View File
@@ -7,11 +7,7 @@ import Settings from '@overleaf/settings'
import logger from '@overleaf/logger'
import express from 'express'
import bodyParser from 'body-parser'
import {
celebrate as validate,
Joi,
errors as handleValidationErrors,
} from 'celebrate'
import { handleValidationError } from '@overleaf/validation-tools'
import mongodb from './app/js/mongodb.js'
import Errors from './app/js/Errors.js'
import HttpController from './app/js/HttpController.js'
@@ -79,13 +75,6 @@ app.post(
app.patch(
'/project/:project_id/doc/:doc_id',
bodyParser.json(),
validate({
body: {
deleted: Joi.boolean(),
name: Joi.string().when('deleted', { is: true, then: Joi.required() }),
deletedAt: Joi.date().when('deleted', { is: true, then: Joi.required() }),
},
}),
HttpController.patchDoc
)
app.delete('/project/:project_id/doc/:doc_id', (req, res) => {
@@ -101,7 +90,7 @@ app.get('/health_check', HttpController.healthCheck)
app.get('/status', (req, res) => res.send('docstore is alive'))
app.use(handleValidationErrors())
app.use(handleValidationError)
app.use(function (error, req, res, next) {
if (error instanceof Errors.NotFoundError) {
logger.warn({ req }, 'not found')
+12 -10
View File
@@ -5,6 +5,17 @@ import HealthChecker from './HealthChecker.js'
import Errors from './Errors.js'
import Settings from '@overleaf/settings'
import { expressify } from '@overleaf/promise-utils'
import { parseReq, z } from '@overleaf/validation-tools'
const patchDocSchema = z.object({
body: z
.object({
deleted: z.literal(true),
deletedAt: z.coerce.date(),
name: z.string(),
})
.strict(),
})
async function getDoc(req, res) {
const { doc_id: docId, project_id: projectId } = req.params
@@ -182,18 +193,9 @@ async function updateDoc(req, res) {
}
async function patchDoc(req, res) {
const { body: meta } = parseReq(req, patchDocSchema)
const { doc_id: docId, project_id: projectId } = req.params
logger.debug({ projectId, docId }, 'patching doc')
const allowedFields = ['deleted', 'deletedAt', 'name']
const meta = {}
Object.entries(req.body).forEach(([field, value]) => {
if (allowedFields.includes(field)) {
meta[field] = value
} else {
logger.fatal({ field }, 'joi validation for pathDoc is broken')
}
})
await DocManager.patchDoc(projectId, docId, meta)
res.sendStatus(204)
}
+1 -1
View File
@@ -25,10 +25,10 @@
"@overleaf/promise-utils": "workspace:*",
"@overleaf/settings": "workspace:*",
"@overleaf/stream-utils": "^0.1.0",
"@overleaf/validation-tools": "workspace:*",
"async": "^3.2.5",
"body-parser": "1.20.4",
"bunyan": "^1.8.15",
"celebrate": "^15.0.3",
"express": "4.22.1",
"lodash": "^4.18.1",
"mongodb-legacy": "6.1.3",
@@ -486,7 +486,11 @@ describe('HttpController', () => {
project_id: ctx.projectId,
doc_id: ctx.docId,
}
ctx.req.body = { name: 'foo.tex' }
ctx.req.body = {
deleted: true,
deletedAt: '2026-06-15T00:00:00Z',
name: 'foo.tex',
}
ctx.DocManager.patchDoc = sinon.stub().resolves()
await ctx.HttpController.patchDoc(ctx.req, ctx.res, ctx.next)
})
@@ -494,7 +498,12 @@ describe('HttpController', () => {
it('should delete the document', ctx => {
expect(ctx.DocManager.patchDoc).to.have.been.calledWith(
ctx.projectId,
ctx.docId
ctx.docId,
{
deleted: true,
deletedAt: new Date('2026-06-15T00:00:00Z'),
name: 'foo.tex',
}
)
})
@@ -510,19 +519,13 @@ describe('HttpController', () => {
await ctx.HttpController.patchDoc(ctx.req, ctx.res, ctx.next)
})
it('should log a message', ctx => {
expect(ctx.logger.fatal).to.have.been.calledWith(
{ field: 'cannot' },
'joi validation for pathDoc is broken'
)
it('should pass a validation error to next', ctx => {
expect(ctx.next).to.have.been.calledOnce
expect(ctx.next.firstCall.args[0].name).to.equal('InvalidRequestError')
})
it('should not pass the invalid field along', ctx => {
expect(ctx.DocManager.patchDoc).to.have.been.calledWith(
ctx.projectId,
ctx.docId,
{}
)
it('should not patch the document', ctx => {
expect(ctx.DocManager.patchDoc).not.to.have.been.called
})
})
})
+3 -14
View File
@@ -6585,10 +6585,10 @@ __metadata:
"@overleaf/promise-utils": "workspace:*"
"@overleaf/settings": "workspace:*"
"@overleaf/stream-utils": "npm:^0.1.0"
"@overleaf/validation-tools": "workspace:*"
async: "npm:^3.2.5"
body-parser: "npm:1.20.4"
bunyan: "npm:^1.8.15"
celebrate: "npm:^15.0.3"
chai: "npm:^4.3.6"
chai-as-promised: "npm:^7.1.1"
express: "npm:4.22.1"
@@ -14578,17 +14578,6 @@ __metadata:
languageName: node
linkType: hard
"celebrate@npm:^15.0.3":
version: 15.0.3
resolution: "celebrate@npm:15.0.3"
dependencies:
escape-html: "npm:1.0.3"
joi: "npm:17.x.x"
lodash: "npm:4.17.x"
checksum: 10c0/f1fdbb3b4eb84d9caef64345763809ece8b3bdf2ade752b91eee3bfba0a174377c0843d52db686b4d8f426eea51c55bc0a9ee2f8f989a9a2898158a1d63595c9
languageName: node
linkType: hard
"chai-as-promised@npm:^7.1.1":
version: 7.1.2
resolution: "chai-as-promised@npm:7.1.2"
@@ -17664,7 +17653,7 @@ __metadata:
languageName: node
linkType: hard
"escape-html@npm:1.0.3, escape-html@npm:^1.0.3, escape-html@npm:~1.0.3":
"escape-html@npm:^1.0.3, escape-html@npm:~1.0.3":
version: 1.0.3
resolution: "escape-html@npm:1.0.3"
checksum: 10c0/524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3
@@ -22028,7 +22017,7 @@ __metadata:
languageName: node
linkType: hard
"joi@npm:17.x.x, joi@npm:^17.6.4":
"joi@npm:^17.6.4":
version: 17.13.3
resolution: "joi@npm:17.13.3"
dependencies: