mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
Merge pull request #29930 from overleaf/mfb-fix-zod-iso-datetime-error
allow iso date time string with offset on zod validation. add unit te… GitOrigin-RevId: 88407fe681a66d13737de41789a9ea807a23627a
This commit is contained in:
committed by
Copybot
parent
06f696ced0
commit
dab59520c3
@@ -41,4 +41,180 @@ describe('zodHelpers', () => {
|
|||||||
expect(parsed.data?.toString()).toBe('507f1f77bcf86cd799439011')
|
expect(parsed.data?.toString()).toBe('507f1f77bcf86cd799439011')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe('datetime', () => {
|
||||||
|
it('parses valid ISO 8601 datetime strings', () => {
|
||||||
|
const parsed = zz.datetime().safeParse('2024-01-01T12:00:00Z')
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
expect(parsed.data).toEqual(new Date('2024-01-01T12:00:00Z'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parses a valid ISO 8601 datetime with offset', () => {
|
||||||
|
const parsed = zz
|
||||||
|
.datetime({ offset: true })
|
||||||
|
.safeParse('2024-01-01T12:00:00+00:00')
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
expect(parsed.data).toEqual(new Date('2024-01-01T12:00:00+00:00'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parses a valid Date object', () => {
|
||||||
|
const date = new Date('2024-01-01T12:00:00Z')
|
||||||
|
const parsed = zz.datetime().safeParse(date)
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
expect(parsed.data).toEqual(date)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails to parse datetime with offset when offset option is false', () => {
|
||||||
|
const parsed = zz
|
||||||
|
.datetime({ offset: false })
|
||||||
|
.safeParse('2024-01-01T12:00:00+00:00')
|
||||||
|
expect(parsed.success).toBe(false)
|
||||||
|
expect(parsed.error?.issues).toHaveLength(1)
|
||||||
|
expect(parsed.error?.issues).toMatchObject([
|
||||||
|
expect.objectContaining({
|
||||||
|
code: 'invalid_format',
|
||||||
|
format: 'datetime',
|
||||||
|
message: 'Invalid ISO datetime',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails to parse null when schema is not nullable', () => {
|
||||||
|
const parsed = zz.datetime().safeParse(null)
|
||||||
|
expect(parsed.success).toBe(false)
|
||||||
|
expect(parsed.error?.message).toContain(
|
||||||
|
'Invalid input: expected date, received null'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails to parse invalid datetime strings', () => {
|
||||||
|
const parsed = zz.datetime().safeParse('invalid-datetime')
|
||||||
|
expect(parsed.success).toBe(false)
|
||||||
|
expect(parsed.error?.issues).toHaveLength(1)
|
||||||
|
expect(parsed.error?.issues).toMatchObject([
|
||||||
|
expect.objectContaining({
|
||||||
|
code: 'invalid_format',
|
||||||
|
format: 'datetime',
|
||||||
|
message: 'Invalid ISO datetime',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('datetimeNullable', () => {
|
||||||
|
it('parses valid ISO 8601 datetime strings', () => {
|
||||||
|
const parsed = zz.datetimeNullable().safeParse('2024-01-01T12:00:00Z')
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
expect(parsed.data).toEqual(new Date('2024-01-01T12:00:00Z'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parses a valid ISO 8601 datetime with offset', () => {
|
||||||
|
const parsed = zz
|
||||||
|
.datetimeNullable({ offset: true })
|
||||||
|
.safeParse('2024-01-01T12:00:00+00:00')
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
expect(parsed.data).toEqual(new Date('2024-01-01T12:00:00+00:00'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parses a valid Date object', () => {
|
||||||
|
const date = new Date('2024-01-01T12:00:00Z')
|
||||||
|
const parsed = zz.datetimeNullable().safeParse(date)
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
expect(parsed.data).toEqual(date)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails to parse datetime with offset when offset option is false', () => {
|
||||||
|
const parsed = zz
|
||||||
|
.datetimeNullable({ offset: false })
|
||||||
|
.safeParse('2024-01-01T12:00:00+00:00')
|
||||||
|
expect(parsed.success).toBe(false)
|
||||||
|
expect(parsed.error?.issues).toHaveLength(1)
|
||||||
|
expect(parsed.error?.issues).toMatchObject([
|
||||||
|
expect.objectContaining({
|
||||||
|
code: 'invalid_format',
|
||||||
|
format: 'datetime',
|
||||||
|
message: 'Invalid ISO datetime',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parses null when schema is nullable and input is null', () => {
|
||||||
|
const parsed = zz.datetimeNullable().safeParse(null)
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
expect(parsed.data).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails to parse invalid datetime strings', () => {
|
||||||
|
const parsed = zz.datetimeNullable().safeParse('invalid-datetime')
|
||||||
|
expect(parsed.success).toBe(false)
|
||||||
|
expect(parsed.error?.issues).toHaveLength(1)
|
||||||
|
expect(parsed.error?.issues).toMatchObject([
|
||||||
|
expect.objectContaining({
|
||||||
|
code: 'invalid_format',
|
||||||
|
format: 'datetime',
|
||||||
|
message: 'Invalid ISO datetime',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('datetimeNullish', () => {
|
||||||
|
it('parses valid ISO 8601 datetime strings', () => {
|
||||||
|
const parsed = zz.datetimeNullish().safeParse('2024-01-01T12:00:00Z')
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
expect(parsed.data).toEqual(new Date('2024-01-01T12:00:00Z'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parses a valid ISO 8601 datetime with offset', () => {
|
||||||
|
const parsed = zz
|
||||||
|
.datetimeNullish({ offset: true })
|
||||||
|
.safeParse('2024-01-01T12:00:00+00:00')
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
expect(parsed.data).toEqual(new Date('2024-01-01T12:00:00+00:00'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parses a valid Date object', () => {
|
||||||
|
const date = new Date('2024-01-01T12:00:00Z')
|
||||||
|
const parsed = zz.datetimeNullish().safeParse(date)
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
expect(parsed.data).toEqual(date)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parses null when schema is nullable and input is null', () => {
|
||||||
|
const parsed = zz.datetimeNullish().safeParse(null)
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
expect(parsed.data).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parses undefined when schema is nullish and input is undefined', () => {
|
||||||
|
const parsed = zz.datetimeNullish().safeParse(undefined)
|
||||||
|
expect(parsed.success).toBe(true)
|
||||||
|
expect(parsed.data).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails to parse datetime with offset when offset option is false', () => {
|
||||||
|
const parsed = zz
|
||||||
|
.datetimeNullish({ offset: false })
|
||||||
|
.safeParse('2024-01-01T12:00:00+00:00')
|
||||||
|
expect(parsed.success).toBe(false)
|
||||||
|
expect(parsed.error?.issues).toHaveLength(1)
|
||||||
|
expect(parsed.error?.issues).toMatchObject([
|
||||||
|
expect.objectContaining({
|
||||||
|
code: 'invalid_format',
|
||||||
|
format: 'datetime',
|
||||||
|
message: 'Invalid ISO datetime',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('fails to parse invalid datetime strings', () => {
|
||||||
|
const parsed = zz.datetimeNullish().safeParse('invalid-datetime')
|
||||||
|
expect(parsed.success).toBe(false)
|
||||||
|
expect(parsed.error?.issues).toHaveLength(1)
|
||||||
|
expect(parsed.error?.issues).toMatchObject([
|
||||||
|
expect.objectContaining({
|
||||||
|
code: 'invalid_format',
|
||||||
|
format: 'datetime',
|
||||||
|
message: 'Invalid ISO datetime',
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
import z from 'zod'
|
||||||
|
|
||||||
|
export interface DatetimeSchemaOptions extends z.core.$ZodISODateTimeParams {
|
||||||
|
allowNull?: boolean
|
||||||
|
allowUndefined?: boolean
|
||||||
|
}
|
||||||
@@ -3,9 +3,19 @@ const mongodb = require('mongodb')
|
|||||||
|
|
||||||
const { ObjectId } = mongodb
|
const { ObjectId } = mongodb
|
||||||
|
|
||||||
const dateWithTransform = (schema, allowNull = false) => {
|
/**
|
||||||
return schema.transform(dt => {
|
* @import { DatetimeSchemaOptions } from './types'
|
||||||
if (allowNull && !dt) return null
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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)
|
return dt instanceof Date ? dt : new Date(dt)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -19,14 +29,10 @@ const zz = {
|
|||||||
.refine(ObjectId.isValid, { message: 'invalid Mongo ObjectId' })
|
.refine(ObjectId.isValid, { message: 'invalid Mongo ObjectId' })
|
||||||
.transform(val => new ObjectId(val)),
|
.transform(val => new ObjectId(val)),
|
||||||
hex: () => z.string().regex(/^[0-9a-f]*$/),
|
hex: () => z.string().regex(/^[0-9a-f]*$/),
|
||||||
datetime: () => dateWithTransform(z.union([z.iso.datetime(), z.date()])),
|
datetime: options => datetimeSchema(options),
|
||||||
datetimeNullable: () =>
|
datetimeNullable: options => datetimeSchema({ ...options, allowNull: true }),
|
||||||
dateWithTransform(z.union([z.iso.datetime(), z.date(), z.null()]), true),
|
datetimeNullish: options =>
|
||||||
datetimeNullish: () =>
|
datetimeSchema({ ...options, allowNull: true, allowUndefined: true }),
|
||||||
dateWithTransform(
|
|
||||||
z.union([z.iso.datetime(), z.date(), z.null(), z.undefined()]),
|
|
||||||
true
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { zz }
|
module.exports = { zz }
|
||||||
|
|||||||
Reference in New Issue
Block a user