Skip to content

Commit 01100d7

Browse files
committed
[backend] Refactor to assertType utility
1 parent c67d22e commit 01100d7

File tree

4 files changed

+60
-45
lines changed

4 files changed

+60
-45
lines changed

opencti-platform/opencti-graphql/src/database/attachment-processor-props.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Mutable } from '../types/type-utils';
1+
import type { Mutable } from '../utils/type-utils';
22

33
// List of fields extracted by the attachment ingest processor.
44
// The full list is available in the Elasticsearch docs:

opencti-platform/opencti-graphql/src/modules/internal/document/document.ts

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { assertType } from '../../../utils/type-utils';
12
import type { AttachmentProcessorExtractedProp } from '../../../database/attachment-processor-props';
23
import { ENTITY_TYPE_INTERNAL_FILE } from '../../../schema/internalObject';
34
import { schemaAttributesDefinition } from '../../../schema/schema-attributes';
@@ -12,47 +13,12 @@ import {
1213
refreshedAt,
1314
standardId,
1415
updatedAt,
15-
type MappingDefinition,
16-
type BasicStoreAttribute,
1716
} from '../../../schema/attribute-definition';
1817
import { ENTITY_TYPE_MARKING_DEFINITION } from '../../../schema/stixMetaObject';
1918
import { ABSTRACT_STIX_CORE_OBJECT } from '../../../schema/general';
2019
import { UPLOAD_STATUS_VALUES } from './document-domain';
2120

22-
export const ATTACHMENT_MAPPINGS = [
23-
{ name: 'author', label: 'Author', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
24-
{ name: 'comments', label: 'Comments', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
25-
{ name: 'content', label: 'Content', type: 'string', format: 'text', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
26-
{ name: 'content_length', label: 'Content length', type: 'numeric', precision: 'integer', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
27-
{ name: 'content_type', label: 'Content type', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
28-
{ name: 'creator_tool', label: 'Creator tool', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
29-
{ name: 'date', label: 'Created date', type: 'date', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
30-
{ name: 'description', label: 'Description', type: 'string', format: 'text', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
31-
{ name: 'format', label: 'Format', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
32-
{ name: 'keywords', label: 'Keywords', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
33-
{ name: 'language', label: 'Language', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
34-
{ name: 'metadata_date', label: 'Metadata date', type: 'date', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
35-
{ name: 'modified', label: 'Modified date', type: 'date', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
36-
{ name: 'modifier', label: 'Modifier', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
37-
{ name: 'print_date', label: 'Print date', type: 'date', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
38-
{ name: 'title', label: 'Title', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
39-
] satisfies ({
40-
name: AttachmentProcessorExtractedProp;
41-
} & MappingDefinition<BasicStoreAttribute>)[];
42-
43-
// Compile-time shenanigans to make sure we don't forget to update
44-
// ATTACHMENT_MAPPINGS when/if we start extracting new fields
45-
// via the ES/OS attachment ingest pipeline.
46-
type AttachmentMappingsWithCheck = Exclude<
47-
AttachmentProcessorExtractedProp,
48-
typeof ATTACHMENT_MAPPINGS[number]['name']
49-
> extends never
50-
? MappingDefinition<BasicStoreAttribute>[]
51-
: 'Make sure ATTACHMENT_MAPPINGS defines one mapping for each AttachmentProcessorExtractedProp';
52-
53-
const TYPE_CHECKED_ATTACHMENT_MAPPINGS: AttachmentMappingsWithCheck = ATTACHMENT_MAPPINGS;
54-
55-
const attributes: Array<AttributeDefinition> = [
21+
const attributes = [
5622
id,
5723
internalId,
5824
standardId,
@@ -117,12 +83,40 @@ const attributes: Array<AttributeDefinition> = [
11783
multiple: false,
11884
upsert: false,
11985
isFilterable: false,
120-
mappings: TYPE_CHECKED_ATTACHMENT_MAPPINGS,
86+
mappings: [
87+
{ name: 'author', label: 'Author', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
88+
{ name: 'comments', label: 'Comments', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
89+
{ name: 'content', label: 'Content', type: 'string', format: 'text', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
90+
{ name: 'content_length', label: 'Content length', type: 'numeric', precision: 'integer', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
91+
{ name: 'content_type', label: 'Content type', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
92+
{ name: 'creator_tool', label: 'Creator tool', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
93+
{ name: 'date', label: 'Created date', type: 'date', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
94+
{ name: 'description', label: 'Description', type: 'string', format: 'text', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
95+
{ name: 'format', label: 'Format', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
96+
{ name: 'keywords', label: 'Keywords', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
97+
{ name: 'language', label: 'Language', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
98+
{ name: 'metadata_date', label: 'Metadata date', type: 'date', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
99+
{ name: 'modified', label: 'Modified date', type: 'date', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
100+
{ name: 'modifier', label: 'Modifier', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
101+
{ name: 'print_date', label: 'Print date', type: 'date', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
102+
{ name: 'title', label: 'Title', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: true },
103+
],
121104
},
122105
{ name: 'uploaded_at', label: 'Upload date', type: 'date', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: false },
123106
{ name: 'file_id', label: 'File identifier', type: 'string', format: 'short', mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: false },
124107
{ name: 'entity_id', label: 'Related entity', type: 'string', format: 'id', entityTypes: [ABSTRACT_STIX_CORE_OBJECT], mandatoryType: 'internal', editDefault: false, multiple: false, upsert: false, isFilterable: false },
125108
{ name: 'removed', label: 'Removed', type: 'boolean', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false },
126-
];
109+
] as const satisfies Array<AttributeDefinition>;
110+
111+
const attachmentAttributes = attributes[18];
112+
113+
type AttachmentAttributeMappingNames = typeof attachmentAttributes.mappings[number]['name'][];
114+
115+
// Make sure there's an attachment mapping for each field extracted
116+
// by the `attachment` ingest processor, exhaustively.
117+
assertType<
118+
AttachmentAttributeMappingNames,
119+
AttachmentProcessorExtractedProp[]
120+
>(attachmentAttributes.mappings.map(({ name }) => name));
127121

128122
schemaAttributesDefinition.registerAttributes(ENTITY_TYPE_INTERNAL_FILE, attributes);

opencti-platform/opencti-graphql/src/types/type-utils.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,3 @@ import { isNotEmptyField } from '../database/utils';
33
export const filterEmpty = <T>(data: T | null | undefined): data is T => {
44
return isNotEmptyField(data);
55
};
6-
7-
/**
8-
* Inverse operation of the built-in Readonly<T> utility type:
9-
* makes all records of an object mutable.
10-
*/
11-
export type Mutable<T> = { -readonly [P in keyof T]: T[P]; };
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Inverse operation of the built-in Readonly<T> utility type:
3+
* makes all records of an object mutable.
4+
*/
5+
export type Mutable<T> = { -readonly [P in keyof T]: T[P]; };
6+
7+
type AssertEqual<T, TExpected> = [T] extends [TExpected]
8+
? [TExpected] extends [T]
9+
? T
10+
: never
11+
: never
12+
13+
/**
14+
* Compile-time type assertion utility checking that a type T
15+
* is exactly of type TExpected.
16+
*
17+
* @example
18+
* ```
19+
* type Fruit = 'banana' | 'strawberry' | 'pineapple';
20+
* const allFruits = ['banana' as const, 'strawberry' as const, 'pineapple' as const];
21+
* // Checks for exhaustiveness
22+
* assertType<typeof allFruits[number][], Fruit[]>(allFruits);
23+
* ```
24+
*/
25+
export const assertType = <T, TExpected>(_x: AssertEqual<T, TExpected>) => {
26+
// noop
27+
}

0 commit comments

Comments
 (0)