@prismicio/types-internal
Version:
Prismic types for Custom Types and Prismic Data
314 lines (288 loc) • 8.04 kB
text/typescript
import { either } from "fp-ts/lib/Either"
import * as t from "io-ts"
import { withFallback } from "io-ts-types/lib/withFallback"
import { StringOrNull } from "../../../validators"
const arrayString = (
entries:
| string
| string[]
| {
[x: string]: {
name: string
}[]
}
| undefined,
) => {
if (entries instanceof Array) {
const isValidEntries = entries.reduce(
(acc, l) => acc && typeof l === "string",
true,
)
if (isValidEntries) return t.success(entries)
}
return
}
const plainString = (
entries:
| string
| string[]
| {
[x: string]: {
name: string
}[]
}
| undefined,
) => {
if (typeof entries === "string") {
return t.success([entries])
}
return
}
const MasksArrayString = new t.Type<ReadonlyArray<string>, object, unknown>(
"MasksArrayString",
(u: unknown): u is ReadonlyArray<string> => {
return u instanceof Array
},
(u: unknown, context: t.Context) => {
return either.chain(
t.union([t.array(t.string), t.string]).validate(u, context),
(masks) => {
return arrayString(masks) || plainString(masks) || t.failure(u, context)
},
)
},
(res) => res,
)
// Field selection in content relationship fields doesn't support nested groups.
const CustomTypeLevel2FieldGroupFields = new t.Type<
readonly string[],
readonly string[]
>(
"CustomTypeLevel2FieldGroupFields",
(u: unknown): u is readonly string[] =>
Array.isArray(u) && u.every((item) => typeof item === "string"),
(u: unknown, context: t.Context) =>
either.chain(t.array(t.string).validate(u, context), (fields) => {
// duplicates not allowed
const filtered = new Set(fields)
return filtered.size === fields.length
? t.success(fields)
: t.failure(u, context, "Fields have duplicates.")
}),
(a) => a,
)
const CustomTypeLevel2Field = t.union([
t.strict({
id: t.string,
fields: CustomTypeLevel2FieldGroupFields,
}),
t.string,
])
const CustomTypeLevel2Fields = new t.Type<
readonly t.TypeOf<typeof CustomTypeLevel2Field>[],
readonly t.OutputOf<typeof CustomTypeLevel2Field>[]
>(
"CustomTypeLevel2Fields",
(u: unknown): u is readonly t.TypeOf<typeof CustomTypeLevel2Field>[] =>
Array.isArray(u) && u.every((item) => CustomTypeLevel2Field.is(item)),
(u: unknown, context: t.Context) =>
either.chain(
t.array(CustomTypeLevel2Field).validate(u, context),
(fields) => {
// duplicates not allowed
const filtered = new Set(
fields.map((field) => (typeof field === "string" ? field : field.id)),
)
return filtered.size === fields.length
? t.success(fields)
: t.failure(u, context, "Fields have duplicates.")
},
),
(a) => a,
)
const CustomTypeLevel2 = t.union([
t.string,
t.strict({
id: t.string,
fields: CustomTypeLevel2Fields,
}),
])
const CustomTypesLevel2 = new t.Type<
readonly t.TypeOf<typeof CustomTypeLevel2>[],
readonly t.OutputOf<typeof CustomTypeLevel2>[]
>(
"CustomTypesLevel2",
(u: unknown): u is readonly t.TypeOf<typeof CustomTypeLevel2>[] =>
Array.isArray(u) && u.every((item) => CustomTypeLevel2.is(item)),
(u: unknown, context: t.Context) =>
either.chain(t.array(CustomTypeLevel2).validate(u, context), (cts) => {
// duplicates not allowed
const filtered = new Set(
cts.map((ct) => (typeof ct === "string" ? ct : ct.id)),
)
return filtered.size === cts.length
? t.success(cts)
: t.failure(u, context, "Custom types have duplicates.")
}),
(a) => a,
)
const CustomTypeLevel1FieldCustomTypes = t.strict({
id: t.string,
customtypes: CustomTypesLevel2,
})
const CustomTypeLevel1FieldGroupField = t.union([
CustomTypeLevel1FieldCustomTypes,
t.string,
])
const CustomTypeLevel1FieldGroupFields = new t.Type<
readonly t.TypeOf<typeof CustomTypeLevel1FieldGroupField>[],
readonly t.OutputOf<typeof CustomTypeLevel1FieldGroupField>[]
>(
"CustomTypeLevel1FieldGroupFields",
(
u: unknown,
): u is readonly t.TypeOf<typeof CustomTypeLevel1FieldGroupField>[] =>
Array.isArray(u) &&
u.every((item) => CustomTypeLevel1FieldGroupField.is(item)),
(u: unknown, context: t.Context) =>
either.chain(
t.array(CustomTypeLevel1FieldGroupField).validate(u, context),
(fields) => {
// duplicates not allowed
const filtered = new Set(
fields.map((field) => (typeof field === "string" ? field : field.id)),
)
return filtered.size === fields.length
? t.success(fields)
: t.failure(u, context, "Fields have duplicates.")
},
),
(a) => a,
)
const CustomTypeLevel1Field = t.union([
t.strict({
id: t.string,
fields: CustomTypeLevel1FieldGroupFields,
}),
CustomTypeLevel1FieldCustomTypes,
t.string,
])
const CustomTypeLevel1Fields = new t.Type<
readonly t.TypeOf<typeof CustomTypeLevel1Field>[],
readonly t.OutputOf<typeof CustomTypeLevel1Field>[]
>(
"CustomTypeLevel1Fields",
(u: unknown): u is readonly t.TypeOf<typeof CustomTypeLevel1Field>[] =>
Array.isArray(u) && u.every((item) => CustomTypeLevel1Field.is(item)),
(u: unknown, context: t.Context) =>
either.chain(
t.array(CustomTypeLevel1Field).validate(u, context),
(fields) => {
// duplicates not allowed
const filtered = new Set(
fields.map((field) => (typeof field === "string" ? field : field.id)),
)
return filtered.size === fields.length
? t.success(fields)
: t.failure(u, context, "Fields have duplicates.")
},
),
(a) => a,
)
const CustomTypeLevel1 = t.union([
t.string,
t.strict({
id: t.string,
fields: CustomTypeLevel1Fields,
}),
])
export const CustomTypes = new t.Type<
readonly t.TypeOf<typeof CustomTypeLevel1>[],
readonly t.OutputOf<typeof CustomTypeLevel1>[]
>(
"CustomTypes",
(u: unknown): u is readonly t.TypeOf<typeof CustomTypeLevel1>[] =>
Array.isArray(u) && u.every((item) => CustomTypeLevel1.is(item)),
(u: unknown, context: t.Context) =>
either.chain(t.array(CustomTypeLevel1).validate(u, context), (cts) => {
// if a ct appears more than once as a string, we allow it (legacy)
// if a ct appears once as a string and then again via object (or vice versa), we don't allow it
// if a ct appears more than once as an object, we don't allow it
const strings = new Set<string>()
const objects = new Set<string>()
for (const ct of cts) {
let failed = false
if (typeof ct === "string") {
failed = objects.has(ct)
strings.add(ct)
} else {
const { id } = ct
failed = strings.has(id) || objects.has(id)
objects.add(id)
}
if (failed)
return t.failure(u, context, "Custom types have duplicates.")
}
if (objects.size > 1) {
return t.failure(
u,
context,
"Cannot have multiple custom types with fields selection.",
)
}
if (strings.size > 0 && objects.size > 0) {
return t.failure(
u,
context,
"Cannot mix custom types as strings and objects with fields.",
)
}
return t.success(cts)
}),
(a) => a,
)
export const LinkFieldType = "Link"
export const LinkConfig = t.exact(
t.partial({
label: StringOrNull,
useAsTitle: t.boolean,
placeholder: t.string,
select: withFallback(
t.union([
t.literal("media"),
t.literal("document"),
t.literal("web"),
t.null,
]),
null,
),
customtypes: CustomTypes,
masks: MasksArrayString, // legacy definition of the custom types allowed
tags: MasksArrayString,
allowTargetBlank: t.boolean,
allowText: t.boolean,
/**
* `repeat` property is used to allow multiple links to be added.
* `undefined` means that the field is not repeatable (hence repeat = false).
*/
repeat: t.boolean,
/**
* `variants` allows an option to be picked from a list (e.g. "primary"). To
* be considered, the list must have at least one item.
*/
variants: t.array(t.string),
}),
)
export type LinkConfig = t.TypeOf<typeof LinkConfig>
export const Link = t.exact(
t.intersection([
t.type({
type: t.literal(LinkFieldType),
}),
t.partial({
fieldset: StringOrNull,
config: LinkConfig,
}),
]),
)
export type Link = t.TypeOf<typeof Link>