Skip to content

Commit

Permalink
refactor: validate objects with zod
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolassutter committed Dec 23, 2022
1 parent 8974752 commit acf048f
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 37 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"react-router-dom": "^6.4.5",
"react-use": "^17.4.0",
"swiper": "^8.4.5",
"uuid": "^9.0.0"
"uuid": "^9.0.0",
"zod": "^3.20.2"
},
"devDependencies": {
"@commitlint/cli": "^17.3.0",
Expand Down
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

91 changes: 55 additions & 36 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,72 @@
import type { JSX } from 'preact'
import MarkdownIt from 'markdown-it'
import type { MediaType, Movie, Person, Season, Show, Slug } from '#types'
import { z } from 'zod'

export async function makePromise<T extends (...args: any[]) => Promise<any>>(
cb: T,
) {
return cb()
const movieShape = z
.object({
// A movie has a runtime, shows and others do not
runtime: z.number().nullable(),
})
.or(
z.object({
media_type: z.literal('movie'),
}),
)

const showShape = z
.object({
number_of_episodes: z.number(),
})
.or(
z.object({
media_type: z.literal('tv'),
}),
)

const seasonShape = z.object({
season_number: z.number(),
poster_path: z.number().nullable(),
overview: z.string(),
})

const personShape = z
.object({
profile_path: z.string().nullable(),
})
.or(
z.object({
media_type: z.literal('person'),
}),
)

const matches = <T>(cb: () => T) => {
try {
cb()
return true
} catch (error) {
return false
}
}

export function isMovie(param: Movie | Show | Person): param is Movie {
return (
// A movie cannot have number_of_episodes
!('number_of_episodes' in param) &&
// Either media_type is not in param
(!('media_type' in param) ||
// Or media_type is defined and is 'movie'
('media_type' in param && param.media_type === 'movie'))
)
return matches(() => movieShape.parse(param))
}

export function isShow(param: Movie | Show | Person): param is Show {
return (
'number_of_episodes' in param ||
('media_type' in param && param.media_type === 'tv')
)
return matches(() => showShape.parse(param))
}

export function isSeason(param: unknown): param is Season {
return Boolean(
typeof param === 'object' &&
param &&
'season_number' in param &&
'poster_path' in param &&
'overview' in param,
)
return matches(() => seasonShape.parse(param))
}

export function isPerson(param: Movie | Show | Person): param is Person {
return (
'profile_path' in param ||
('media_type' in param && param.media_type === 'person')
)
return matches(() => personShape.parse(param))
}

export function classesInAttrs(attrs?: JSX.HTMLAttributes<any>) {
return clsx(attrs?.class, attrs?.className)
}
export const classesInAttrs = (attrs?: JSX.HTMLAttributes<any>) =>
clsx(attrs?.class, attrs?.className)

export function getProfilePicture<T extends { profile_path?: string | null }>(
person: T,
Expand All @@ -71,12 +92,10 @@ export function getStillPicture<T extends { still_path?: string | null }>(
: undefined
}

export const isMediaType = (param: unknown): param is MediaType => {
return (['movie', 'tv'] as MediaType[]).includes(param as any)
}
export const isMediaType = (param: unknown): param is MediaType =>
(['movie', 'tv'] as MediaType[]).includes(param as any)

export const isValidSlug = (slug: unknown): slug is Slug => {
return ['popular', 'top_rated', 'discover', 'upcoming'].includes(slug as any)
}
export const isValidSlug = (slug: unknown): slug is Slug =>
['popular', 'top_rated', 'discover', 'upcoming'].includes(slug as any)

export const md = new MarkdownIt()

0 comments on commit acf048f

Please sign in to comment.