File size: 8,112 Bytes
08aee07
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
'use strict';

const Joi = require('./index');


const internals = {};


// Preferences

internals.wrap = Joi.string()
    .min(1)
    .max(2)
    .allow(false);


exports.preferences = Joi.object({
    allowUnknown: Joi.boolean(),
    abortEarly: Joi.boolean(),
    artifacts: Joi.boolean(),
    cache: Joi.boolean(),
    context: Joi.object(),
    convert: Joi.boolean(),
    dateFormat: Joi.valid('date', 'iso', 'string', 'time', 'utc'),
    debug: Joi.boolean(),
    errors: {
        escapeHtml: Joi.boolean(),
        label: Joi.valid('path', 'key', false),
        language: [
            Joi.string(),
            Joi.object().ref()
        ],
        render: Joi.boolean(),
        stack: Joi.boolean(),
        wrap: {
            label: internals.wrap,
            array: internals.wrap,
            string: internals.wrap
        }
    },
    externals: Joi.boolean(),
    messages: Joi.object(),
    noDefaults: Joi.boolean(),
    nonEnumerables: Joi.boolean(),
    presence: Joi.valid('required', 'optional', 'forbidden'),
    skipFunctions: Joi.boolean(),
    stripUnknown: Joi.object({
        arrays: Joi.boolean(),
        objects: Joi.boolean()
    })
        .or('arrays', 'objects')
        .allow(true, false),
    warnings: Joi.boolean()
})
    .strict();


// Extensions

internals.nameRx = /^[a-zA-Z0-9]\w*$/;


internals.rule = Joi.object({
    alias: Joi.array().items(Joi.string().pattern(internals.nameRx)).single(),
    args: Joi.array().items(
        Joi.string(),
        Joi.object({
            name: Joi.string().pattern(internals.nameRx).required(),
            ref: Joi.boolean(),
            assert: Joi.alternatives([
                Joi.function(),
                Joi.object().schema()
            ])
                .conditional('ref', { is: true, then: Joi.required() }),
            normalize: Joi.function(),
            message: Joi.string().when('assert', { is: Joi.function(), then: Joi.required() })
        })
    ),
    convert: Joi.boolean(),
    manifest: Joi.boolean(),
    method: Joi.function().allow(false),
    multi: Joi.boolean(),
    validate: Joi.function()
});


exports.extension = Joi.object({
    type: Joi.alternatives([
        Joi.string(),
        Joi.object().regex()
    ])
        .required(),
    args: Joi.function(),
    cast: Joi.object().pattern(internals.nameRx, Joi.object({
        from: Joi.function().maxArity(1).required(),
        to: Joi.function().minArity(1).maxArity(2).required()
    })),
    base: Joi.object().schema()
        .when('type', { is: Joi.object().regex(), then: Joi.forbidden() }),
    coerce: [
        Joi.function().maxArity(3),
        Joi.object({ method: Joi.function().maxArity(3).required(), from: Joi.array().items(Joi.string()).single() })
    ],
    flags: Joi.object().pattern(internals.nameRx, Joi.object({
        setter: Joi.string(),
        default: Joi.any()
    })),
    manifest: {
        build: Joi.function().arity(2)
    },
    messages: [Joi.object(), Joi.string()],
    modifiers: Joi.object().pattern(internals.nameRx, Joi.function().minArity(1).maxArity(2)),
    overrides: Joi.object().pattern(internals.nameRx, Joi.function()),
    prepare: Joi.function().maxArity(3),
    rebuild: Joi.function().arity(1),
    rules: Joi.object().pattern(internals.nameRx, internals.rule),
    terms: Joi.object().pattern(internals.nameRx, Joi.object({
        init: Joi.array().allow(null).required(),
        manifest: Joi.object().pattern(/.+/, [
            Joi.valid('schema', 'single'),
            Joi.object({
                mapped: Joi.object({
                    from: Joi.string().required(),
                    to: Joi.string().required()
                })
                    .required()
            })
        ])
    })),
    validate: Joi.function().maxArity(3)
})
    .strict();


exports.extensions = Joi.array().items(Joi.object(), Joi.function().arity(1)).strict();


// Manifest

internals.desc = {

    buffer: Joi.object({
        buffer: Joi.string()
    }),

    func: Joi.object({
        function: Joi.function().required(),
        options: {
            literal: true
        }
    }),

    override: Joi.object({
        override: true
    }),

    ref: Joi.object({
        ref: Joi.object({
            type: Joi.valid('value', 'global', 'local'),
            path: Joi.array().required(),
            separator: Joi.string().length(1).allow(false),
            ancestor: Joi.number().min(0).integer().allow('root'),
            map: Joi.array().items(Joi.array().length(2)).min(1),
            adjust: Joi.function(),
            iterables: Joi.boolean(),
            in: Joi.boolean(),
            render: Joi.boolean()
        })
            .required()
    }),

    regex: Joi.object({
        regex: Joi.string().min(3)
    }),

    special: Joi.object({
        special: Joi.valid('deep').required()
    }),

    template: Joi.object({
        template: Joi.string().required(),
        options: Joi.object()
    }),

    value: Joi.object({
        value: Joi.alternatives([Joi.object(), Joi.array()]).required()
    })
};


internals.desc.entity = Joi.alternatives([
    Joi.array().items(Joi.link('...')),
    Joi.boolean(),
    Joi.function(),
    Joi.number(),
    Joi.string(),
    internals.desc.buffer,
    internals.desc.func,
    internals.desc.ref,
    internals.desc.regex,
    internals.desc.special,
    internals.desc.template,
    internals.desc.value,
    Joi.link('/')
]);


internals.desc.values = Joi.array()
    .items(
        null,
        Joi.boolean(),
        Joi.function(),
        Joi.number().allow(Infinity, -Infinity),
        Joi.string().allow(''),
        Joi.symbol(),
        internals.desc.buffer,
        internals.desc.func,
        internals.desc.override,
        internals.desc.ref,
        internals.desc.regex,
        internals.desc.template,
        internals.desc.value
    );


internals.desc.messages = Joi.object()
    .pattern(/.+/, [
        Joi.string(),
        internals.desc.template,
        Joi.object().pattern(/.+/, [Joi.string(), internals.desc.template])
    ]);


exports.description = Joi.object({
    type: Joi.string().required(),
    flags: Joi.object({
        cast: Joi.string(),
        default: Joi.any(),
        description: Joi.string(),
        empty: Joi.link('/'),
        failover: internals.desc.entity,
        id: Joi.string(),
        label: Joi.string(),
        only: true,
        presence: ['optional', 'required', 'forbidden'],
        result: ['raw', 'strip'],
        strip: Joi.boolean(),
        unit: Joi.string()
    })
        .unknown(),
    preferences: {
        allowUnknown: Joi.boolean(),
        abortEarly: Joi.boolean(),
        artifacts: Joi.boolean(),
        cache: Joi.boolean(),
        convert: Joi.boolean(),
        dateFormat: ['date', 'iso', 'string', 'time', 'utc'],
        errors: {
            escapeHtml: Joi.boolean(),
            label: ['path', 'key'],
            language: [
                Joi.string(),
                internals.desc.ref
            ],
            wrap: {
                label: internals.wrap,
                array: internals.wrap
            }
        },
        externals: Joi.boolean(),
        messages: internals.desc.messages,
        noDefaults: Joi.boolean(),
        nonEnumerables: Joi.boolean(),
        presence: ['required', 'optional', 'forbidden'],
        skipFunctions: Joi.boolean(),
        stripUnknown: Joi.object({
            arrays: Joi.boolean(),
            objects: Joi.boolean()
        })
            .or('arrays', 'objects')
            .allow(true, false),
        warnings: Joi.boolean()
    },
    allow: internals.desc.values,
    invalid: internals.desc.values,
    rules: Joi.array().min(1).items({
        name: Joi.string().required(),
        args: Joi.object().min(1),
        keep: Joi.boolean(),
        message: [
            Joi.string(),
            internals.desc.messages
        ],
        warn: Joi.boolean()
    }),

    // Terms

    keys: Joi.object().pattern(/.*/, Joi.link('/')),
    link: internals.desc.ref
})
    .pattern(/^[a-z]\w*$/, Joi.any());