/**
Copyright 2021 Forestry.io Holdings, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/ import { addNamespaceToSchema } from '../ast-builder' ; import _ from 'lodash' ; import { sequential } from '../util' ; import * as yup from 'yup' ; import type { TinaFieldEnriched , TinaCloudSchemaEnriched , TinaCloudSchemaBase , TinaCloudCollectionEnriched , TinaCloudTemplateEnriched , TinaCloudCollection , } from '../types' ; import { TinaField } from '..' ; const FIELD_TYPES = [ 'string' , 'number' , 'boolean' , 'datetime' , 'image' , 'reference' , 'object' , 'rich-text' , ] ; export const validateSchema = async ( schema ) : Promise < TinaCloudSchemaBase > => { const schema2 = addNamespaceToSchema ( _ . cloneDeep ( schema ) ) as TinaCloudSchemaEnriched ; const collections = await sequential ( schema2 . collections , async ( collection ) => validateCollection ( collection ) ) ; validationCollectionsPathAndMatch ( collections ) ; return { collections , } ; } ; const validationCollectionsPathAndMatch = ( collections ) => { const paths = collections . map ( ( x ) => x . path ) ; if ( paths . length === new Set ( paths ) . size ) { return ; } const noMatchCollections = collections . filter ( ( x ) => { return typeof x ?. match === 'undefined' ; } ) . map ( ( x ) => x . path ) ; if ( noMatchCollections . length !== new Set ( noMatchCollections ) . size ) { throw new Error ( 'path must be unique when no `match` is provided' ) ; } const hasMatchAndPath = collections . filter ( ( x ) => { return typeof x . path !== 'undefined' && typeof x . match !== 'undefined' ; } ) . map ( ( x ) => `${ x . path }|${ x . match }` ) ; if ( hasMatchAndPath . length !== new Set ( hasMatchAndPath ) . size ) { throw new Error ( 'Both `match` and `path` can not be the same' ) ; } const groupbyPath = collections . reduce ( ( r , a ) => { r [ a . path ] = r [ a . path ] || [ ] ; r [ a . path ] . push ( a ) ; return r ; } , Object . create ( null ) ) ; Object . keys ( groupbyPath ) . forEach ( ( key ) => { const collectionsArr = groupbyPath [ key ] ; if ( collectionsArr . length === 1 ) { return ; } const matches = collectionsArr . filter ( ( x ) => { return typeof x . match !== 'undefined' ; } ) ; if ( matches . length !== collections . length ) { throw new Error ( 'path must be unique when no `match` is provided' ) ; } } ) ; } ; const validateCollection = async ( collection ) : Promise < TinaCloudCollectionEnriched > => { let templates = [ ] ; let fields = [ ] ; const messageName = collection . namespace . join ( '.' ) ; const collectionSchema = yup . object ( { name : yup . string ( ) . matches ( /^[a-zA-Z0-9_]*$/ , { message : ( obj ) => `Collection's "name" must match ${ obj . regex } at ${ messageName }` , } ) . required ( ) , path : yup . string ( ) . required ( ) . transform ( ( value ) => { return value . replace ( /^\/|\/$/g , '' ) ; } ) , } ) ; await collectionSchema . validate ( collection ) ; const validCollection = ( await collectionSchema . cast ( collection ) ) as TinaCloudCollectionEnriched ; if ( validCollection . templates ) { templates = await sequential ( validCollection . templates , async ( template ) => { if ( typeof template === 'string' ) { throw new Error ( `Global templates are not yet supported` ) ; } const fields = await sequential ( template . fields , async ( field ) => { return validateField ( field ) ; } ) ; return { ... validCollection , ... fields , } as TinaCloudTemplateEnriched ; } ) ; } if ( validCollection . fields ) { if ( typeof validCollection . fields === 'string' ) { throw new Error ( `Global templates are not yet supported` ) ; } fields = await sequential ( validCollection . fields , async ( field ) => { return validateField ( field ) ; } ) ; return { ... validCollection , fields , } as TinaCloudCollectionEnriched ; } return collection ; } ; const validateField = async ( field ) : Promise < TinaFieldEnriched > => { const messageName = field . namespace . join ( '.' ) ; const schema = yup . object ( { name : yup . string ( ) . matches ( /^[a-zA-Z0-9_]*$/ , { message : ( obj ) => `Field's 'name' must match ${ obj . regex } at ${ messageName }` , } ) . required ( ) , type : yup . string ( ) . oneOf ( FIELD_TYPES , ( obj ) => `'type' must be one of: ${ obj . values }, but got '${ obj . value }' at ${ messageName }` ) , } ) ; await schema . validate ( field ) ; return field ; } ;