1 | import type {CodeKeywordDefinition, ErrorObject, KeywordErrorDefinition} from "../../types"
|
2 | import type {KeywordCxt} from "../../compile/validate"
|
3 | import {checkDataTypes, getSchemaTypes, DataType} from "../../compile/validate/dataType"
|
4 | import {_, str, Name} from "../../compile/codegen"
|
5 | import {useFunc} from "../../compile/util"
|
6 | import equal from "../../runtime/equal"
|
7 |
|
8 | export type UniqueItemsError = ErrorObject<
|
9 | "uniqueItems",
|
10 | {i: number; j: number},
|
11 | boolean | {$data: string}
|
12 | >
|
13 |
|
14 | const error: KeywordErrorDefinition = {
|
15 | message: ({params: {i, j}}) =>
|
16 | str`must NOT have duplicate items (items ## ${j} and ${i} are identical)`,
|
17 | params: ({params: {i, j}}) => _`{i: ${i}, j: ${j}}`,
|
18 | }
|
19 |
|
20 | const def: CodeKeywordDefinition = {
|
21 | keyword: "uniqueItems",
|
22 | type: "array",
|
23 | schemaType: "boolean",
|
24 | $data: true,
|
25 | error,
|
26 | code(cxt: KeywordCxt) {
|
27 | const {gen, data, $data, schema, parentSchema, schemaCode, it} = cxt
|
28 | if (!$data && !schema) return
|
29 | const valid = gen.let("valid")
|
30 | const itemTypes = parentSchema.items ? getSchemaTypes(parentSchema.items) : []
|
31 | cxt.block$data(valid, validateUniqueItems, _`${schemaCode} === false`)
|
32 | cxt.ok(valid)
|
33 |
|
34 | function validateUniqueItems(): void {
|
35 | const i = gen.let("i", _`${data}.length`)
|
36 | const j = gen.let("j")
|
37 | cxt.setParams({i, j})
|
38 | gen.assign(valid, true)
|
39 | gen.if(_`${i} > 1`, () => (canOptimize() ? loopN : loopN2)(i, j))
|
40 | }
|
41 |
|
42 | function canOptimize(): boolean {
|
43 | return itemTypes.length > 0 && !itemTypes.some((t) => t === "object" || t === "array")
|
44 | }
|
45 |
|
46 | function loopN(i: Name, j: Name): void {
|
47 | const item = gen.name("item")
|
48 | const wrongType = checkDataTypes(itemTypes, item, it.opts.strictNumbers, DataType.Wrong)
|
49 | const indices = gen.const("indices", _`{}`)
|
50 | gen.for(_`;${i}--;`, () => {
|
51 | gen.let(item, _`${data}[${i}]`)
|
52 | gen.if(wrongType, _`continue`)
|
53 | if (itemTypes.length > 1) gen.if(_`typeof ${item} == "string"`, _`${item} += "_"`)
|
54 | gen
|
55 | .if(_`typeof ${indices}[${item}] == "number"`, () => {
|
56 | gen.assign(j, _`${indices}[${item}]`)
|
57 | cxt.error()
|
58 | gen.assign(valid, false).break()
|
59 | })
|
60 | .code(_`${indices}[${item}] = ${i}`)
|
61 | })
|
62 | }
|
63 |
|
64 | function loopN2(i: Name, j: Name): void {
|
65 | const eql = useFunc(gen, equal)
|
66 | const outer = gen.name("outer")
|
67 | gen.label(outer).for(_`;${i}--;`, () =>
|
68 | gen.for(_`${j} = ${i}; ${j}--;`, () =>
|
69 | gen.if(_`${eql}(${data}[${i}], ${data}[${j}])`, () => {
|
70 | cxt.error()
|
71 | gen.assign(valid, false).break(outer)
|
72 | })
|
73 | )
|
74 | )
|
75 | }
|
76 | },
|
77 | }
|
78 |
|
79 | export default def
|