Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1import {
2 getScalarType,
3 isContainerType,
4 type ContainerType,
5 type Field,
6 type FieldScalarType,
7 type FieldType,
8 type ScalarType,
9 type ScalarTypeRuntimeType,
10 type TaggedScalar,
11} from '@roostorg/types';
12
13import { hasOwn } from '../../utils/misc.js';
14import {
15 type FieldRoleToScalarType,
16 type ItemSchema,
17 type SchemaFieldRoles,
18} from '../moderationConfigService/index.js';
19import { fieldTypeHandlers } from './fieldTypeHandlers.js';
20import { type NormalizedItemData } from './toNormalizedItemDataOrErrors.js';
21
22/**
23 * Extracts all the values from all the passed in fields from a given piece of
24 * content, as one merged array. I.e., if one field is a container with
25 * multiple values, all those values will be flattened into the final result.
26 *
27 * NB: the `content` object might not actually have the fields and structure
28 * suggested by `fields`. In that case, the returned values may not be of the
29 * type suggested by `getScalarType(field)`, and some fields might be missing.
30 *
31 * @param data Content object whose field values should be returned
32 * @param fields The fields whose values should be included.
33 */
34export function getValuesFromFields(
35 data: NormalizedItemData,
36 fields: Field[],
37): TaggedScalar<ScalarType>[] {
38 return fields.flatMap((field) => {
39 const fieldValue = getFieldValueOrValues(data, field);
40 return Array.isArray(fieldValue)
41 ? fieldValue
42 : fieldValue !== undefined
43 ? [fieldValue]
44 : [];
45 });
46}
47
48export function getFieldValueOrValues<T extends FieldType>(
49 data: NormalizedItemData,
50 field: Field<T>,
51) {
52 const { name, type, container } = field;
53 const values = !hasOwn(data, name)
54 ? []
55 : // NB: casting to `never` below is unambiguously wrong, but it's needed
56 // because the type that TS infers when trying to generate a single,
57 // callable signature for `getValues` is not correct (and has never as
58 // its argument types).
59 fieldTypeHandlers[type as FieldType]
60 .getValues(data[name] as never, container as never)
61 .map(
62 (value) =>
63 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
64 ({ value, type: getScalarType(field) }) as TaggedScalar<ScalarType>,
65 );
66
67 return (isContainerType(type) ? values : values[0]) as
68 | (T & ScalarType extends never
69 ? never
70 : TaggedScalar<FieldScalarType<T & ScalarType>>)
71 | (T & ContainerType extends never
72 ? never
73 : TaggedScalar<FieldScalarType<T & ContainerType>>[])
74 | undefined;
75}
76
77export function getFieldValueForRole<
78 FieldRoles extends SchemaFieldRoles,
79 Role extends keyof FieldRoles,
80>(
81 schema: ItemSchema,
82 schemaFieldRoles: FieldRoles,
83 role: Role,
84 data: NormalizedItemData,
85) {
86 const fieldName = schemaFieldRoles[role];
87 const field = schema.find((it) => it.name === fieldName);
88 if (field === undefined) {
89 return undefined;
90 }
91 const fieldValue = getFieldValueOrValues(data, field);
92 if (fieldValue === undefined) {
93 return undefined;
94 }
95 if (Array.isArray(fieldValue)) {
96 throw new Error('Unexpected array when getting field value');
97 }
98 return fieldValue.value satisfies ScalarTypeRuntimeType<ScalarType> as ScalarTypeRuntimeType<
99 FieldRoleToScalarType[Role & keyof FieldRoleToScalarType]
100 >;
101}