winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

ManifestSchemaValidation.cpp (21666B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "winget/Yaml.h"
      5 #include "winget/JsonSchemaValidation.h"
      6 #include "winget/ManifestCommon.h"
      7 #include "winget/ManifestSchemaValidation.h"
      8 #include "winget/ManifestYamlParser.h"
      9 #include "winget/Resources.h"
     10 
     11 #include <ManifestSchema.h>
     12 
     13 namespace AppInstaller::Manifest::YamlParser
     14 {
     15     using namespace std::string_view_literals;
     16 
     17     namespace
     18     {
     19         enum class YamlScalarType
     20         {
     21             String,
     22             Int,
     23             Bool
     24         };
     25 
     26         // List of fields that use non string scalar types
     27         const std::map<std::string_view, YamlScalarType> ManifestFieldTypes =
     28         {
     29             { "InstallerSuccessCodes"sv, YamlScalarType::Int },
     30             { "InstallerAbortsTerminal"sv, YamlScalarType::Bool },
     31             { "InstallLocationRequired"sv, YamlScalarType::Bool },
     32             { "RequireExplicitUpgrade"sv, YamlScalarType::Bool },
     33             { "DisplayInstallWarnings"sv, YamlScalarType::Bool },
     34             { "InstallerReturnCode"sv, YamlScalarType::Int },
     35             { "DownloadCommandProhibited", YamlScalarType::Bool },
     36             { "ArchiveBinariesDependOnPath", YamlScalarType::Bool }
     37         };
     38 
     39         YamlScalarType GetManifestScalarValueType(const std::string& key)
     40         {
     41             auto iter = ManifestFieldTypes.find(key);
     42             if (iter != ManifestFieldTypes.end())
     43             {
     44                 return iter->second;
     45             }
     46 
     47             return YamlScalarType::String;
     48         }
     49 
     50         Json::Value YamlScalarNodeToJson(const YAML::Node& scalarNode, YamlScalarType scalarType)
     51         {
     52             if (scalarType == YamlScalarType::Int)
     53             {
     54                 return Json::Value(scalarNode.as<int>());
     55             }
     56             else if (scalarType == YamlScalarType::Bool)
     57             {
     58                 return Json::Value(scalarNode.as<bool>());
     59             }
     60             else
     61             {
     62                 return Json::Value(scalarNode.as<std::string>());
     63             }
     64         }
     65 
     66         Json::Value ManifestYamlNodeToJson(const YAML::Node& rootNode, YamlScalarType scalarType = YamlScalarType::String)
     67         {
     68             Json::Value result;
     69 
     70             if (rootNode.IsNull())
     71             {
     72                 result = Json::Value::nullSingleton();
     73             }
     74             else if (rootNode.IsMap())
     75             {
     76                 for (auto const& keyValuePair : rootNode.Mapping())
     77                 {
     78                     // We only support string type as key in our manifest
     79                     auto key = keyValuePair.first.as<std::string>();
     80                     result[keyValuePair.first.as<std::string>()] = ManifestYamlNodeToJson(keyValuePair.second, GetManifestScalarValueType(key));
     81                 }
     82             }
     83             else if (rootNode.IsSequence())
     84             {
     85                 for (auto const& value : rootNode.Sequence())
     86                 {
     87                     result.append(ManifestYamlNodeToJson(value, scalarType));
     88                 }
     89             }
     90             else if (rootNode.IsScalar())
     91             {
     92                 result = YamlScalarNodeToJson(rootNode, scalarType);
     93             }
     94             else
     95             {
     96                 THROW_HR(E_UNEXPECTED);
     97             }
     98 
     99             return result;
    100         }
    101 
    102         std::vector<ValidationError> ParseSchemaHeaderString(const YamlManifestInfo& manifestInfo, const ValidationError::Level& errorLevel, std::string& schemaHeaderUrlString)
    103         {
    104             std::vector<ValidationError> errors;
    105             std::string schemaHeader = manifestInfo.DocumentSchemaHeader.SchemaHeader;
    106 
    107             // Remove the leading '#' and any leading/trailing whitespaces
    108             if (schemaHeader[0] == '#')
    109             {
    110                 schemaHeader = schemaHeader.substr(1); // Remove the leading '#'
    111                 schemaHeader = Utility::Trim(schemaHeader); // Trim leading/trailing whitespaces
    112             }
    113 
    114             // Parse the schema header string as YAML string to get the schema header URL
    115             try
    116             {
    117                 auto root = YAML::Load(schemaHeader);
    118 
    119                 if (root.IsNull() || (!root.IsNull() && !root.IsDefined()))
    120                 {
    121                     errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::InvalidSchemaHeader, "", schemaHeader, manifestInfo.DocumentSchemaHeader.Mark.line, manifestInfo.DocumentSchemaHeader.Mark.column, errorLevel, manifestInfo.FileName));
    122                 }
    123                 else
    124                 {
    125                     schemaHeaderUrlString = root[YAML::DocumentSchemaHeader::YamlLanguageServerKey].as<std::string>();
    126                 }
    127             }
    128             catch (const YAML::Exception&)
    129             {
    130                 errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::InvalidSchemaHeader, "", schemaHeader, manifestInfo.DocumentSchemaHeader.Mark.line, manifestInfo.DocumentSchemaHeader.Mark.column, errorLevel, manifestInfo.FileName));
    131             }
    132             catch (const std::exception&)
    133             {
    134                 errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::InvalidSchemaHeader, "", schemaHeader, manifestInfo.DocumentSchemaHeader.Mark.line, manifestInfo.DocumentSchemaHeader.Mark.column, errorLevel, manifestInfo.FileName));
    135             }
    136 
    137             return errors;
    138         }
    139 
    140         bool ParseSchemaHeaderUrl(const std::string& schemaHeaderValue, std::string& schemaType, std::string& schemaVersion)
    141         {
    142             // Use regex to match the pattern of @"winget-manifest\.(?<type>\w+)\.(?<version>[\d\.]+)\.schema\.json$"
    143             std::regex schemaUrlPattern(R"(winget-manifest\.(\w+)\.([\d\.]+)\.schema\.json$)");
    144             std::smatch match;
    145 
    146             if (std::regex_search(schemaHeaderValue, match, schemaUrlPattern))
    147             {
    148                 schemaType = match[1].str();
    149                 schemaVersion = match[2].str();
    150                 return true;
    151             }
    152 
    153             return false;
    154         }
    155 
    156         std::vector<ValidationError> ValidateSchemaHeaderType(const std::string& headerManifestType, const ManifestTypeEnum& expectedManifestType, const YamlManifestInfo& manifestInfo, ValidationError::Level errorLevel)
    157         {
    158             std::vector<ValidationError> errors;
    159             ManifestTypeEnum actualManifestType = ConvertToManifestTypeEnum(headerManifestType);
    160             size_t schemaHeaderTypeIndex = manifestInfo.DocumentSchemaHeader.SchemaHeader.find(headerManifestType) + 1;
    161 
    162             if (actualManifestType != expectedManifestType)
    163             {
    164                 errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::SchemaHeaderManifestTypeMismatch, "", headerManifestType, manifestInfo.DocumentSchemaHeader.Mark.line, schemaHeaderTypeIndex, errorLevel, manifestInfo.FileName));
    165             }
    166 
    167             return errors;
    168         }
    169 
    170         std::vector<ValidationError> ValidateSchemaHeaderVersion(const std::string& headerManifestVersion, const ManifestVer& expectedManifestVersion, const YamlManifestInfo& manifestInfo, ValidationError::Level errorLevel)
    171         {
    172             std::vector<ValidationError> errors;
    173             ManifestVer actualHeaderVersion(headerManifestVersion);
    174             size_t schemaHeaderVersionIndex = manifestInfo.DocumentSchemaHeader.SchemaHeader.find(headerManifestVersion) + 1;
    175 
    176             if (actualHeaderVersion != expectedManifestVersion)
    177             {
    178                 errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::SchemaHeaderManifestVersionMismatch, "", headerManifestVersion, manifestInfo.DocumentSchemaHeader.Mark.line, schemaHeaderVersionIndex, errorLevel, manifestInfo.FileName));
    179             }
    180 
    181             return errors;
    182         }
    183 
    184         bool IsValidSchemaHeaderUrl(const std::string& schemaHeaderUrlString, const YamlManifestInfo& manifestInfo, const ManifestVer& manifestVersion)
    185         {
    186             // Load the schema file to compare the schema header URL with the schema ID in the schema file
    187             Json::Value schemaFile = LoadSchemaDoc(manifestVersion, manifestInfo.ManifestType);
    188 
    189             if (schemaFile.isMember("$id"))
    190             {
    191                 std::string schemaId = schemaFile["$id"].asString();
    192 
    193                 // Prefix schema ID with "schema=" to match the schema header URL pattern and compare it with the schema header URL
    194                 schemaId = "$schema=" + schemaId;
    195 
    196                 if (Utility::CaseInsensitiveEquals(schemaId, schemaHeaderUrlString))
    197                 {
    198                     return true;
    199                 }
    200             }
    201 
    202             return false;
    203         }
    204 
    205         ValidationError GetSchemaHeaderUrlPatternMismatchError(const std::string& schemaHeaderUrlString, const YamlManifestInfo& manifestInfo, const ValidationError::Level& errorLevel)
    206         {
    207             size_t schemaHeaderUrlIndex = manifestInfo.DocumentSchemaHeader.SchemaHeader.find(schemaHeaderUrlString) + 1;
    208 
    209             return ValidationError::MessageContextValueLineLevelWithFile(ManifestError::SchemaHeaderUrlPatternMismatch, "", manifestInfo.DocumentSchemaHeader.SchemaHeader, manifestInfo.DocumentSchemaHeader.Mark.line, schemaHeaderUrlIndex, errorLevel, manifestInfo.FileName);
    210         }
    211 
    212         std::vector<ValidationError> ValidateSchemaHeaderUrl(const YamlManifestInfo& manifestInfo, const ManifestVer& manifestVersion, const ValidationError::Level& errorLevel)
    213         {
    214             std::vector<ValidationError> errors;
    215 
    216             std::string schemaHeaderUrlString;
    217             // Parse the schema header string to get the schema header URL
    218             auto parserErrors = ParseSchemaHeaderString(manifestInfo, errorLevel, schemaHeaderUrlString);
    219             std::move(parserErrors.begin(), parserErrors.end(), std::inserter(errors, errors.end()));
    220 
    221             if (!errors.empty())
    222             {
    223                 return errors;
    224             }
    225 
    226             std::string manifestTypeString;
    227             std::string manifestVersionString;
    228 
    229             // Parse the schema header URL to get the manifest type and version
    230             if (ParseSchemaHeaderUrl(schemaHeaderUrlString, manifestTypeString, manifestVersionString))
    231             {
    232                 auto headerManifestTypeErrors = ValidateSchemaHeaderType(manifestTypeString, manifestInfo.ManifestType, manifestInfo, errorLevel);
    233                 std::move(headerManifestTypeErrors.begin(), headerManifestTypeErrors.end(), std::inserter(errors, errors.end()));
    234 
    235                 auto headerManifestVersionErrors = ValidateSchemaHeaderVersion(manifestVersionString, manifestVersion, manifestInfo, errorLevel);
    236                 std::move(headerManifestVersionErrors.begin(), headerManifestVersionErrors.end(), std::inserter(errors, errors.end()));
    237 
    238                 // Finally, match the entire schema header URL with the schema ID in the schema file to ensure the URL domain matches the schema definition file.
    239                 if (!IsValidSchemaHeaderUrl(schemaHeaderUrlString, manifestInfo, manifestVersion))
    240                 {
    241                     errors.emplace_back(GetSchemaHeaderUrlPatternMismatchError(schemaHeaderUrlString, manifestInfo, errorLevel));
    242                 }
    243             }
    244             else
    245             {
    246                 errors.emplace_back(GetSchemaHeaderUrlPatternMismatchError(schemaHeaderUrlString, manifestInfo, errorLevel));
    247             }
    248 
    249             return errors;
    250         }
    251 
    252         std::vector<ValidationError> ValidateYamlManifestSchemaHeader(const YamlManifestInfo& manifestInfo, const ManifestVer& manifestVersion, ValidationError::Level errorLevel)
    253         {
    254             std::vector<ValidationError> errors;
    255             std::string schemaHeaderString;
    256 
    257             if (manifestInfo.DocumentSchemaHeader.SchemaHeader.empty())
    258             {
    259                 errors.emplace_back(ValidationError::MessageLevelWithFile(ManifestError::SchemaHeaderNotFound, errorLevel, manifestInfo.FileName));
    260                 return errors;
    261             }
    262 
    263             auto parserErrors = ValidateSchemaHeaderUrl(manifestInfo, manifestVersion, errorLevel);
    264             std::move(parserErrors.begin(), parserErrors.end(), std::inserter(errors, errors.end()));
    265 
    266             return errors;
    267         }
    268     }
    269 
    270     Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType)
    271     {
    272         int idx = MANIFESTSCHEMA_NO_RESOURCE;
    273         std::map<ManifestTypeEnum, int> resourceMap;
    274 
    275         if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_12 })
    276         {
    277             resourceMap = {
    278                 { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_12_SINGLETON },
    279                 { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_12_VERSION },
    280                 { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_12_INSTALLER },
    281                 { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_12_DEFAULTLOCALE },
    282                 { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_12_LOCALE },
    283             };
    284         }
    285         else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_10 })
    286         {
    287             resourceMap = {
    288                 { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_10_SINGLETON },
    289                 { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_10_VERSION },
    290                 { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_10_INSTALLER },
    291                 { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_10_DEFAULTLOCALE },
    292                 { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_10_LOCALE },
    293             };
    294         }
    295         else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_9 })
    296         {
    297             resourceMap = {
    298                 { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_9_SINGLETON },
    299                 { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_9_VERSION },
    300                 { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_9_INSTALLER },
    301                 { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_9_DEFAULTLOCALE },
    302                 { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_9_LOCALE },
    303             };
    304         }
    305         else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_7 })
    306         {
    307             resourceMap = {
    308                 { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_7_SINGLETON },
    309                 { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_7_VERSION },
    310                 { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_7_INSTALLER },
    311                 { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_7_DEFAULTLOCALE },
    312                 { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_7_LOCALE },
    313             };
    314         }
    315         else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_6 })
    316         {
    317             resourceMap = {
    318                 { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_6_SINGLETON },
    319                 { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_6_VERSION },
    320                 { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_6_INSTALLER },
    321                 { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_6_DEFAULTLOCALE },
    322                 { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_6_LOCALE },
    323             };
    324         }
    325         else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_5 })
    326         {
    327             resourceMap = {
    328                 { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_5_SINGLETON },
    329                 { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_5_VERSION },
    330                 { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_5_INSTALLER },
    331                 { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_5_DEFAULTLOCALE },
    332                 { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_5_LOCALE },
    333             };
    334         }
    335         else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_4 })
    336         {
    337             resourceMap = {
    338                 { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_4_SINGLETON },
    339                 { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_4_VERSION },
    340                 { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_4_INSTALLER },
    341                 { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_4_DEFAULTLOCALE },
    342                 { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_4_LOCALE },
    343             };
    344         }
    345         else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_2 })
    346         {
    347             resourceMap = {
    348                 { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_2_SINGLETON },
    349                 { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_2_VERSION },
    350                 { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_2_INSTALLER },
    351                 { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_2_DEFAULTLOCALE },
    352                 { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_2_LOCALE },
    353             };
    354         }
    355         else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_1 })
    356         {
    357             resourceMap = {
    358                 { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_1_SINGLETON },
    359                 { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_1_VERSION },
    360                 { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_1_INSTALLER },
    361                 { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_1_DEFAULTLOCALE },
    362                 { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_1_LOCALE },
    363             };
    364         }
    365         else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1 })
    366         {
    367             resourceMap = {
    368                 { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_SINGLETON },
    369                 { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_VERSION },
    370                 { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_INSTALLER },
    371                 { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE },
    372                 { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_LOCALE },
    373             };
    374         }
    375         else
    376         {
    377             resourceMap = {
    378                 { ManifestTypeEnum::Preview, IDX_MANIFEST_SCHEMA_PREVIEW },
    379             };
    380         }
    381 
    382         auto iter = resourceMap.find(manifestType);
    383         if (iter != resourceMap.end())
    384         {
    385             idx = iter->second;
    386         }
    387         else
    388         {
    389             THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
    390         }
    391 
    392         std::string_view schemaStr = Resource::GetResourceAsString(idx, MANIFESTSCHEMA_RESOURCE_TYPE);
    393         return JsonSchema::LoadSchemaDoc(schemaStr);
    394     }
    395 
    396     std::vector<ValidationError> ValidateAgainstSchema(const std::vector<YamlManifestInfo>& manifestList, const ManifestVer& manifestVersion)
    397     {
    398         std::vector<ValidationError> errors;
    399         // A list of schema validator to avoid multiple loadings of same schema
    400         std::map<ManifestTypeEnum, valijson::Schema> schemaList;
    401 
    402         for (const auto& entry : manifestList)
    403         {
    404             if (entry.ManifestType == ManifestTypeEnum::Shadow)
    405             {
    406                 // There's no schema for a shadow manifest.
    407                 continue;
    408             }
    409 
    410             if (schemaList.find(entry.ManifestType) == schemaList.end())
    411             {
    412                 // Copy constructor of valijson::Schema was private
    413                 valijson::Schema& newSchema = schemaList.emplace(
    414                     std::piecewise_construct, std::make_tuple(entry.ManifestType), std::make_tuple()).first->second;
    415                 Json::Value schemaJson = LoadSchemaDoc(manifestVersion, entry.ManifestType);
    416                 JsonSchema::PopulateSchema(schemaJson, newSchema);
    417             }
    418 
    419             const auto& schema = schemaList.find(entry.ManifestType)->second;
    420             Json::Value manifestJson = ManifestYamlNodeToJson(entry.Root);
    421             valijson::ValidationResults results;
    422 
    423             if (!JsonSchema::Validate(schema, manifestJson, results))
    424             {
    425                 errors.emplace_back(ValidationError::MessageContextWithFile(ManifestError::SchemaError, JsonSchema::GetErrorStringFromResults(results), entry.FileName));
    426             }
    427         }
    428 
    429         return errors;
    430     }
    431 
    432     std::vector<ValidationError> ValidateYamlManifestsSchemaHeader(const std::vector<YamlManifestInfo>& manifestList, const ManifestVer& manifestVersion, bool treatErrorAsWarning)
    433     {
    434         std::vector<ValidationError> errors;
    435         ValidationError::Level errorLevel = treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error;
    436 
    437         // Read the manifest schema header and ensure it exists
    438         for (const auto& entry : manifestList)
    439         {
    440             if (entry.ManifestType == ManifestTypeEnum::Shadow)
    441             {
    442                 // There's no schema for a shadow manifest.
    443                 continue;
    444             }
    445 
    446             auto schemaHeaderErrors = ValidateYamlManifestSchemaHeader(entry, manifestVersion, errorLevel);
    447             std::move(schemaHeaderErrors.begin(), schemaHeaderErrors.end(), std::inserter(errors, errors.end()));
    448         }
    449 
    450         return errors;
    451     }
    452 }