UNPKG

3.67 kBPlain TextView Raw
1import {find} from 'lodash'
2import {SerializePath, SerializeOptions} from './StructureNodes'
3import {ChildResolverOptions, ChildResolver} from './ChildResolver'
4import {SerializeError, HELP_URL} from './SerializeError'
5import {ListItem, ListItemBuilder} from './ListItem'
6import {
7 GenericListBuilder,
8 BuildableGenericList,
9 GenericList,
10 GenericListInput
11} from './GenericList'
12
13const getArgType = (thing: ListItem) => {
14 return Array.isArray(thing) ? 'array' : typeof thing
15}
16
17const resolveChildForItem: ChildResolver = (itemId: string, options: ChildResolverOptions) => {
18 const parentItem = options.parent as List
19 const target = (parentItem.items.find(item => item.id === itemId) || {child: undefined}).child
20 if (!target || typeof target !== 'function') {
21 return target
22 }
23
24 return typeof target === 'function' ? target(itemId, options) : target
25}
26
27function maybeSerializeListItem(
28 item: ListItem | ListItemBuilder,
29 index: number,
30 path: SerializePath
31): ListItem {
32 if (item instanceof ListItemBuilder) {
33 return item.serialize({path, index})
34 }
35
36 const listItem = item as ListItem
37 if (!listItem || listItem.type !== 'listItem') {
38 const gotWhat = (listItem && listItem.type) || getArgType(listItem)
39 const helpText = gotWhat === 'array' ? ' - did you forget to spread (...moreItems)?' : ''
40 throw new SerializeError(
41 `List items must be of type "listItem", got "${gotWhat}"${helpText}`,
42 path,
43 index
44 ).withHelpUrl(HELP_URL.INVALID_LIST_ITEM)
45 }
46
47 return item
48}
49
50export interface List extends GenericList {
51 items: ListItem[]
52}
53
54export interface ListInput extends GenericListInput {
55 items?: (ListItem | ListItemBuilder)[]
56}
57
58export interface BuildableList extends BuildableGenericList {
59 items?: (ListItem | ListItemBuilder)[]
60}
61
62export class ListBuilder extends GenericListBuilder<BuildableList, ListBuilder> {
63 protected spec: BuildableList
64
65 constructor(spec?: ListInput) {
66 super()
67 this.spec = spec ? spec : {}
68 }
69
70 items(items: (ListItemBuilder | ListItem)[]): ListBuilder {
71 return this.clone({items})
72 }
73
74 getItems() {
75 return this.spec.items
76 }
77
78 serialize(options: SerializeOptions = {path: []}): List {
79 const id = this.spec.id
80 if (typeof id !== 'string' || !id) {
81 throw new SerializeError(
82 '`id` is required for lists',
83 options.path,
84 options.index
85 ).withHelpUrl(HELP_URL.ID_REQUIRED)
86 }
87
88 const items = typeof this.spec.items === 'undefined' ? [] : this.spec.items
89 if (!Array.isArray(items)) {
90 throw new SerializeError(
91 '`items` must be an array of items',
92 options.path,
93 options.index
94 ).withHelpUrl(HELP_URL.LIST_ITEMS_MUST_BE_ARRAY)
95 }
96
97 const path = (options.path || []).concat(id)
98 const serializedItems = items.map((item, index) => maybeSerializeListItem(item, index, path))
99 const dupes = serializedItems.filter((val, i) => find(serializedItems, {id: val.id}, i + 1))
100
101 if (dupes.length > 0) {
102 const dupeIds = dupes.map(item => item.id).slice(0, 5)
103 const dupeDesc = dupes.length > 5 ? `${dupeIds.join(', ')}...` : dupeIds.join(', ')
104 throw new SerializeError(
105 `List items with same ID found (${dupeDesc})`,
106 options.path,
107 options.index
108 ).withHelpUrl(HELP_URL.LIST_ITEM_IDS_MUST_BE_UNIQUE)
109 }
110
111 return {
112 ...super.serialize(options),
113 type: 'list',
114 child: this.spec.child || resolveChildForItem,
115 items: serializedItems
116 }
117 }
118
119 clone(withSpec?: BuildableList) {
120 const builder = new ListBuilder()
121 builder.spec = {...this.spec, ...(withSpec || {})}
122 return builder
123 }
124}