zodHelpers.js 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. const { z } = require('zod')
  2. const mongodb = require('mongodb')
  3. const { ObjectId } = mongodb
  4. /**
  5. * @import { DatetimeSchemaOptions } from './types'
  6. */
  7. /**
  8. * @param {DatetimeSchemaOptions} options
  9. */
  10. const datetimeSchema = ({ allowNull, allowUndefined, ...zodOptions } = {}) => {
  11. const union = [z.date(), z.iso.datetime(zodOptions)]
  12. if (allowNull) union.push(z.null())
  13. if (allowUndefined) union.push(z.undefined())
  14. return z.union(union).transform(dt => {
  15. if (allowNull && !dt) return dt === null ? null : undefined
  16. return dt instanceof Date ? dt : new Date(dt)
  17. })
  18. }
  19. const zz = {
  20. objectId: () =>
  21. z.string().refine(ObjectId.isValid, { message: 'invalid Mongo ObjectId' }),
  22. coercedObjectId: () =>
  23. z
  24. .string()
  25. .refine(ObjectId.isValid, { message: 'invalid Mongo ObjectId' })
  26. .transform(val => new ObjectId(val)),
  27. hex: () => z.string().regex(/^[0-9a-f]*$/),
  28. datetime: options => datetimeSchema(options),
  29. datetimeNullable: options => datetimeSchema({ ...options, allowNull: true }),
  30. datetimeNullish: options =>
  31. datetimeSchema({ ...options, allowNull: true, allowUndefined: true }),
  32. buildId: () =>
  33. z.string().regex(/^[0-9a-f]+-[0-9a-f]+$/, { message: 'invalid buildId' }),
  34. editorBuildId: () =>
  35. z.string().regex(/^[a-f0-9-]{36}-[0-9a-f]+-[0-9a-f]+$/, {
  36. message: 'invalid editorId-buildId',
  37. }),
  38. clsiServerId: () =>
  39. z.string().regex(/^[a-z0-9-]+$/, { message: 'invalid clsiServerId' }),
  40. compileBackendClass: () =>
  41. z
  42. .string()
  43. .regex(/^[a-z0-9-]+$/, { message: 'invalid compileBackendClass' }),
  44. compileGroup: () =>
  45. z.enum(['alpha', 'gvisor', 'standard', 'priority'], {
  46. message: 'invalid compileGroup',
  47. }),
  48. submissionId: () => z.string().regex(/^[a-zA-Z0-9_-]+$/),
  49. filepath: () =>
  50. z
  51. .string()
  52. .nonempty({ message: 'path is empty' })
  53. .refine(s => !s.startsWith('/'), { message: 'path is absolute' })
  54. .refine(s => !s.split('/').includes('..'), {
  55. message: 'path traversal detected',
  56. }),
  57. }
  58. module.exports = { zz }