@prismicio/types-internal
Version:
Prismic types for Custom Types and Prismic Data
652 lines (584 loc) • 15.3 kB
text/typescript
/**
* LinkContent is a union of different link content types.
*
* Most link content types are built from a union of a filled and an unfilled version.
*
* Filled versions make sure that the important fields that make the link work
* are present. They are also exported for when there's a need to ensure that
* the link is filled when used in a specific context, e.g. links in rich text fields.
*
* Unfilled versions are used when the link is not filled yet but contains other
* information like the text.
*
* There are some exceptions that don't have a filled version (e.g. MediaLink)
* for when the exact subtype is not yet known.
*
* Unfilled versions all have a special `kind` property that is used to
* determine the type of the link. For legacy reasons, filled versions don't
* necessarily require this property or its type is loose (e.g. `string`). In
* this cases, the type is determined based on a specific combination of the
* rest of the link data. This is why the order of the subtypes in the union is
* important and should be from the most specific to the least specific. A
* battery of tests ensures that the correct type is inferred.
*/
import { either } from "fp-ts"
import { pipe } from "fp-ts/lib/function"
import * as t from "io-ts"
import { withFallback } from "io-ts-types"
import { NonEmptyString, StringOrNull } from "../../../validators"
import { nullable } from "../../../validators/function"
import type { LegacyContentCtx, WithTypes } from "../../LegacyContentCtx"
import { hasContentType, withKey } from "../../utils"
// ImageLink.
export const ImageLinkType = "ImageLink"
const isImageLinkKind = (input: unknown) => typeof input === "string"
const ImageLinkKind = new t.Type<"image">(
"ImageLinkKind",
(input): input is "image" => isImageLinkKind(input),
(input, context) =>
isImageLinkKind(input)
? t.success("image" as const)
: t.failure(input, context),
() => "image" as const,
)
const filledImageLinkLegacyCodec = t.intersection([
t.strict({
kind: ImageLinkKind,
id: t.string,
url: t.string,
height: t.string,
width: t.string,
size: t.string,
name: t.string,
}),
t.exact(
t.partial({
date: StringOrNull,
}),
),
])
type FilledImageLinkLegacy = t.TypeOf<typeof filledImageLinkLegacyCodec>
const FilledImageLinkLegacy = new t.Type<
FilledImageLinkContent,
FilledImageLinkLegacy
>(
"FilledImageLink",
(u): u is FilledImageLinkContent =>
hasContentType(u) && u.__TYPE__ === ImageLinkType,
(image) => {
return pipe(
filledImageLinkLegacyCodec.decode(image),
either.map((parsedImage) => {
return FilledImageLinkContent.encode({
...parsedImage,
__TYPE__: ImageLinkType,
})
}),
)
},
(image: FilledImageLinkContent): FilledImageLinkLegacy => {
return filledImageLinkLegacyCodec.encode(image)
},
)
export const FilledImageLinkContent = t.intersection([
t.strict({
__TYPE__: t.literal(ImageLinkType),
}),
filledImageLinkLegacyCodec,
])
export type FilledImageLinkContent = t.TypeOf<typeof FilledImageLinkContent>
const imageLinkLegacyCodec = t.intersection([
t.union([
filledImageLinkLegacyCodec,
t.strict({
kind: t.literal("image"),
}),
]),
t.exact(
t.partial({
text: t.string,
variant: t.string,
}),
),
])
type ImageLinkLegacy = t.TypeOf<typeof imageLinkLegacyCodec>
const ImageLinkLegacy = new t.Type<ImageLinkContent, ImageLinkLegacy, unknown>(
"ImageLink",
(u): u is ImageLinkContent =>
hasContentType(u) && u.__TYPE__ === ImageLinkType,
(image) => {
return pipe(
imageLinkLegacyCodec.decode(image),
either.map((parsedImage) => {
return ImageLinkContent.encode({
...parsedImage,
__TYPE__: ImageLinkType,
})
}),
)
},
(image: ImageLinkContent): ImageLinkLegacy => {
return imageLinkLegacyCodec.encode(image)
},
)
export const ImageLinkContent = t.intersection([
t.strict({
__TYPE__: t.literal(ImageLinkType),
}),
imageLinkLegacyCodec,
])
export type ImageLinkContent = t.TypeOf<typeof ImageLinkContent>
// FileLink.
export const FileLinkType = "FileLink"
const isFileLinkKind = (input: unknown) => typeof input === "string"
const FileLinkKind = new t.Type<"file">(
"FileLinkKind",
(input): input is "file" => isFileLinkKind(input),
(input, context) =>
isFileLinkKind(input)
? t.success("file" as const)
: t.failure(input, context),
() => "file" as const,
)
const filledFileLinkLegacyCodec = t.exact(
t.intersection([
t.type({
kind: FileLinkKind,
id: t.string,
url: t.string,
name: t.string,
size: withFallback(t.string, "0"),
}),
t.partial({ date: StringOrNull }),
]),
)
type FilledFileLinkLegacy = t.TypeOf<typeof filledFileLinkLegacyCodec>
const FilledFileLinkLegacy = new t.Type<
FilledFileLinkContent,
FilledFileLinkLegacy
>(
"FilledFileLink",
(u): u is FilledFileLinkContent =>
hasContentType(u) && u.__TYPE__ === FileLinkType,
(file) => {
return pipe(
filledFileLinkLegacyCodec.decode(file),
either.map((parsedFile) => {
return FilledFileLinkContent.encode({
...parsedFile,
__TYPE__: FileLinkType,
})
}),
)
},
(file: FilledFileLinkContent): FilledFileLinkLegacy => {
return filledFileLinkLegacyCodec.encode(file)
},
)
export const FilledFileLinkContent = t.intersection([
t.strict({
__TYPE__: t.literal(FileLinkType),
}),
filledFileLinkLegacyCodec,
])
export type FilledFileLinkContent = t.TypeOf<typeof FilledFileLinkContent>
const fileLinkLegacyCodec = t.intersection([
t.union([
filledFileLinkLegacyCodec,
t.strict({
kind: t.literal("file"),
}),
]),
t.exact(
t.partial({
text: t.string,
variant: t.string,
}),
),
])
type FileLinkLegacy = t.TypeOf<typeof fileLinkLegacyCodec>
const FileLinkLegacy = new t.Type<FileLinkContent, FileLinkLegacy>(
"FileLink",
(u): u is FileLinkContent => hasContentType(u) && u.__TYPE__ === FileLinkType,
(file) => {
return pipe(
fileLinkLegacyCodec.decode(file),
either.map((parsedFile) => {
return FileLinkContent.encode({
...parsedFile,
__TYPE__: FileLinkType,
})
}),
)
},
(file: FileLinkContent): FileLinkLegacy => {
return fileLinkLegacyCodec.encode(file)
},
)
export const FileLinkContent = t.intersection([
t.strict({
__TYPE__: t.literal(FileLinkType),
}),
fileLinkLegacyCodec,
])
export type FileLinkContent = t.TypeOf<typeof FileLinkContent>
// MediaLink.
export const MediaLinkType = "MediaLink"
const mediaLinkLegacyCodec = t.intersection([
t.strict({
kind: t.literal("media"),
}),
t.exact(
t.partial({
text: t.string,
variant: t.string,
}),
),
])
type MediaLinkLegacy = t.TypeOf<typeof mediaLinkLegacyCodec>
const MediaLinkLegacy = new t.Type<MediaLinkContent, MediaLinkLegacy>(
"MediaLink",
(u): u is MediaLinkContent =>
hasContentType(u) && u.__TYPE__ === MediaLinkType,
(mediaLink) => {
return pipe(
mediaLinkLegacyCodec.decode(mediaLink),
either.map((parsedMediaLink) => {
return MediaLinkContent.encode({
...parsedMediaLink,
__TYPE__: MediaLinkType,
})
}),
)
},
(mediaLink: MediaLinkContent): MediaLinkLegacy => {
return mediaLinkLegacyCodec.encode(mediaLink)
},
)
export const MediaLinkContent = t.intersection([
t.strict({
__TYPE__: t.literal(MediaLinkType),
}),
mediaLinkLegacyCodec,
])
export type MediaLinkContent = t.TypeOf<typeof MediaLinkContent>
// DocumentLink.
export const DocumentLinkType = "DocumentLink"
const filledDocumentLinkLegacyCodec = t.strict({
id: NonEmptyString,
})
type FilledDocumentLinkLegacy = t.TypeOf<typeof filledDocumentLinkLegacyCodec>
const FilledDocumentLinkLegacy = new t.Type<
FilledDocumentLinkContent,
FilledDocumentLinkLegacy
>(
"FilledDocumentLink",
(u): u is FilledDocumentLinkContent =>
hasContentType(u) && u.__TYPE__ === DocumentLinkType,
(doc) => {
return pipe(
filledDocumentLinkLegacyCodec.decode(doc),
either.map((parsedDoc) => {
return FilledDocumentLinkContent.encode({
...parsedDoc,
__TYPE__: DocumentLinkType,
})
}),
)
},
(doc: FilledDocumentLinkContent): FilledDocumentLinkLegacy => {
return filledDocumentLinkLegacyCodec.encode(doc)
},
)
export const FilledDocumentLinkContent = t.intersection([
t.strict({
__TYPE__: t.literal(DocumentLinkType),
}),
filledDocumentLinkLegacyCodec,
])
export type FilledDocumentLinkContent = t.TypeOf<
typeof FilledDocumentLinkContent
>
const documentLinkLegacyCodec = t.intersection([
t.union([
filledDocumentLinkLegacyCodec,
t.strict({
kind: t.literal("document"),
}),
]),
t.exact(
t.partial({
text: t.string,
variant: t.string,
}),
),
])
type DocumentLinkLegacy = t.TypeOf<typeof documentLinkLegacyCodec>
const DocumentLinkLegacy = new t.Type<DocumentLinkContent, DocumentLinkLegacy>(
"DocumentLink",
(u): u is DocumentLinkContent =>
hasContentType(u) && u.__TYPE__ === DocumentLinkType,
(file) => {
return pipe(
documentLinkLegacyCodec.decode(file),
either.map((parsedDoc) => {
return DocumentLinkContent.encode({
...parsedDoc,
__TYPE__: DocumentLinkType,
})
}),
)
},
(doc: DocumentLinkContent): DocumentLinkLegacy => {
return documentLinkLegacyCodec.encode(doc)
},
)
export const DocumentLinkContent = t.intersection([
t.strict({
__TYPE__: t.literal(DocumentLinkType),
}),
documentLinkLegacyCodec,
])
export type DocumentLinkContent = t.TypeOf<typeof DocumentLinkContent>
// ExternalLink.
export const ExternalLinkType = "ExternalLink"
const filledExternalLinkLegacyCodec = t.exact(
t.intersection([
t.type({
url: t.string,
}),
t.partial({
kind: t.literal("web"),
target: StringOrNull,
preview: nullable(
t.partial({
title: t.string,
}),
),
}),
]),
)
type FilledExternalLinkLegacy = t.TypeOf<typeof filledExternalLinkLegacyCodec>
const FilledExternalLinkLegacy = new t.Type<
FilledExternalLinkContent,
FilledExternalLinkLegacy
>(
"FilledExternalLink",
(u): u is FilledExternalLinkContent =>
hasContentType(u) && u.__TYPE__ === ExternalLinkType,
(link) => {
return pipe(
filledExternalLinkLegacyCodec.decode(link),
either.map((parsedLink) => {
return FilledExternalLinkContent.encode({
...parsedLink,
__TYPE__: ExternalLinkType,
})
}),
)
},
(link: FilledExternalLinkContent): FilledExternalLinkLegacy => {
return filledExternalLinkLegacyCodec.encode(link)
},
)
export const FilledExternalLinkContent = t.intersection([
t.strict({
__TYPE__: t.literal(ExternalLinkType),
}),
filledExternalLinkLegacyCodec,
])
export type FilledExternalLinkContent = t.TypeOf<
typeof FilledExternalLinkContent
>
const externalLinkLegacyCodec = t.intersection([
t.union([
filledExternalLinkLegacyCodec,
t.strict({
kind: t.literal("web"),
}),
]),
t.exact(
t.partial({
text: t.string,
variant: t.string,
}),
),
])
type ExternalLinkLegacy = t.TypeOf<typeof externalLinkLegacyCodec>
const ExternalLinkLegacy = new t.Type<ExternalLinkContent, ExternalLinkLegacy>(
"ExternalLink",
(u): u is ExternalLinkContent =>
hasContentType(u) && u.__TYPE__ === ExternalLinkType,
(file) => {
return pipe(
externalLinkLegacyCodec.decode(file),
either.map((parsedLink) => {
return ExternalLinkContent.encode({
...parsedLink,
__TYPE__: ExternalLinkType,
})
}),
)
},
(link: ExternalLinkContent): ExternalLinkLegacy => {
return externalLinkLegacyCodec.encode(link)
},
)
export const ExternalLinkContent = t.intersection([
t.strict({
__TYPE__: t.literal(ExternalLinkType),
}),
externalLinkLegacyCodec,
])
export type ExternalLinkContent = t.TypeOf<typeof ExternalLinkContent>
// AnyLink.
export const AnyLinkType = "AnyLink"
const anyLinkLegacyCodec = t.exact(
t.intersection([
t.type({
kind: withFallback(t.literal("any"), "any"),
}),
t.partial({
text: t.string,
variant: t.string,
}),
]),
)
type AnyLinkLegacy = t.TypeOf<typeof anyLinkLegacyCodec>
const AnyLinkLegacy = new t.Type<AnyLinkContent, AnyLinkLegacy>(
"AnyLink",
(u): u is AnyLinkContent => hasContentType(u) && u.__TYPE__ === AnyLinkType,
(anyLink) => {
return pipe(
anyLinkLegacyCodec.decode(anyLink),
either.map((parsedAnyLink) => {
return AnyLinkContent.encode({
...parsedAnyLink,
__TYPE__: AnyLinkType,
})
}),
)
},
(anyLink: AnyLinkContent): AnyLinkLegacy => {
return anyLinkLegacyCodec.encode(anyLink)
},
)
export const AnyLinkContent = t.intersection([
t.strict({
__TYPE__: t.literal(AnyLinkType),
}),
anyLinkLegacyCodec,
])
export type AnyLinkContent = t.TypeOf<typeof AnyLinkContent>
// Link.
export const LinkContentType = "LinkContent"
export const isLinkContent = (u: unknown): u is LinkContent =>
hasContentType(u) && u.__TYPE__ === LinkContentType
export const LinkLegacy = withKey(
t.union([
ImageLinkLegacy,
FileLinkLegacy,
DocumentLinkLegacy,
ExternalLinkLegacy,
MediaLinkLegacy,
AnyLinkLegacy,
]),
)
type LinkLegacy = t.OutputOf<typeof LinkLegacy>
export const LinkContentLegacy = (ctx: LegacyContentCtx) =>
new t.Type<LinkContent, WithTypes<LinkLegacy>, unknown>(
"LinkLegacy",
isLinkContent,
(u) => {
return pipe(
LinkLegacy.decode(u),
either.map(({ key, ...value }) =>
LinkContent.encode({
__TYPE__: "LinkContent",
key,
value,
}),
),
)
},
({ key, value }: LinkContent): WithTypes<LinkLegacy> => {
return {
content: LinkLegacy.encode({
...value,
key,
}),
types: { [ctx.keyOfType]: "Link" },
keys: {},
}
},
)
export const Link = t.union([
ImageLinkContent,
FileLinkContent,
DocumentLinkContent,
ExternalLinkContent,
MediaLinkContent,
AnyLinkContent,
])
export const LinkContent = withKey(
t.strict({
__TYPE__: t.literal(LinkContentType),
value: Link,
}),
)
export type LinkContent = t.TypeOf<typeof LinkContent>
export type LinksTypes =
| LinkContent["__TYPE__"]
| LinkContent["value"]["__TYPE__"]
// FilledLink.
export const isFilledLinkContent = (u: unknown): u is FilledLinkContent =>
hasContentType(u) && u.__TYPE__ === LinkContentType
export const FilledLinkLegacy = t.union([
FilledImageLinkLegacy,
FilledFileLinkLegacy,
FilledDocumentLinkLegacy,
FilledExternalLinkLegacy,
])
type FilledLinkLegacy = t.OutputOf<typeof FilledLinkLegacy>
export const FilledLinkContentLegacy = (ctx: LegacyContentCtx) => {
const FilledLinkLegacyWithKey = withKey(FilledLinkLegacy)
return new t.Type<FilledLinkContent, WithTypes<FilledLinkLegacy>>(
"FilledLinkLegacy",
isFilledLinkContent,
(u) => {
return pipe(
FilledLinkLegacyWithKey.decode(u),
either.map(({ key, ...value }) =>
FilledLinkContent.encode({
__TYPE__: "LinkContent",
key,
value,
}),
),
)
},
({ key, value }: FilledLinkContent): WithTypes<FilledLinkLegacy> => {
return {
content: FilledLinkLegacyWithKey.encode({
...value,
key,
}),
types: { [ctx.keyOfType]: "Link" },
keys: {},
}
},
)
}
export const FilledLink = t.union([
FilledImageLinkContent,
FilledFileLinkContent,
FilledDocumentLinkContent,
FilledExternalLinkContent,
])
export const FilledLinkContent = withKey(
t.strict({
__TYPE__: t.literal(LinkContentType),
value: FilledLink,
}),
)
export type FilledLinkContent = t.TypeOf<typeof FilledLinkContent>