Z
Zod•3mo ago
Steve

Steve - Would this be valid, or would it possib...

Would this be valid, or would it possibly parse data as unknown while there could be a more specific match?
import { ContentType } from '@/api/ContentType'
import { TipTapDocumentDto } from '@/tiptap/schemas/translations/Document'
import { Document } from '@toegang-voor-iedereen/document-spec-types'
import { z } from 'zod'

const responseStarted = z.object({ status: z.literal('started') })
const responseDoneBase = z.object({
status: z.literal('done'),
id: z.string().min(1),
// Can be string, can be JSON.
})

const responseDoneTiptap = responseDoneBase.extend({
dataType: z.literal(ContentType.Tiptap),
data: TipTapDocumentDto,
})

const responseDoneHTML = responseDoneBase.extend({
dataType: z.literal(ContentType.HTML),
data: z.string(),
})

const responseDoneSpec = responseDoneBase.extend({
dataType: z.literal(ContentType.DocumentSpecification),
data: Document,
})

const responseDoneUnknown = responseDoneBase.extend({
dataType: z.string(),
data: z.unknown(), // TODO: change to z.string() | JSON
})

export const ConversionResponse = z.union([
responseStarted,
responseDoneTiptap,
responseDoneHTML,
responseDoneSpec,
responseDoneUnknown,
])

export type ConversionResponse = z.infer<typeof ConversionResponse>
import { ContentType } from '@/api/ContentType'
import { TipTapDocumentDto } from '@/tiptap/schemas/translations/Document'
import { Document } from '@toegang-voor-iedereen/document-spec-types'
import { z } from 'zod'

const responseStarted = z.object({ status: z.literal('started') })
const responseDoneBase = z.object({
status: z.literal('done'),
id: z.string().min(1),
// Can be string, can be JSON.
})

const responseDoneTiptap = responseDoneBase.extend({
dataType: z.literal(ContentType.Tiptap),
data: TipTapDocumentDto,
})

const responseDoneHTML = responseDoneBase.extend({
dataType: z.literal(ContentType.HTML),
data: z.string(),
})

const responseDoneSpec = responseDoneBase.extend({
dataType: z.literal(ContentType.DocumentSpecification),
data: Document,
})

const responseDoneUnknown = responseDoneBase.extend({
dataType: z.string(),
data: z.unknown(), // TODO: change to z.string() | JSON
})

export const ConversionResponse = z.union([
responseStarted,
responseDoneTiptap,
responseDoneHTML,
responseDoneSpec,
responseDoneUnknown,
])

export type ConversionResponse = z.infer<typeof ConversionResponse>
Solution:
order matters in union definitions (they're run in series one after the other), so as long as you put the more specific ones first, it'll match the more specific ones and not the unknown one.
Jump to solution
3 Replies
Solution
Scott Trinh
Scott Trinh•3mo ago
order matters in union definitions (they're run in series one after the other), so as long as you put the more specific ones first, it'll match the more specific ones and not the unknown one.
Scott Trinh
Scott Trinh•3mo ago
The actual type you get from this is going to be annoying to deal with in that it cannot be a discriminated union on the dataType given that it'll be | string. I've dealt with similar issues before by parsing without the unknown fallthrough and doing a second parse with the unknown fallthrough in the error case. That keeps the happy-path with a nice type, and the unhappy path is still relatively loose, while still catching structural issues overall.
Steve
SteveOP•3mo ago
@Scott Trinh refactored a bit, got something valid out PS we are making it to the deadline 🙂
Want results from more Discord servers?
Add your server