Files
overleaf-cep/libraries/validation-tools/zodHelpers.js
Jakob Ackermann 37cc65ec7e [web] consolidate clsi downloads and add zod validation (#33069)
* [web] consolidate clsi downloads and add zod validation

* [validation-tools] make prettier happy

* [web] make clsiServerId optional

* [web] fix type of buildId

* [web] gracefully handle ObjectId

* [web] fix type of buildId

* [monorepo] address review feedback

- cjs export
- update module path in comments
- skip adding ?clsiserverid if not set
- allow nested output file download for submissions and add tests

* [web] address review feedback

* [web] cache one more zod schema

* [web] fix unit tests

GitOrigin-RevId: 0a1e618955983e035defd6d3c0528b81e0e85c95
2026-05-05 08:06:05 +00:00

64 lines
2.0 KiB
JavaScript

const { z } = require('zod')
const mongodb = require('mongodb')
const { ObjectId } = mongodb
/**
* @import { DatetimeSchemaOptions } from './types'
*/
/**
* @param {DatetimeSchemaOptions} options
*/
const datetimeSchema = ({ allowNull, allowUndefined, ...zodOptions } = {}) => {
const union = [z.date(), z.iso.datetime(zodOptions)]
if (allowNull) union.push(z.null())
if (allowUndefined) union.push(z.undefined())
return z.union(union).transform(dt => {
if (allowNull && !dt) return dt === null ? null : undefined
return dt instanceof Date ? dt : new Date(dt)
})
}
const zz = {
objectId: () =>
z.string().refine(ObjectId.isValid, { message: 'invalid Mongo ObjectId' }),
coercedObjectId: () =>
z
.string()
.refine(ObjectId.isValid, { message: 'invalid Mongo ObjectId' })
.transform(val => new ObjectId(val)),
hex: () => z.string().regex(/^[0-9a-f]*$/),
datetime: options => datetimeSchema(options),
datetimeNullable: options => datetimeSchema({ ...options, allowNull: true }),
datetimeNullish: options =>
datetimeSchema({ ...options, allowNull: true, allowUndefined: true }),
buildId: () =>
z.string().regex(/^[0-9a-f]+-[0-9a-f]+$/, { message: 'invalid buildId' }),
editorBuildId: () =>
z.string().regex(/^[a-f0-9-]{36}-[0-9a-f]+-[0-9a-f]+$/, {
message: 'invalid editorId-buildId',
}),
clsiServerId: () =>
z.string().regex(/^[a-z0-9-]+$/, { message: 'invalid clsiServerId' }),
compileBackendClass: () =>
z
.string()
.regex(/^[a-z0-9-]+$/, { message: 'invalid compileBackendClass' }),
compileGroup: () =>
z.enum(['alpha', 'gvisor', 'standard', 'priority'], {
message: 'invalid compileGroup',
}),
submissionId: () => z.string().regex(/^[a-zA-Z0-9_-]+$/),
filepath: () =>
z
.string()
.nonempty({ message: 'path is empty' })
.refine(s => !s.startsWith('/'), { message: 'path is absolute' })
.refine(s => !s.split('/').includes('..'), {
message: 'path traversal detected',
}),
}
module.exports = { zz }