@prismicio/types-internal 2.2.0-alpha.10 → 2.2.0-alpha.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/common/UUID.d.ts +7 -0
- package/lib/common/UUID.js +8 -0
- package/lib/content/fields/slices/Slice/RepeatableContent.js +0 -1
- package/lib/import/converters/Document.d.ts +1 -2
- package/lib/import/converters/Document.js +2 -0
- package/lib/import/converters/fields/Slices/SharedSlice.d.ts +4 -0
- package/lib/import/converters/fields/Slices/SharedSlice.js +19 -0
- package/lib/import/converters/fields/Slices/SharedSliceContent.d.ts +10 -0
- package/lib/import/converters/fields/Slices/SharedSliceContent.js +58 -0
- package/lib/import/converters/fields/Slices/Slices.d.ts +4 -0
- package/lib/import/converters/fields/Slices/Slices.js +16 -0
- package/lib/import/converters/fields/Slices/index.d.ts +1 -0
- package/lib/import/converters/fields/Slices/index.js +4 -0
- package/lib/import/converters/fields/index.d.ts +1 -0
- package/lib/import/converters/fields/index.js +1 -0
- package/lib/import/converters/fields/nestable/Nestable.d.ts +2 -2
- package/lib/import/validators/fields/ImportField.d.ts +11 -1
- package/lib/import/validators/fields/ImportField.js +6 -1
- package/lib/import/validators/fields/ImportSlices/ImportSlices.d.ts +8 -0
- package/lib/import/validators/fields/ImportSlices/ImportSlices.js +29 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/SharedSlice.d.ts +14 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/SharedSlice.js +60 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/errors.d.ts +4 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/errors.js +7 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/OptionalSharedSliceId.d.ts +2 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/OptionalSharedSliceId.js +15 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContent.d.ts +17 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContent.js +30 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContentEntry.d.ts +43 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContentEntry.js +69 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/errors.d.ts +4 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/errors.js +6 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/index.d.ts +2 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/index.js +5 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/types.d.ts +1 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/types.js +2 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceId.d.ts +9 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceId.js +39 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceType.d.ts +11 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceType.js +24 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceVariation.d.ts +11 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceVariation.js +24 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/index.d.ts +5 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/fields/index.js +8 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/index.d.ts +3 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/index.js +6 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/utils.d.ts +339 -0
- package/lib/import/validators/fields/ImportSlices/SharedSlice/utils.js +11 -0
- package/lib/import/validators/fields/ImportSlices/index.d.ts +1 -0
- package/lib/import/validators/fields/ImportSlices/index.js +4 -0
- package/lib/import/validators/fields/ImportSlices/utils.d.ts +4 -0
- package/lib/import/validators/fields/ImportSlices/utils.js +35 -0
- package/lib/utils/io-ts.d.ts +2 -0
- package/lib/utils/io-ts.js +22 -0
- package/lib/validators/BasicTypes.d.ts +1 -0
- package/lib/validators/BasicTypes.js +2 -1
- package/lib/validators/NonEmptyString.js +2 -1
- package/package.json +4 -2
- package/src/common/UUID.ts +18 -0
- package/src/content/fields/slices/Slice/RepeatableContent.ts +0 -1
- package/src/import/converters/Document.ts +8 -3
- package/src/import/converters/fields/Slices/SharedSlice.ts +24 -0
- package/src/import/converters/fields/Slices/SharedSliceContent.ts +94 -0
- package/src/import/converters/fields/Slices/Slices.ts +20 -0
- package/src/import/converters/fields/Slices/index.ts +1 -0
- package/src/import/converters/fields/index.ts +1 -0
- package/src/import/converters/fields/nestable/Nestable.ts +2 -2
- package/src/import/validators/fields/ImportField.ts +7 -2
- package/src/import/validators/fields/ImportSlices/ImportSlices.ts +54 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/SharedSlice.ts +114 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/errors.ts +6 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/fields/OptionalSharedSliceId.ts +20 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContent.ts +61 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/SharedSliceContentEntry.ts +100 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/errors.ts +10 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/index.ts +2 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/types.ts +1 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceId.ts +65 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceType.ts +45 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceVariation.ts +45 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/fields/index.ts +5 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/index.ts +3 -0
- package/src/import/validators/fields/ImportSlices/SharedSlice/utils.ts +21 -0
- package/src/import/validators/fields/ImportSlices/index.ts +1 -0
- package/src/import/validators/fields/ImportSlices/utils.ts +43 -0
- package/src/utils/io-ts.ts +29 -0
- package/src/validators/BasicTypes.ts +5 -0
- package/src/validators/NonEmptyString.ts +4 -4
- package/lib/import/validators/fields/nestable/Image.d.ts +0 -62
- package/lib/import/validators/fields/nestable/Image.js +0 -76
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import type { Asset } from "../../common"
|
|
2
|
-
import type { Embed } from "../../common/Embed"
|
|
1
|
+
import type { Asset, Embed } from "../../common"
|
|
3
2
|
import type { Document, WidgetContent } from "../../content"
|
|
4
3
|
import type { ImportDocument } from "../validators"
|
|
5
4
|
import type { ImportField } from "../validators/fields/ImportField"
|
|
6
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
convertNestableWidget,
|
|
7
|
+
importSlicesConverter,
|
|
8
|
+
uidConverter,
|
|
9
|
+
} from "./fields"
|
|
7
10
|
|
|
8
11
|
export function convertImportToContent(
|
|
9
12
|
document: ImportDocument,
|
|
@@ -27,6 +30,8 @@ function convertWidget(
|
|
|
27
30
|
switch (field.type) {
|
|
28
31
|
case "UID":
|
|
29
32
|
return uidConverter(field.value)
|
|
33
|
+
case "Slices":
|
|
34
|
+
return importSlicesConverter(field.value, assets, embeds)
|
|
30
35
|
default:
|
|
31
36
|
return convertNestableWidget(field, assets, embeds)
|
|
32
37
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { v4 as uuid } from "uuid"
|
|
2
|
+
|
|
3
|
+
import type { Asset, Embed } from "../../../../common"
|
|
4
|
+
import type { SliceItemContent } from "../../../../content"
|
|
5
|
+
import type { SharedSlice as ImportSharedSlice } from "../../../validators/fields/ImportSlices/SharedSlice"
|
|
6
|
+
import { importSharedSliceContentConverter } from "./SharedSliceContent"
|
|
7
|
+
|
|
8
|
+
// TODO should we put it together with SharedSliceId validator?
|
|
9
|
+
const buildSharedSliceId = (slice_type: string) => `${slice_type}$${uuid()}`
|
|
10
|
+
|
|
11
|
+
export const sharedSliceConverter = (
|
|
12
|
+
slice: ImportSharedSlice,
|
|
13
|
+
assets: Record<Asset["id"], Asset | undefined>,
|
|
14
|
+
embeds: Record<string, Embed | undefined>,
|
|
15
|
+
): SliceItemContent => {
|
|
16
|
+
// Right now we only support SharedSlices, if we support more types of slices in the future we'll need to select a correct converter here
|
|
17
|
+
const widget = importSharedSliceContentConverter(slice, assets, embeds)
|
|
18
|
+
return {
|
|
19
|
+
key: slice.id ?? buildSharedSliceId(slice.slice_type),
|
|
20
|
+
name: slice.slice_type,
|
|
21
|
+
maybeLabel: slice.slice_label ?? undefined,
|
|
22
|
+
widget,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { pipe } from "fp-ts/function"
|
|
2
|
+
import * as O from "fp-ts/Option"
|
|
3
|
+
import * as R from "fp-ts/Record"
|
|
4
|
+
|
|
5
|
+
import type { Asset, Embed } from "../../../../common"
|
|
6
|
+
import type { NestableContent, SharedSliceContent } from "../../../../content"
|
|
7
|
+
import {
|
|
8
|
+
GroupItemContentType,
|
|
9
|
+
SharedSliceContentType,
|
|
10
|
+
} from "../../../../content"
|
|
11
|
+
import type {
|
|
12
|
+
SharedSlice as ImportSharedSlice,
|
|
13
|
+
SharedSliceContent as ImportSharedSliceContent,
|
|
14
|
+
} from "../../../validators/fields/ImportSlices/SharedSlice"
|
|
15
|
+
import { convertNestableWidget } from "../nestable"
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Converts the ImportSharedSliceContent which is a record of widget keys and ImportNestable values to a record of widget keys and NestableContent values.
|
|
19
|
+
*
|
|
20
|
+
* @param content a single ImportSharedSliceContent
|
|
21
|
+
* @param assets assets that are required for a conversion of a nestable widget
|
|
22
|
+
* @param embeds embeds that are required for a conversion of a nestable widget
|
|
23
|
+
*/
|
|
24
|
+
const sharedSliceContentConverter = (
|
|
25
|
+
content: ImportSharedSliceContent,
|
|
26
|
+
assets: Record<Asset["id"], Asset | undefined>,
|
|
27
|
+
embeds: Record<string, Embed | undefined>,
|
|
28
|
+
): Record<string, NestableContent> =>
|
|
29
|
+
pipe(
|
|
30
|
+
content,
|
|
31
|
+
// convertNestableWidget can theoretically return undefined, so we need to filter out those values
|
|
32
|
+
R.filterMap((contentValue) =>
|
|
33
|
+
pipe(
|
|
34
|
+
contentValue,
|
|
35
|
+
(content) => convertNestableWidget(content, assets, embeds),
|
|
36
|
+
O.fromNullable,
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Converts a list of items to a list of repeatable widgets.
|
|
43
|
+
* Each ImportSharedSliceContent element is a record of fields, but in the content model it is represented as a list of tuples (key, value)
|
|
44
|
+
*
|
|
45
|
+
* For example, given the following slice content:
|
|
46
|
+
* {
|
|
47
|
+
* "slice_text": (ImportNestable),
|
|
48
|
+
* "slice_number": (ImportNestable),
|
|
49
|
+
* }
|
|
50
|
+
*
|
|
51
|
+
* has to be converted to:
|
|
52
|
+
*
|
|
53
|
+
* {
|
|
54
|
+
* __TYPE__: "GroupItemContentType",
|
|
55
|
+
* value: [
|
|
56
|
+
* ["slice_text", (NestableContent)],
|
|
57
|
+
* ["slice_number", (NestableContent)],
|
|
58
|
+
* ]
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* @param items list of items to be converted
|
|
62
|
+
* @param assets assets that are required for a conversion of a nestable widget
|
|
63
|
+
*/
|
|
64
|
+
const itemsConverter = (
|
|
65
|
+
items: ImportSharedSliceContent[],
|
|
66
|
+
assets: Record<Asset["id"], Asset | undefined>,
|
|
67
|
+
embeds: Record<string, Embed | undefined>,
|
|
68
|
+
): SharedSliceContent["items"] =>
|
|
69
|
+
items.map((item) =>
|
|
70
|
+
pipe(
|
|
71
|
+
sharedSliceContentConverter(item, assets, embeds),
|
|
72
|
+
(record) => Object.entries(record),
|
|
73
|
+
(entries) => ({ __TYPE__: GroupItemContentType, value: entries }),
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Builds SharedSliceContent model from ImportSharedSlice
|
|
79
|
+
* @param field ImportSharedSlice to be converted - a single slice from the slices array in the import document
|
|
80
|
+
* @param assets assets that are required for a conversion of a nestable widget
|
|
81
|
+
* @param embeds embeds that are required for a conversion of a nestable widget
|
|
82
|
+
*/
|
|
83
|
+
export const importSharedSliceContentConverter = (
|
|
84
|
+
field: ImportSharedSlice,
|
|
85
|
+
assets: Record<Asset["id"], Asset | undefined>,
|
|
86
|
+
embeds: Record<string, Embed | undefined>,
|
|
87
|
+
): SharedSliceContent => ({
|
|
88
|
+
__TYPE__: SharedSliceContentType,
|
|
89
|
+
primary: field.primary
|
|
90
|
+
? sharedSliceContentConverter(field.primary, assets, embeds)
|
|
91
|
+
: {},
|
|
92
|
+
items: field.items ? itemsConverter(field.items, assets, embeds) : [],
|
|
93
|
+
variation: field.variation,
|
|
94
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Asset, Embed } from "../../../../common"
|
|
2
|
+
import type { SliceItemContent, SlicesContent } from "../../../../content"
|
|
3
|
+
import type { ImportSlices } from "../../../validators/fields/ImportSlices"
|
|
4
|
+
import { sharedSliceConverter } from "./SharedSlice"
|
|
5
|
+
|
|
6
|
+
export const importSlicesConverter = (
|
|
7
|
+
field: ImportSlices["value"],
|
|
8
|
+
assets: Record<Asset["id"], Asset | undefined>,
|
|
9
|
+
embeds: Record<string, Embed | undefined>,
|
|
10
|
+
): SlicesContent | undefined => {
|
|
11
|
+
if (field === null) return
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
value: field.map((slice): SliceItemContent => {
|
|
15
|
+
// Right now we only support SharedSlices, if we support more types of slices in the future we'll need to select a correct converter here
|
|
16
|
+
return sharedSliceConverter(slice, assets, embeds)
|
|
17
|
+
}),
|
|
18
|
+
__TYPE__: "SliceContentType",
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Slices"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Asset } from "../../../../common"
|
|
2
2
|
import type { Embed } from "../../../../common/Embed"
|
|
3
|
-
import type {
|
|
3
|
+
import type { NestableContent } from "../../../../content"
|
|
4
4
|
import type { ImportNestable } from "../../../validators"
|
|
5
5
|
import {
|
|
6
6
|
booleanConverter,
|
|
@@ -20,7 +20,7 @@ export function convertNestableWidget(
|
|
|
20
20
|
field: ImportNestable,
|
|
21
21
|
assets: Record<Asset["id"], Asset | undefined>,
|
|
22
22
|
embeds: Record<string, Embed | undefined>,
|
|
23
|
-
):
|
|
23
|
+
): NestableContent | undefined {
|
|
24
24
|
switch (field.type) {
|
|
25
25
|
case "Boolean":
|
|
26
26
|
return booleanConverter(field.value)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { StaticWidget } from "../../../customtypes"
|
|
2
|
+
import { ImportSlices } from "./ImportSlices"
|
|
2
3
|
import { ImportNestable } from "./nestable"
|
|
3
4
|
import { ImportUID } from "./UID"
|
|
4
5
|
|
|
5
|
-
export type ImportField = ImportUID | ImportNestable
|
|
6
|
+
export type ImportField = ImportUID | ImportSlices | ImportNestable
|
|
6
7
|
|
|
7
8
|
export const ImportField = {
|
|
8
9
|
is(u: unknown): u is ImportNestable {
|
|
@@ -13,8 +14,12 @@ export const ImportField = {
|
|
|
13
14
|
switch (field.type) {
|
|
14
15
|
case "UID":
|
|
15
16
|
return { codec: ImportUID, result: ImportUID.decode(content) }
|
|
16
|
-
case "Choice":
|
|
17
17
|
case "Slices":
|
|
18
|
+
return {
|
|
19
|
+
codec: ImportSlices(field),
|
|
20
|
+
result: ImportSlices(field).decode(content),
|
|
21
|
+
}
|
|
22
|
+
case "Choice":
|
|
18
23
|
case "Group":
|
|
19
24
|
throw new Error(`Unsupported type of field ${field.type}`)
|
|
20
25
|
default:
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as E from "fp-ts/Either"
|
|
2
|
+
import { pipe } from "fp-ts/function"
|
|
3
|
+
import * as t from "io-ts"
|
|
4
|
+
|
|
5
|
+
import type { StaticSlices } from "../../../../customtypes"
|
|
6
|
+
import { EmptyObjectOrElse } from "../../../../validators"
|
|
7
|
+
import { ImportContent } from "../ImportContent"
|
|
8
|
+
import { SharedSlice } from "./SharedSlice"
|
|
9
|
+
import {
|
|
10
|
+
extractSupportedSlices,
|
|
11
|
+
findImportSharedSliceDuplicateIds,
|
|
12
|
+
} from "./utils"
|
|
13
|
+
|
|
14
|
+
export const ImportSlices = (staticSlices: StaticSlices) => {
|
|
15
|
+
const supportedSlices = extractSupportedSlices(staticSlices)
|
|
16
|
+
|
|
17
|
+
// For now we only support the SharedSlice, however if we want to support more in the future
|
|
18
|
+
// we would have to change the codec here to something like this: t.array(t.union([ImportSharedSlice(sharedSlices), NewSliceCodec(newSliceCustomTypes)])).
|
|
19
|
+
const SlicesArrayCodec = t.array(SharedSlice(supportedSlices))
|
|
20
|
+
|
|
21
|
+
return ImportContent(
|
|
22
|
+
"Slices",
|
|
23
|
+
EmptyObjectOrElse(
|
|
24
|
+
new t.Type<SharedSlice[]>(
|
|
25
|
+
"ImportSlices",
|
|
26
|
+
(u): u is SharedSlice[] => SlicesArrayCodec.is(u),
|
|
27
|
+
(u, c) => {
|
|
28
|
+
return pipe(
|
|
29
|
+
SlicesArrayCodec.validate(u, c),
|
|
30
|
+
E.chain((slices) => {
|
|
31
|
+
// This part might not make sense for all Slice types in the future, but for now we only support the SharedSlice
|
|
32
|
+
// In case we support more in the future, we would have to filter only the relevant type for this check
|
|
33
|
+
const sharedSliceDuplicates =
|
|
34
|
+
findImportSharedSliceDuplicateIds(slices)
|
|
35
|
+
if (sharedSliceDuplicates.length > 0) {
|
|
36
|
+
return t.failure(
|
|
37
|
+
slices,
|
|
38
|
+
[],
|
|
39
|
+
`Duplicate slice IDs detected: ${sharedSliceDuplicates.join(
|
|
40
|
+
", ",
|
|
41
|
+
)}`,
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
return t.success(slices)
|
|
45
|
+
}),
|
|
46
|
+
)
|
|
47
|
+
},
|
|
48
|
+
t.identity,
|
|
49
|
+
),
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type ImportSlices = t.TypeOf<ReturnType<typeof ImportSlices>>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as E from "fp-ts/Either"
|
|
2
|
+
import { pipe } from "fp-ts/function"
|
|
3
|
+
import * as t from "io-ts"
|
|
4
|
+
|
|
5
|
+
import type { SharedSlice as SharedSliceCustomType } from "../../../../../customtypes"
|
|
6
|
+
import { AnyArray, NonEmptyString, String } from "../../../../../validators"
|
|
7
|
+
import {
|
|
8
|
+
OptionalSharedSliceId,
|
|
9
|
+
SharedSliceContent,
|
|
10
|
+
SharedSliceId,
|
|
11
|
+
SharedSliceType,
|
|
12
|
+
SharedSliceVariation,
|
|
13
|
+
} from "./fields"
|
|
14
|
+
|
|
15
|
+
export type SharedSlice = {
|
|
16
|
+
id: SharedSliceId | null | undefined
|
|
17
|
+
slice_type: string
|
|
18
|
+
name: string
|
|
19
|
+
variation: string
|
|
20
|
+
primary: SharedSliceContent | null | undefined
|
|
21
|
+
items: SharedSliceContent[] | null | undefined
|
|
22
|
+
slice_label: string | null | undefined
|
|
23
|
+
version: string | null | undefined
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SharedSliceShape = t.type({
|
|
27
|
+
id: OptionalSharedSliceId,
|
|
28
|
+
slice_type: NonEmptyString,
|
|
29
|
+
name: String,
|
|
30
|
+
variation: NonEmptyString,
|
|
31
|
+
primary: t.union([t.undefined, t.null, t.record(NonEmptyString, t.unknown)]),
|
|
32
|
+
items: t.union([
|
|
33
|
+
t.undefined,
|
|
34
|
+
t.null,
|
|
35
|
+
t.array(t.record(NonEmptyString, t.unknown)),
|
|
36
|
+
]),
|
|
37
|
+
slice_label: t.union([t.undefined, t.null, String]),
|
|
38
|
+
version: t.union([t.undefined, t.null, String]),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
export const SharedSlice = (sharedSlices: SharedSliceCustomType[]) =>
|
|
42
|
+
new t.Type<SharedSlice>(
|
|
43
|
+
"SharedSlice",
|
|
44
|
+
(u): u is SharedSlice => SharedSliceShape.is(u),
|
|
45
|
+
(u, c) =>
|
|
46
|
+
pipe(
|
|
47
|
+
// We validate the base fields that we can validate directly based on the provided 'sharedSlices'
|
|
48
|
+
t
|
|
49
|
+
.type({
|
|
50
|
+
id: OptionalSharedSliceId,
|
|
51
|
+
slice_type: SharedSliceType(sharedSlices),
|
|
52
|
+
slice_label: t.union([t.undefined, t.null, String]),
|
|
53
|
+
version: t.union([t.undefined, t.null, String]),
|
|
54
|
+
})
|
|
55
|
+
.validate(u, c),
|
|
56
|
+
E.chain((decoded) =>
|
|
57
|
+
pipe(
|
|
58
|
+
// We validate the 'variation' field, for which we need the SharedSlice custom type retrieved in the first step
|
|
59
|
+
t
|
|
60
|
+
.type({
|
|
61
|
+
variation: SharedSliceVariation(decoded.slice_type.data),
|
|
62
|
+
})
|
|
63
|
+
.validate(u, c),
|
|
64
|
+
E.map(({ variation }) => ({ ...decoded, variation })),
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
E.chain((decoded) =>
|
|
68
|
+
pipe(
|
|
69
|
+
// We validate the 'primary' and 'items' content fields, for which we need the Variation custom type retrieved in the previous step
|
|
70
|
+
t
|
|
71
|
+
.partial({
|
|
72
|
+
primary: SharedSliceContent(
|
|
73
|
+
decoded.slice_type.slice_type,
|
|
74
|
+
"primary",
|
|
75
|
+
decoded.variation.data.primary ?? {},
|
|
76
|
+
),
|
|
77
|
+
// We need to use this wrapper codec instead of using t.array(SharedSliceContent) directly in order to get proper custom error messages
|
|
78
|
+
items: AnyArray.pipe(
|
|
79
|
+
t.array(
|
|
80
|
+
SharedSliceContent(
|
|
81
|
+
decoded.slice_type.slice_type,
|
|
82
|
+
"items",
|
|
83
|
+
decoded.variation.data.items ?? {},
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
),
|
|
87
|
+
})
|
|
88
|
+
.validate(u, c),
|
|
89
|
+
E.map(({ primary, items }) => ({ ...decoded, primary, items })),
|
|
90
|
+
),
|
|
91
|
+
),
|
|
92
|
+
E.map(
|
|
93
|
+
({
|
|
94
|
+
id,
|
|
95
|
+
slice_type,
|
|
96
|
+
variation,
|
|
97
|
+
primary,
|
|
98
|
+
items,
|
|
99
|
+
slice_label,
|
|
100
|
+
version,
|
|
101
|
+
}) => ({
|
|
102
|
+
id,
|
|
103
|
+
slice_type: slice_type.slice_type,
|
|
104
|
+
name: slice_type.data.name,
|
|
105
|
+
variation: variation.variation,
|
|
106
|
+
primary,
|
|
107
|
+
items,
|
|
108
|
+
slice_label,
|
|
109
|
+
version: version ?? variation.data.version,
|
|
110
|
+
}),
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
t.identity,
|
|
114
|
+
)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const SharedSliceErrors = {
|
|
2
|
+
SliceTypeNotFound: (slice_type: string) =>
|
|
3
|
+
`Shared slice '${slice_type}' not found in document's custom type`,
|
|
4
|
+
VariationNotFound: (variation: string, slice_type: string) =>
|
|
5
|
+
`Variation '${variation}' not found for Slice '${slice_type}'`,
|
|
6
|
+
} as const
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as t from "io-ts"
|
|
2
|
+
|
|
3
|
+
import { SharedSliceId } from "./SharedSliceId"
|
|
4
|
+
|
|
5
|
+
// Wrapping the 'SharedSliceId' codec in an optional codec here for the sake of error reporting.
|
|
6
|
+
// When using t.union([t.undefined, t.null, SharedSliceId]) we get a nested error context
|
|
7
|
+
// with the actual decoding error at position 2 in the context array.
|
|
8
|
+
// To avoid mutating the error context we create this new wrapper codec so that the error context will always have desired depth.
|
|
9
|
+
export const OptionalSharedSliceId = new t.Type<
|
|
10
|
+
SharedSliceId | null | undefined
|
|
11
|
+
>(
|
|
12
|
+
"OptionalSharedSliceId",
|
|
13
|
+
(u): u is SharedSliceId | null | undefined =>
|
|
14
|
+
u === null || u === undefined || SharedSliceId.is(u),
|
|
15
|
+
(u, c) => {
|
|
16
|
+
if (u === null || u === undefined) return t.success(u)
|
|
17
|
+
return SharedSliceId.validate(u, c)
|
|
18
|
+
},
|
|
19
|
+
t.identity,
|
|
20
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as E from "fp-ts/Either"
|
|
2
|
+
import { pipe } from "fp-ts/lib/function"
|
|
3
|
+
import * as t from "io-ts"
|
|
4
|
+
|
|
5
|
+
import { WidgetKey } from "../../../../../../../common"
|
|
6
|
+
import type { NestableWidget } from "../../../../../../../customtypes"
|
|
7
|
+
import { combineValidationResults } from "../../../../../../../utils/io-ts"
|
|
8
|
+
import { AnyObject } from "../../../../../../../validators"
|
|
9
|
+
import type { ImportNestable } from "../../../../nestable"
|
|
10
|
+
import { SharedSliceContentEntry } from "./SharedSliceContentEntry"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Represents the object in the 'primary' field or a single item in the 'items' array in the SharedSlice.
|
|
14
|
+
*/
|
|
15
|
+
export type SharedSliceContent = Record<WidgetKey, ImportNestable>
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Builds the decoder for a single entry in the 'primary' field or a single item in the 'items' array in the SharedSlice.
|
|
19
|
+
*
|
|
20
|
+
* @param sliceName name of the slice - used for error reporting in order to know which slice is being decoded
|
|
21
|
+
* @param sliceContentField 'primary' or 'items' - used for error reporting in order to know which field of the slice is being decoded
|
|
22
|
+
* @param sliceFieldModels models of the fields of the slice from custom type - used to know which codec to use for decoding each value of the object
|
|
23
|
+
* @constructor
|
|
24
|
+
*/
|
|
25
|
+
export const SharedSliceContent = (
|
|
26
|
+
sliceName: string,
|
|
27
|
+
sliceContentField: "primary" | "items",
|
|
28
|
+
sliceFieldModels: Record<WidgetKey, NestableWidget>,
|
|
29
|
+
) =>
|
|
30
|
+
new t.Type<SharedSliceContent, SharedSliceContent, unknown>(
|
|
31
|
+
"SharedSliceContent",
|
|
32
|
+
(u): u is SharedSliceContent => t.record(WidgetKey, t.unknown).is(u),
|
|
33
|
+
(u, c) =>
|
|
34
|
+
pipe(
|
|
35
|
+
// We first decode the raw content object in the shape of Record<WidgetKey, unknown>
|
|
36
|
+
AnyObject.validate(u, c),
|
|
37
|
+
E.chain((rawContent) =>
|
|
38
|
+
pipe(
|
|
39
|
+
// We split the object into entries because there is no easy other way to decode each value with a different codec
|
|
40
|
+
Object.entries(rawContent).map((entry) =>
|
|
41
|
+
SharedSliceContentEntry(
|
|
42
|
+
sliceName,
|
|
43
|
+
sliceContentField,
|
|
44
|
+
sliceFieldModels,
|
|
45
|
+
).validate(entry, c),
|
|
46
|
+
),
|
|
47
|
+
// We get a validation result for each entry in the content object (t.Validation<SharedSliceContentEntry>[]),
|
|
48
|
+
// however we want to get a single validation result for the whole array of entries (t.Validation<SharedSliceContentEntry[]>).
|
|
49
|
+
combineValidationResults,
|
|
50
|
+
// We build the object back from entries if all decoded successfully
|
|
51
|
+
E.map((entries) =>
|
|
52
|
+
entries.reduce(
|
|
53
|
+
(acc, [key, value]) => ({ ...acc, [key]: value }),
|
|
54
|
+
{},
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
),
|
|
60
|
+
t.identity,
|
|
61
|
+
)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as E from "fp-ts/Either"
|
|
2
|
+
import { pipe } from "fp-ts/function"
|
|
3
|
+
import * as t from "io-ts"
|
|
4
|
+
|
|
5
|
+
import { WidgetKey } from "../../../../../../../common"
|
|
6
|
+
import type { NestableWidget } from "../../../../../../../customtypes"
|
|
7
|
+
import { ImportNestable } from "../../../../nestable"
|
|
8
|
+
import { SharedSliceContentErrors } from "./errors"
|
|
9
|
+
|
|
10
|
+
export type SharedSliceContentEntry = [WidgetKey, ImportNestable]
|
|
11
|
+
/**
|
|
12
|
+
* Represents a single entry in the 'primary' field or a single entry in an item in the 'items' array in the SharedSlice. It is used to build the SharedSliceContent type from entries of the import object.
|
|
13
|
+
*
|
|
14
|
+
* For example, given the following slice:
|
|
15
|
+
* {
|
|
16
|
+
* "slice_type": "my_slice",
|
|
17
|
+
* "variation": "default",
|
|
18
|
+
* "primary": {
|
|
19
|
+
* "slice_text": "abc", // This is one entry
|
|
20
|
+
* "slice_text2": "def: // This is another entry
|
|
21
|
+
* },
|
|
22
|
+
* "items": [
|
|
23
|
+
* {
|
|
24
|
+
* "slice_number1": 0, // This is one entry
|
|
25
|
+
* "slice_number2": 1 // And this is yet another entry
|
|
26
|
+
* }
|
|
27
|
+
* ]
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* The 'primary' object is converted to following entries to be decoded:
|
|
31
|
+
* [
|
|
32
|
+
* ["slice_text", "abc"],
|
|
33
|
+
* ["slice_text2", "def"]
|
|
34
|
+
* ]
|
|
35
|
+
* and the first item in the 'items' array is converted to following entries to be decoded:
|
|
36
|
+
* [
|
|
37
|
+
* ["slice_number1", 0],
|
|
38
|
+
* ["slice_number2", 1]
|
|
39
|
+
* ]
|
|
40
|
+
*
|
|
41
|
+
* We decode them as separate entries because each of the values might require a different codec as per what is specified in the SharedSlice custom type.
|
|
42
|
+
* In this way we save on the decoding performance in comparison to a scenario where we would decode the whole content object as a record with a union type decoder for each value.
|
|
43
|
+
*
|
|
44
|
+
* @param sliceName name of the slice - used for error reporting in order to know which slice is being decoded
|
|
45
|
+
* @param sliceContentField 'primary' or 'items' - used for error reporting in order to know which field of the slice is being decoded
|
|
46
|
+
* @param sliceFieldModels models of the fields of the slice from custom type - used to know which codec to use for decoding each value of the object
|
|
47
|
+
*/
|
|
48
|
+
export const SharedSliceContentEntry = (
|
|
49
|
+
sliceName: string,
|
|
50
|
+
sliceContentField: "primary" | "items",
|
|
51
|
+
sliceFieldModels: Record<WidgetKey, NestableWidget>,
|
|
52
|
+
) =>
|
|
53
|
+
new t.Type<SharedSliceContentEntry, SharedSliceContentEntry, unknown>(
|
|
54
|
+
"SharedSliceContentEntry",
|
|
55
|
+
(u): u is SharedSliceContentEntry =>
|
|
56
|
+
t.tuple([WidgetKey, t.unknown]).is(u) && ImportNestable.is(u[1]),
|
|
57
|
+
(u, c) => {
|
|
58
|
+
return pipe(
|
|
59
|
+
t.tuple([WidgetKey, t.unknown]).validate(u, c),
|
|
60
|
+
E.chain(([key, content]) => {
|
|
61
|
+
const model: NestableWidget | undefined = sliceFieldModels[key]
|
|
62
|
+
if (!model) {
|
|
63
|
+
return t.failure(
|
|
64
|
+
[key, content],
|
|
65
|
+
[...c, { key, actual: content, type: t.unknown }],
|
|
66
|
+
SharedSliceContentErrors.UnknownField(
|
|
67
|
+
sliceName,
|
|
68
|
+
sliceContentField,
|
|
69
|
+
key,
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
const { codec, result } = ImportNestable.decode(model)(content)
|
|
74
|
+
|
|
75
|
+
return pipe(
|
|
76
|
+
result,
|
|
77
|
+
E.map<ImportNestable, SharedSliceContentEntry>((decodedContent) => [
|
|
78
|
+
key,
|
|
79
|
+
decodedContent,
|
|
80
|
+
]),
|
|
81
|
+
E.mapLeft(
|
|
82
|
+
(errors): t.Errors =>
|
|
83
|
+
errors.map((error) => {
|
|
84
|
+
const context = [
|
|
85
|
+
...c,
|
|
86
|
+
...error.context.filter(({ key }) => !!key), // We replace the "" key coming from ImportNestable decoder
|
|
87
|
+
{ key, actual: content, type: codec },
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
const updatedError: t.ValidationError = { ...error, context }
|
|
91
|
+
|
|
92
|
+
return updatedError
|
|
93
|
+
}),
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
}),
|
|
97
|
+
)
|
|
98
|
+
},
|
|
99
|
+
t.identity,
|
|
100
|
+
)
|
package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/errors.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SharedSliceContentField } from "./types"
|
|
2
|
+
|
|
3
|
+
export const SharedSliceContentErrors = {
|
|
4
|
+
UnknownField: (
|
|
5
|
+
sliceName: string,
|
|
6
|
+
contentField: SharedSliceContentField,
|
|
7
|
+
fieldName: string,
|
|
8
|
+
) =>
|
|
9
|
+
`Unsupported field '${fieldName}'for '${contentField}' content in Shared Slice '${sliceName}'`,
|
|
10
|
+
} as const
|
package/src/import/validators/fields/ImportSlices/SharedSlice/fields/SharedSliceContent/types.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type SharedSliceContentField = "primary" | "items"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as E from "fp-ts/Either"
|
|
2
|
+
import { pipe } from "fp-ts/function"
|
|
3
|
+
import * as t from "io-ts"
|
|
4
|
+
|
|
5
|
+
import { UUID } from "../../../../../../common/UUID"
|
|
6
|
+
import { NonEmptyString } from "../../../../../../validators"
|
|
7
|
+
|
|
8
|
+
export type SharedSliceId = `${string}$${UUID}`
|
|
9
|
+
|
|
10
|
+
const baseErrorText =
|
|
11
|
+
"Slice id must be a non empty string matching the following pattern 'slice_type$uuid'"
|
|
12
|
+
|
|
13
|
+
export const SharedSliceIdValidationError = {
|
|
14
|
+
Base: baseErrorText,
|
|
15
|
+
IncorrectUUID: (uuid: string | undefined) =>
|
|
16
|
+
`Incorrect UUID: '${uuid}' - ${baseErrorText}`,
|
|
17
|
+
IncorrectSliceType: (sliceType: string | undefined) =>
|
|
18
|
+
`Incorrect slice_type: '${sliceType}' - ${baseErrorText}`,
|
|
19
|
+
} as const
|
|
20
|
+
|
|
21
|
+
export const SharedSliceId = new t.Type<SharedSliceId, string, unknown>(
|
|
22
|
+
"SharedSliceId",
|
|
23
|
+
(u): u is SharedSliceId => {
|
|
24
|
+
if (typeof u !== "string") {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
const [sliceName, uuid] = u.split("$")
|
|
28
|
+
return NonEmptyString.is(sliceName) && UUID.is(uuid)
|
|
29
|
+
},
|
|
30
|
+
(u, c) =>
|
|
31
|
+
pipe(
|
|
32
|
+
NonEmptyString.validate(u, c),
|
|
33
|
+
E.mapLeft((errors) =>
|
|
34
|
+
errors.map(
|
|
35
|
+
(error): t.ValidationError => ({
|
|
36
|
+
...error,
|
|
37
|
+
message: SharedSliceIdValidationError.Base,
|
|
38
|
+
}),
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
E.chain((sliceId) => {
|
|
42
|
+
const [sliceName, uuid] = sliceId.split("$")
|
|
43
|
+
return pipe(
|
|
44
|
+
t.tuple([NonEmptyString, UUID]).validate([sliceName, uuid], c),
|
|
45
|
+
E.mapLeft((errors) =>
|
|
46
|
+
errors.map((error): t.ValidationError => {
|
|
47
|
+
const tupleErrorContext = error.context[error.context.length - 1]
|
|
48
|
+
return {
|
|
49
|
+
...error,
|
|
50
|
+
context: [
|
|
51
|
+
...error.context.slice(0, -1), // We don't want the tuple decoder error context
|
|
52
|
+
],
|
|
53
|
+
message:
|
|
54
|
+
tupleErrorContext?.key === "0"
|
|
55
|
+
? SharedSliceIdValidationError.IncorrectSliceType(sliceName)
|
|
56
|
+
: SharedSliceIdValidationError.IncorrectUUID(uuid),
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
}),
|
|
62
|
+
E.map(([sliceName, uuid]) => `${sliceName}$${uuid}`),
|
|
63
|
+
),
|
|
64
|
+
(sliceId) => sliceId,
|
|
65
|
+
)
|