import jsonValidator from 'is-my-json-valid' ; import deref from 'json-schema-deref-sync' ; import type { CollectionFormat , Definition , Document , Parameter , PathItem } from './schema' ; export interface CompiledDefinition extends Definition { validator : ( value : any ) => boolean ; } export interface CompiledParameter extends Parameter { validator : ( value : any ) => boolean ; } export interface CompiledPath { regex : RegExp ; path : PathItem ; name : string ; expected : string [ ] ; requestPath ? : string ; } export type Compiled = ( path : string ) => CompiledPath | undefined ; function stringValidator ( schema ) { const validator = jsonValidator ( schema ) ; return ( inputValue ) => { if ( typeof inputValue === 'undefined' ) { return ! schema . required ; } let value = inputValue ; switch ( schema . type ) { case 'number' : case 'integer' : if ( ! isNaN ( value ) ) { value = Number ( value ) ; } break ; case 'boolean' : if ( value === 'true' ) { value = true ; } else if ( value === 'false' ) { value = false ; } break ; case 'array' : if ( ! Array . isArray ( value ) ) { const format = schema . collectionFormat || 'csv' ; switch ( format ) { case 'csv' : value = String ( value ) . split ( ',' ) ; break ; case 'ssv' : value = String ( value ) . split ( ' ' ) ; break ; case 'tsv' : value = String ( value ) . split ( '\t' ) ; break ; case 'pipes' : value = String ( value ) . split ( '|' ) ; break ; case 'multi' : default : value = [ value ] ; break ; } } switch ( schema . items . type ) { case 'number' : case 'integer' : value = value . map ( ( num ) => { if ( ! isNaN ( num ) ) { return Number ( num ) ; } return num ; } ) ; break ; case 'boolean' : value = value . map ( ( bool ) => { if ( bool === 'true' ) { return true ; } else if ( bool === 'false' ) { return false ; } return bool ; } ) ; break ; default : } break ; default : } return validator ( value ) ; } ; } export function compile ( document ) { const swagger = deref ( document ) ; Object . keys ( swagger . paths ) . forEach ( ( pathName ) => { const path = swagger . paths [ pathName ] ; Object . keys ( path ) . filter ( ( name ) => name !== 'parameters' ) . forEach ( ( operationName ) => { const operation = path [ operationName ] ; const parameters = { } ; const resolveParameter = ( parameter ) => { parameters [ `${ parameter . name }:${ parameter . location }` ] = parameter ; } ; ( path . parameters || [ ] ) . forEach ( resolveParameter ) ; ( operation . parameters || [ ] ) . forEach ( resolveParameter ) ; operation . resolvedParameters = Object . keys ( parameters ) . map ( ( key ) => parameters [ key ] ) ; operation . resolvedParameters . forEach ( ( parameter ) => { const schema = parameter . schema || parameter ; if ( parameter . in === 'query' || parameter . in === 'header' || parameter . in === 'path' ) { parameter . validator = stringValidator ( schema ) ; } else { parameter . validator = jsonValidator ( schema ) ; } } ) ; Object . keys ( operation . responses ) . forEach ( ( statusCode ) => { const response = operation . responses [ statusCode ] ; if ( response . schema ) { response . validator = jsonValidator ( response . schema ) ; } else { response . validator = ( body ) => typeof body === 'undefined' || body === null || body === '' ; } } ) ; } ) ; } ) ; const basePath = swagger . basePath || '' ; const matcher = Object . keys ( swagger . paths ) . map ( ( name ) => ( { name , path : swagger . paths [ name ] , regex : new RegExp ( `^${ basePath . replace ( /\/*$/ , '' ) }${ name . replace ( /{[^}]*}/g , '[^/]+' ) }/?$` ) , expected : ( name . match ( /[^\/]+/g ) || [ ] ) . map ( ( s ) => s . toString ( ) ) , } ) ) ; return ( ( path ) => { const matches = matcher . filter ( ( match ) => Boolean ( path . match ( match . regex ) ) ) ; if ( matches . length === 0 ) { return ; } return { requestPath : path . substring ( ( basePath || '' ) . length ) , ... matches [ 0 ] , } ; } ) as Compiled ; }