directus

Форк
0
/
validate-diff.ts 
157 строк · 5.4 Кб
1
import Joi from 'joi';
2
import { InvalidPayloadError } from '@directus/errors';
3
import type { SnapshotDiffWithHash, SnapshotWithHash } from '../types/snapshot.js';
4
import { DiffKind } from '../types/snapshot.js';
5

6
const deepDiffSchema = Joi.object({
7
	kind: Joi.string()
8
		.valid(...Object.values(DiffKind))
9
		.required(),
10
	path: Joi.array().items(Joi.alternatives().try(Joi.string(), Joi.number())),
11
	lhs: Joi.any().when('kind', { is: [DiffKind.NEW, DiffKind.ARRAY], then: Joi.optional(), otherwise: Joi.required() }),
12
	rhs: Joi.any().when('kind', {
13
		is: [DiffKind.DELETE, DiffKind.ARRAY],
14
		then: Joi.optional(),
15
		otherwise: Joi.required(),
16
	}),
17
	index: Joi.number().when('kind', { is: DiffKind.ARRAY, then: Joi.required() }),
18
	item: Joi.link('#deepdiff').when('kind', { is: DiffKind.ARRAY, then: Joi.required() }),
19
}).id('deepdiff');
20

21
const applyJoiSchema = Joi.object({
22
	hash: Joi.string().required(),
23
	diff: Joi.object({
24
		collections: Joi.array()
25
			.items(
26
				Joi.object({
27
					collection: Joi.string().required(),
28
					diff: Joi.array().items(deepDiffSchema).required(),
29
				}),
30
			)
31
			.required(),
32
		fields: Joi.array()
33
			.items(
34
				Joi.object({
35
					collection: Joi.string().required(),
36
					field: Joi.string().required(),
37
					diff: Joi.array().items(deepDiffSchema).required(),
38
				}),
39
			)
40
			.required(),
41
		relations: Joi.array()
42
			.items(
43
				Joi.object({
44
					collection: Joi.string().required(),
45
					field: Joi.string().required(),
46
					related_collection: Joi.string().allow(null),
47
					diff: Joi.array().items(deepDiffSchema).required(),
48
				}),
49
			)
50
			.required(),
51
	}).required(),
52
});
53

54
/**
55
 * Validates the diff against the current schema snapshot.
56
 *
57
 * @returns True if the diff can be applied (valid & not empty).
58
 */
59
export function validateApplyDiff(applyDiff: SnapshotDiffWithHash, currentSnapshotWithHash: SnapshotWithHash) {
60
	const { error } = applyJoiSchema.validate(applyDiff);
61
	if (error) throw new InvalidPayloadError({ reason: error.message });
62

63
	// No changes to apply
64
	if (
65
		applyDiff.diff.collections.length === 0 &&
66
		applyDiff.diff.fields.length === 0 &&
67
		applyDiff.diff.relations.length === 0
68
	) {
69
		return false;
70
	}
71

72
	// Diff can be applied due to matching hash
73
	if (applyDiff.hash === currentSnapshotWithHash.hash) return true;
74

75
	for (const diffCollection of applyDiff.diff.collections) {
76
		const collection = diffCollection.collection;
77

78
		if (diffCollection.diff[0]?.kind === DiffKind.NEW) {
79
			const existingCollection = currentSnapshotWithHash.collections.find(
80
				(c) => c.collection === diffCollection.collection,
81
			);
82

83
			if (existingCollection) {
84
				throw new InvalidPayloadError({
85
					reason: `Provided diff is trying to create collection "${collection}" but it already exists. Please generate a new diff and try again`,
86
				});
87
			}
88
		} else if (diffCollection.diff[0]?.kind === DiffKind.DELETE) {
89
			const existingCollection = currentSnapshotWithHash.collections.find(
90
				(c) => c.collection === diffCollection.collection,
91
			);
92

93
			if (!existingCollection) {
94
				throw new InvalidPayloadError({
95
					reason: `Provided diff is trying to delete collection "${collection}" but it does not exist. Please generate a new diff and try again`,
96
				});
97
			}
98
		}
99
	}
100

101
	for (const diffField of applyDiff.diff.fields) {
102
		const field = `${diffField.collection}.${diffField.field}`;
103

104
		if (diffField.diff[0]?.kind === DiffKind.NEW) {
105
			const existingField = currentSnapshotWithHash.fields.find(
106
				(f) => f.collection === diffField.collection && f.field === diffField.field,
107
			);
108

109
			if (existingField) {
110
				throw new InvalidPayloadError({
111
					reason: `Provided diff is trying to create field "${field}" but it already exists. Please generate a new diff and try again`,
112
				});
113
			}
114
		} else if (diffField.diff[0]?.kind === DiffKind.DELETE) {
115
			const existingField = currentSnapshotWithHash.fields.find(
116
				(f) => f.collection === diffField.collection && f.field === diffField.field,
117
			);
118

119
			if (!existingField) {
120
				throw new InvalidPayloadError({
121
					reason: `Provided diff is trying to delete field "${field}" but it does not exist. Please generate a new diff and try again`,
122
				});
123
			}
124
		}
125
	}
126

127
	for (const diffRelation of applyDiff.diff.relations) {
128
		let relation = `${diffRelation.collection}.${diffRelation.field}`;
129
		if (diffRelation.related_collection) relation += `-> ${diffRelation.related_collection}`;
130

131
		if (diffRelation.diff[0]?.kind === DiffKind.NEW) {
132
			const existingRelation = currentSnapshotWithHash.relations.find(
133
				(r) => r.collection === diffRelation.collection && r.field === diffRelation.field,
134
			);
135

136
			if (existingRelation) {
137
				throw new InvalidPayloadError({
138
					reason: `Provided diff is trying to create relation "${relation}" but it already exists. Please generate a new diff and try again`,
139
				});
140
			}
141
		} else if (diffRelation.diff[0]?.kind === DiffKind.DELETE) {
142
			const existingRelation = currentSnapshotWithHash.relations.find(
143
				(r) => r.collection === diffRelation.collection && r.field === diffRelation.field,
144
			);
145

146
			if (!existingRelation) {
147
				throw new InvalidPayloadError({
148
					reason: `Provided diff is trying to delete relation "${relation}" but it does not exist. Please generate a new diff and try again`,
149
				});
150
			}
151
		}
152
	}
153

154
	throw new InvalidPayloadError({
155
		reason: `Provided hash does not match the current instance's schema hash, indicating the schema has changed after this diff was generated. Please generate a new diff and try again`,
156
	});
157
}
158

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.