You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							121 lines
						
					
					
						
							3.9 KiB
						
					
					
				
			
		
		
	
	
							121 lines
						
					
					
						
							3.9 KiB
						
					
					
				| import type {
 | |
|   AddedFormat,
 | |
|   FormatValidator,
 | |
|   AsyncFormatValidator,
 | |
|   CodeKeywordDefinition,
 | |
|   KeywordErrorDefinition,
 | |
|   ErrorObject,
 | |
| } from "../../types"
 | |
| import type {KeywordCxt} from "../../compile/validate"
 | |
| import {_, str, nil, or, Code, getProperty, regexpCode} from "../../compile/codegen"
 | |
| 
 | |
| type FormatValidate =
 | |
|   | FormatValidator<string>
 | |
|   | FormatValidator<number>
 | |
|   | AsyncFormatValidator<string>
 | |
|   | AsyncFormatValidator<number>
 | |
|   | RegExp
 | |
|   | string
 | |
|   | true
 | |
| 
 | |
| export type FormatError = ErrorObject<"format", {format: string}, string | {$data: string}>
 | |
| 
 | |
| const error: KeywordErrorDefinition = {
 | |
|   message: ({schemaCode}) => str`must match format "${schemaCode}"`,
 | |
|   params: ({schemaCode}) => _`{format: ${schemaCode}}`,
 | |
| }
 | |
| 
 | |
| const def: CodeKeywordDefinition = {
 | |
|   keyword: "format",
 | |
|   type: ["number", "string"],
 | |
|   schemaType: "string",
 | |
|   $data: true,
 | |
|   error,
 | |
|   code(cxt: KeywordCxt, ruleType?: string) {
 | |
|     const {gen, data, $data, schema, schemaCode, it} = cxt
 | |
|     const {opts, errSchemaPath, schemaEnv, self} = it
 | |
|     if (!opts.validateFormats) return
 | |
| 
 | |
|     if ($data) validate$DataFormat()
 | |
|     else validateFormat()
 | |
| 
 | |
|     function validate$DataFormat(): void {
 | |
|       const fmts = gen.scopeValue("formats", {
 | |
|         ref: self.formats,
 | |
|         code: opts.code.formats,
 | |
|       })
 | |
|       const fDef = gen.const("fDef", _`${fmts}[${schemaCode}]`)
 | |
|       const fType = gen.let("fType")
 | |
|       const format = gen.let("format")
 | |
|       // TODO simplify
 | |
|       gen.if(
 | |
|         _`typeof ${fDef} == "object" && !(${fDef} instanceof RegExp)`,
 | |
|         () => gen.assign(fType, _`${fDef}.type || "string"`).assign(format, _`${fDef}.validate`),
 | |
|         () => gen.assign(fType, _`"string"`).assign(format, fDef)
 | |
|       )
 | |
|       cxt.fail$data(or(unknownFmt(), invalidFmt()))
 | |
| 
 | |
|       function unknownFmt(): Code {
 | |
|         if (opts.strictSchema === false) return nil
 | |
|         return _`${schemaCode} && !${format}`
 | |
|       }
 | |
| 
 | |
|       function invalidFmt(): Code {
 | |
|         const callFormat = schemaEnv.$async
 | |
|           ? _`(${fDef}.async ? await ${format}(${data}) : ${format}(${data}))`
 | |
|           : _`${format}(${data})`
 | |
|         const validData = _`(typeof ${format} == "function" ? ${callFormat} : ${format}.test(${data}))`
 | |
|         return _`${format} && ${format} !== true && ${fType} === ${ruleType} && !${validData}`
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     function validateFormat(): void {
 | |
|       const formatDef: AddedFormat | undefined = self.formats[schema]
 | |
|       if (!formatDef) {
 | |
|         unknownFormat()
 | |
|         return
 | |
|       }
 | |
|       if (formatDef === true) return
 | |
|       const [fmtType, format, fmtRef] = getFormat(formatDef)
 | |
|       if (fmtType === ruleType) cxt.pass(validCondition())
 | |
| 
 | |
|       function unknownFormat(): void {
 | |
|         if (opts.strictSchema === false) {
 | |
|           self.logger.warn(unknownMsg())
 | |
|           return
 | |
|         }
 | |
|         throw new Error(unknownMsg())
 | |
| 
 | |
|         function unknownMsg(): string {
 | |
|           return `unknown format "${schema as string}" ignored in schema at path "${errSchemaPath}"`
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       function getFormat(fmtDef: AddedFormat): [string, FormatValidate, Code] {
 | |
|         const code =
 | |
|           fmtDef instanceof RegExp
 | |
|             ? regexpCode(fmtDef)
 | |
|             : opts.code.formats
 | |
|             ? _`${opts.code.formats}${getProperty(schema)}`
 | |
|             : undefined
 | |
|         const fmt = gen.scopeValue("formats", {key: schema, ref: fmtDef, code})
 | |
|         if (typeof fmtDef == "object" && !(fmtDef instanceof RegExp)) {
 | |
|           return [fmtDef.type || "string", fmtDef.validate, _`${fmt}.validate`]
 | |
|         }
 | |
| 
 | |
|         return ["string", fmtDef, fmt]
 | |
|       }
 | |
| 
 | |
|       function validCondition(): Code {
 | |
|         if (typeof formatDef == "object" && !(formatDef instanceof RegExp) && formatDef.async) {
 | |
|           if (!schemaEnv.$async) throw new Error("async format in sync schema")
 | |
|           return _`await ${fmtRef}(${data})`
 | |
|         }
 | |
|         return typeof format == "function" ? _`${fmtRef}(${data})` : _`${fmtRef}.test(${data})`
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| }
 | |
| 
 | |
| export default def
 |