const validateBoundaries = function()

in parsers/LU/JS/packages/lu/src/parser/luis/luisValidator.js [36:172]


const validateBoundaries = function(luisJSON) {
    // boundaries documented here - https://docs.microsoft.com/en-us/azure/cognitive-services/luis/luis-boundaries

    // intents: 500 per application: 499 custom intents, and the required None intent.
    if (luisJSON.intents.length > retCode.boundaryLimits.MAX_NUM_INTENTS) {
        validationError(retCode.errorCode.BOUNDARY_INTENTS, `${luisJSON.intents.length} intents found in application. At most ${retCode.boundaryLimits.MAX_NUM_INTENTS} is allowed.`)
    }

    // utterances: 15,000 per application - there is no limit on the number of utterances per intent
    if (luisJSON.utterances.length > retCode.boundaryLimits.MAX_NUM_UTTERANCES) {
        validationError(retCode.errorCode.BOUNDARY_UTTERANCES, `${luisJSON.utterances.length} utterances found in application. At most ${retCode.boundaryLimits.MAX_NUM_UTTERANCES} is allowed.`)
    }

    // pattern.any entities - 100 per application
    if (luisJSON.patternAnyEntities.length > retCode.boundaryLimits.MAX_NUM_PATTERNANY_ENTITIES) {
        validationError(retCode.errorCode.BOUNDARY_PATTERNANYENTITY, `${luisJSON.patternAnyEntities.length} pattern.any entities found in application. At most ${retCode.boundaryLimits.MAX_NUM_PATTERNANY_ENTITIES} is allowed.`)
    }

    // Utterances - 500 characters.
    luisJSON.utterances.forEach(utt => {
        if (utt.text.length > retCode.boundaryLimits.MAX_CHAR_IN_UTTERANCE) {
            validationError(retCode.errorCode.BOUNDARY_UTTERANCE_CHAR_LENGTH, `utterance '${utt.text}' under intent '${utt.intent}' has ${utt.text.length} characters. At most ${retCode.boundaryLimits.MAX_CHAR_IN_UTTERANCE} is allowed.`)
        }
    })

    // patterns - 500 patterns per application.
    if (luisJSON.patterns.length > retCode.boundaryLimits.MAX_NUM_PATTERNS) {
        validationError(retCode.errorCode.BOUNDARY_PATTERNS, `${luisJSON.patterns.length} patterns found in application. At most ${retCode.boundaryLimits.MAX_NUM_PATTERNS} is allowed.`)
    }

    // patterns - Maximum length of pattern is 400 characters.
    luisJSON.patterns.forEach(utt => {
        if (utt.pattern.length > retCode.boundaryLimits.MAX_CHAR_IN_PATTERNS) {
            validationError(retCode.errorCode.BOUNDARY_PATTERN_CHAR_LIMIT, `Pattern '${utt.pattern}' under intent '${utt.intent}' has ${utt.pattern.length} characters. At most ${retCode.boundaryLimits.MAX_CHAR_IN_PATTERNS} characters are allowed in any pattern.`)
        }
    })

    // regex entity - 20 entities.
    if (luisJSON.regex_entities.length > retCode.boundaryLimits.MAX_NUM_REGEX_ENTITIES) {
        validationError(retCode.errorCode.BOUNDARY_REGEX_ENTITY, `${luisJSON.regex_entities.length} regex entities found in application. At most ${retCode.boundaryLimits.MAX_NUM_REGEX_ENTITIES} is allowed.`)
    }

    // regex entity - 500 character max. per regular expression entity pattern
    luisJSON.regex_entities.forEach(utt => {
        if (utt.regexPattern.length > retCode.boundaryLimits.MAX_CHAR_REGEX_ENTITY_PATTERN) {
            validationError(retCode.errorCode.BOUNDARY_REGEX_CHAR_LIMIT, `Regex entity '${utt.name}' with pattern /${utt.regexPattern}/ has ${utt.regexPattern.length} characters. At most ${retCode.boundaryLimits.MAX_CHAR_REGEX_ENTITY_PATTERN} is allowed.`)
        }
    })

    // list entities: max 20000 synonyms.
    luisJSON.closedLists.forEach(listEntity => {
        listEntity.subLists.forEach(subList => {
            if (subList.list.length > retCode.boundaryLimits.MAX_LIST_ENTITY_SYNONYMS) {
                validationError(retCode.errorCode.BOUNDARY_SYNONYMS_LENGTH, `'${listEntity.name}' list entity for parent (normalized value) '${subList.canonicalForm}' has ${subList.list.length} synonyms. At most ${retCode.boundaryLimits.MAX_LIST_ENTITY_SYNONYMS} is allowed.`)
            }
        })
    })

    let phraseLists = luisJSON.model_features || luisJSON.phraseLists || [];
    // phrase list - 500 phrase lists. 
    if (phraseLists.length > retCode.boundaryLimits.MAX_NUM_PHRASE_LISTS) {
        validationError(retCode.errorCode.BOUNDARY_PHRASE_LIST_LIMIT, `${phraseLists.length} phrase lists found in application. At most ${retCode.boundaryLimits.MAX_NUM_PHRASE_LISTS} is allowed.`)
    }

    // phrase list - Maximum number of total phrases per application of 500,000 phrases.
    let totalPhrasesInApp = 0;
    phraseLists.forEach(item => totalPhrasesInApp += item.words.split(',').length);
    if (totalPhrasesInApp > retCode.boundaryLimits.MAX_NUM_PHRASES_IN_ALL_PHRASE_LIST) {
        validationError(retCode.errorCode.BOUNDARY_TOTAL_PHRASES, `${totalPhrasesInApp} phrases found across all phrase list definitions. At most ${retCode.boundaryLimits.MAX_NUM_PHRASES_IN_ALL_PHRASE_LIST} is allowed.`)
    }

    // phrase list - Interchangeable Phraselist has max of 50,000 phrases. 
    totalPhrasesInApp = 0;
    phraseLists.filter(item => item.mode).forEach(item => {
        if (item.words.split(',').length > retCode.boundaryLimits.MAX_INTERCHANGEABLE_PHRASES) {
            validationError(retCode.errorCode.BOUNDARY_INTC_PHRASES_LIMIT, `${totalPhrasesInApp} phrases found across all interchangeable phrase list definitions. At most ${retCode.boundaryLimits.MAX_INTERCHANGEABLE_PHRASES} is allowed.`)
        }
    })

    // phrase list - Non-interchangeable phraselist has max of 5,000 phrases. 
    totalPhrasesInApp = 0;
    phraseLists.filter(item => !item.mode).forEach(item => {
        if (item.words.split(',').length > retCode.boundaryLimits.MAX_NON_INTERCHANGEABLE_PHRASES) {
            validationError(retCode.errorCode.BOUNDARY_NINTC_PHRASES_LIMIT, `${totalPhrasesInApp} phrases found across all non-interchangeable phrase list definitions. At most ${retCode.boundaryLimits.MAX_NON_INTERCHANGEABLE_PHRASES} is allowed.`)
        }
    })

    // Roles - 10 roles per entity
    let totalRoles = 0;
    ["prebuiltEntities", "patternAnyEntities", "regex_entities", "closedLists", "composites", "entities"].forEach(scope => {
        luisJSON[scope].forEach(item => {
            if (item.roles.length > retCode.boundaryLimits.MAX_ROLES_PER_ENTITY) {
                validationError(retCode.errorCode.BOUNDARY_ROLES_PER_ENTITY, `${scope.substring(0, scope.length - 1)} ${item.name} has ${item.roles.length} roles. At most ${retCode.boundaryLimits.MAX_ROLES_PER_ENTITY} is allowed.`)
            } 
            totalRoles += item.roles.length; 
        })
    })

    // Roles - 300 roles per application 
    if (totalRoles > retCode.boundaryLimits.MAX_NUM_ROLES) {
        validationError(retCode.errorCode.BOUNDARY_TOTAL_ROLES, `${totalRoles} role definitions found across all entity types. At most ${retCode.boundaryLimits.MAX_NUM_ROLES} is allowed.`)
    }

    // features - Maximum number of models that can be used as a descriptor (feature) to a specific model to be 10 models.
    ["intents", "entities"].forEach(scope => {
        luisJSON[scope].forEach(item => {
            if (item.features && item.features.length > retCode.boundaryLimits.MAX_NUM_DESCRIPTORS_PER_MODEL) {
                validationError(retCode.errorCode.BOUNDARY_FEATURE_PER_MODEL, `${scope.substring(0, scope.length - 1)} ${item.name} has ${item.features.length} descriptors (feature). At most ${retCode.boundaryLimits.MAX_NUM_DESCRIPTORS_PER_MODEL} is allowed.`)
            }
        })
    })

    // ml entities + roles - A limit of either 100 parent entities or 330 entities, whichever limit the user hits first. A role counts as an entity for the purpose of this boundary. An example is a composite with a simple entity, which has 2 roles is: 1 composite + 1 simple + 2 roles = 4 of the 330 entities.
    let numberOfParentEntities = 0;
    luisJSON.entities.forEach(item => {
        if (item.children && item.children.length > 0) numberOfParentEntities += 1;
    })

    let totalNumberOfEntitiesAndRoles = 0;
    ["prebuiltEntities", "patternAnyEntities", "regex_entities", "closedLists", "composites", "entities"].forEach(item => {
        totalNumberOfEntitiesAndRoles += luisJSON[item].length;
    })
    totalNumberOfEntitiesAndRoles += totalRoles;

    if (numberOfParentEntities > retCode.boundaryLimits.MAX_NUM_PARENT_ENTITIES) {
        validationError(retCode.errorCode.BOUNDARY_PARENT_ENTITY_LIMIT, `${numberOfParentEntities} parent ml entities found in application. At most ${retCode.boundaryLimits.MAX_NUM_PARENT_ENTITIES} is allowed.`)
    }
    
    if (totalNumberOfEntitiesAndRoles > retCode.boundaryLimits.MAX_TOTAL_ENTITES_AND_ROLES) {
        validationError(retCode.errorCode.BOUNDARY_TOTAL_ENTITIES_AND_ROLES, `${totalNumberOfEntitiesAndRoles} combined roles and entity definitions found. At most ${retCode.boundaryLimits.MAX_TOTAL_ENTITES_AND_ROLES} is allowed.`)
    }

    // up to 50 list entities
    if (luisJSON.closedLists.length > retCode.boundaryLimits.MAX_NUM_CLOSED_LISTS) {
        validationError(retCode.errorCode.BOUNDARY_TOTAL_CLOSED_LISTS, `${luisJSON.closedLists.length} list entity definitions found. At most ${retCode.boundaryLimits.MAX_NUM_CLOSED_LISTS} is allowed.`)
    }
}