UNPKG

@prismicio/types-internal

Version:
652 lines (584 loc) 15.3 kB
/** * 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>