| Line | Branch | Exec | Source | 
|---|---|---|---|
| 1 | /* | ||
| 2 | * Copyright (c) 2024 Muhammad Nawaz | ||
| 3 | * Licensed under the MIT License. See LICENSE file for more information. | ||
| 4 | */ | ||
| 5 | // [ END OF LICENSE c6bd0f49d040fca8d8a9cb05868e66aa63f0e2e0 ] | ||
| 6 | |||
| 7 | #include "oas_validator_imp.hpp" | ||
| 8 | #include <algorithm> | ||
| 9 | #include <fstream> | ||
| 10 | #include <rapidjson/istreamwrapper.h> | ||
| 11 | #include <sstream> | ||
| 12 | |||
| 13 | 7 | OASValidatorImp::OASValidatorImp(const std::string& oas_specs, | |
| 14 | 7 | const std::unordered_map<std::string, std::unordered_set<std::string>>& method_map) | |
| 15 | 10/20✓ Branch 3 taken 7 times. ✗ Branch 4 not taken. ✓ Branch 7 taken 7 times. ✗ Branch 8 not taken. ✓ Branch 11 taken 7 times. ✗ Branch 12 not taken. ✓ Branch 15 taken 7 times. ✗ Branch 16 not taken. ✓ Branch 19 taken 7 times. ✗ Branch 20 not taken. ✓ Branch 23 taken 7 times. ✗ Branch 24 not taken. ✓ Branch 27 taken 7 times. ✗ Branch 28 not taken. ✓ Branch 31 taken 7 times. ✗ Branch 32 not taken. ✓ Branch 35 taken 7 times. ✗ Branch 36 not taken. ✓ Branch 38 taken 7 times. ✗ Branch 39 not taken. | 7 | : method_map_(BuildCaseInsensitiveMap(method_map)) | 
| 16 | { | ||
| 17 | 1/2✓ Branch 1 taken 7 times. ✗ Branch 2 not taken. | 7 | rapidjson::Document doc; | 
| 18 | 2/2✓ Branch 1 taken 6 times. ✓ Branch 2 taken 1 times. | 7 | ParseSpecs(oas_specs, doc); | 
| 19 | 1/2✓ Branch 2 taken 6 times. ✗ Branch 3 not taken. | 6 | ResolveReferences(doc, doc, doc.GetAllocator()); | 
| 20 | |||
| 21 | 1/2✓ Branch 1 taken 6 times. ✗ Branch 2 not taken. | 6 | const rapidjson::Value& paths = doc["paths"]; | 
| 22 | 6 | std::vector<std::string> ref_keys; | |
| 23 | 1/2✓ Branch 1 taken 6 times. ✗ Branch 2 not taken. | 6 | ref_keys.emplace_back("paths"); | 
| 24 | |||
| 25 | 4/6✓ Branch 1 taken 6 times. ✗ Branch 2 not taken. ✓ Branch 5 taken 534 times. ✗ Branch 6 not taken. ✓ Branch 8 taken 528 times. ✓ Branch 9 taken 6 times. | 534 | for (auto path_itr = paths.MemberBegin(); path_itr != paths.MemberEnd(); ++path_itr) { | 
| 26 | 1/2✓ Branch 1 taken 528 times. ✗ Branch 2 not taken. | 528 | ProcessPath(path_itr, ref_keys); | 
| 27 | } | ||
| 28 | 10 | } | |
| 29 | |||
| 30 | 12 | ValidationError OASValidatorImp::ValidateRoute(const std::string& method, const std::string& http_path, | |
| 31 | std::string& error_msg) | ||
| 32 | { | ||
| 33 | ValidatorsStore* validators; | ||
| 34 | 1/2✓ Branch 1 taken 12 times. ✗ Branch 2 not taken. | 24 | return GetValidators(method, http_path, validators, error_msg); | 
| 35 | } | ||
| 36 | |||
| 37 | 4 | ValidationError OASValidatorImp::ValidateBody(const std::string& method, const std::string& http_path, | |
| 38 | const std::string& json_body, std::string& error_msg) | ||
| 39 | { | ||
| 40 | ValidatorsStore* validators; | ||
| 41 | |||
| 42 | 1/2✓ Branch 1 taken 4 times. ✗ Branch 2 not taken. | 4 | auto err_code = GetValidators(method, http_path, validators, error_msg); | 
| 43 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 4 times. | 4 | CHECK_ERROR(err_code) | 
| 44 | |||
| 45 | 1/2✓ Branch 1 taken 4 times. ✗ Branch 2 not taken. | 4 | return validators->ValidateBody(json_body, error_msg); | 
| 46 | } | ||
| 47 | |||
| 48 | 12 | ValidationError OASValidatorImp::ValidatePathParam(const std::string& method, const std::string& http_path, | |
| 49 | std::string& error_msg) | ||
| 50 | { | ||
| 51 | 12 | std::unordered_map<size_t, ParamRange> param_idxs; | |
| 52 | ValidatorsStore* validators; | ||
| 53 | |||
| 54 | 1/2✓ Branch 1 taken 12 times. ✗ Branch 2 not taken. | 12 | auto err_code = GetValidators(method, http_path, validators, error_msg, ¶m_idxs); | 
| 55 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 12 times. | 12 | CHECK_ERROR(err_code) | 
| 56 | |||
| 57 | 1/2✓ Branch 1 taken 12 times. ✗ Branch 2 not taken. | 12 | return validators->ValidatePathParams(param_idxs, error_msg); | 
| 58 | 12 | } | |
| 59 | |||
| 60 | 6 | ValidationError OASValidatorImp::ValidateQueryParam(const std::string& method, const std::string& http_path, | |
| 61 | std::string& error_msg) | ||
| 62 | { | ||
| 63 | 6 | std::string query; | |
| 64 | ValidatorsStore* validators; | ||
| 65 | |||
| 66 | 1/2✓ Branch 1 taken 6 times. ✗ Branch 2 not taken. | 6 | auto err_code = GetValidators(method, http_path, validators, error_msg, nullptr, &query); | 
| 67 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 6 times. | 6 | CHECK_ERROR(err_code) | 
| 68 | |||
| 69 | 1/2✓ Branch 1 taken 6 times. ✗ Branch 2 not taken. | 6 | return validators->ValidateQueryParams(query, error_msg); | 
| 70 | 6 | } | |
| 71 | |||
| 72 | 6 | ValidationError OASValidatorImp::ValidateHeaders(const std::string& method, const std::string& http_path, | |
| 73 | const std::unordered_map<std::string, std::string>& headers, | ||
| 74 | std::string& error_msg) | ||
| 75 | { | ||
| 76 | ValidatorsStore* validators; | ||
| 77 | |||
| 78 | 1/2✓ Branch 1 taken 6 times. ✗ Branch 2 not taken. | 6 | auto err_code = GetValidators(method, http_path, validators, error_msg); | 
| 79 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 6 times. | 6 | CHECK_ERROR(err_code) | 
| 80 | |||
| 81 | 1/2✓ Branch 1 taken 6 times. ✗ Branch 2 not taken. | 6 | return validators->ValidateHeaderParams(headers, error_msg); | 
| 82 | } | ||
| 83 | |||
| 84 | 17 | ValidationError OASValidatorImp::ValidateRequest(const std::string& method, const std::string& http_path, | |
| 85 | std::string& error_msg) | ||
| 86 | { | ||
| 87 | 17 | std::unordered_map<size_t, ParamRange> param_idxs; | |
| 88 | 17 | std::string query; | |
| 89 | ValidatorsStore* validators; | ||
| 90 | |||
| 91 | 1/2✓ Branch 1 taken 17 times. ✗ Branch 2 not taken. | 17 | auto err_code = GetValidators(method, http_path, validators, error_msg, ¶m_idxs, &query); | 
| 92 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 17 times. | 17 | CHECK_ERROR(err_code) | 
| 93 | |||
| 94 | 1/2✓ Branch 1 taken 17 times. ✗ Branch 2 not taken. | 17 | err_code = validators->ValidatePathParams(param_idxs, error_msg); | 
| 95 | 2/2✓ Branch 0 taken 7 times. ✓ Branch 1 taken 10 times. | 17 | CHECK_ERROR(err_code) | 
| 96 | |||
| 97 | 1/2✓ Branch 1 taken 10 times. ✗ Branch 2 not taken. | 10 | return validators->ValidateQueryParams(query, error_msg); | 
| 98 | 17 | } | |
| 99 | |||
| 100 | 1 | ValidationError OASValidatorImp::ValidateRequest(const std::string& method, const std::string& http_path, | |
| 101 | const std::string& json_body, std::string& error_msg) | ||
| 102 | { | ||
| 103 | 1 | std::unordered_map<size_t, ParamRange> param_idxs; | |
| 104 | 1 | std::string query; | |
| 105 | ValidatorsStore* validators; | ||
| 106 | |||
| 107 | 1/2✓ Branch 1 taken 1 times. ✗ Branch 2 not taken. | 1 | auto err_code = GetValidators(method, http_path, validators, error_msg, ¶m_idxs, &query); | 
| 108 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 1 times. | 1 | CHECK_ERROR(err_code) | 
| 109 | |||
| 110 | 1/2✓ Branch 1 taken 1 times. ✗ Branch 2 not taken. | 1 | err_code = validators->ValidateBody(json_body, error_msg); | 
| 111 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 1 times. | 1 | CHECK_ERROR(err_code) | 
| 112 | |||
| 113 | 1/2✓ Branch 1 taken 1 times. ✗ Branch 2 not taken. | 1 | err_code = validators->ValidatePathParams(param_idxs, error_msg); | 
| 114 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 1 times. | 1 | CHECK_ERROR(err_code) | 
| 115 | |||
| 116 | 1/2✓ Branch 1 taken 1 times. ✗ Branch 2 not taken. | 1 | return validators->ValidateQueryParams(query, error_msg); | 
| 117 | 1 | } | |
| 118 | |||
| 119 | 3 | ValidationError OASValidatorImp::ValidateRequest(const std::string& method, const std::string& http_path, | |
| 120 | const std::unordered_map<std::string, std::string>& headers, | ||
| 121 | std::string& error_msg) | ||
| 122 | { | ||
| 123 | 3 | std::unordered_map<size_t, ParamRange> param_idxs; | |
| 124 | 3 | std::string query; | |
| 125 | ValidatorsStore* validators; | ||
| 126 | |||
| 127 | 1/2✓ Branch 1 taken 3 times. ✗ Branch 2 not taken. | 3 | auto err_code = GetValidators(method, http_path, validators, error_msg, ¶m_idxs, &query); | 
| 128 | 2/2✓ Branch 0 taken 2 times. ✓ Branch 1 taken 1 times. | 3 | CHECK_ERROR(err_code) | 
| 129 | |||
| 130 | 1/2✓ Branch 1 taken 1 times. ✗ Branch 2 not taken. | 1 | err_code = validators->ValidatePathParams(param_idxs, error_msg); | 
| 131 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 1 times. | 1 | CHECK_ERROR(err_code) | 
| 132 | |||
| 133 | 1/2✓ Branch 1 taken 1 times. ✗ Branch 2 not taken. | 1 | err_code = validators->ValidateQueryParams(query, error_msg); | 
| 134 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 1 times. | 1 | CHECK_ERROR(err_code) | 
| 135 | |||
| 136 | 1/2✓ Branch 1 taken 1 times. ✗ Branch 2 not taken. | 1 | return validators->ValidateHeaderParams(headers, error_msg); | 
| 137 | 3 | } | |
| 138 | |||
| 139 | 3 | ValidationError OASValidatorImp::ValidateRequest(const std::string& method, const std::string& http_path, | |
| 140 | const std::string& json_body, | ||
| 141 | const std::unordered_map<std::string, std::string>& headers, | ||
| 142 | std::string& error_msg) | ||
| 143 | { | ||
| 144 | 3 | std::unordered_map<size_t, ParamRange> param_idxs; | |
| 145 | 3 | std::string query; | |
| 146 | ValidatorsStore* validators; | ||
| 147 | |||
| 148 | 1/2✓ Branch 1 taken 3 times. ✗ Branch 2 not taken. | 3 | auto err_code = GetValidators(method, http_path, validators, error_msg, ¶m_idxs, &query); | 
| 149 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 3 times. | 3 | CHECK_ERROR(err_code) | 
| 150 | |||
| 151 | 1/2✓ Branch 1 taken 3 times. ✗ Branch 2 not taken. | 3 | err_code = validators->ValidateBody(json_body, error_msg); | 
| 152 | 2/2✓ Branch 0 taken 1 times. ✓ Branch 1 taken 2 times. | 3 | CHECK_ERROR(err_code) | 
| 153 | |||
| 154 | 1/2✓ Branch 1 taken 2 times. ✗ Branch 2 not taken. | 2 | err_code = validators->ValidatePathParams(param_idxs, error_msg); | 
| 155 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 2 times. | 2 | CHECK_ERROR(err_code) | 
| 156 | |||
| 157 | 1/2✓ Branch 1 taken 2 times. ✗ Branch 2 not taken. | 2 | err_code = validators->ValidateQueryParams(query, error_msg); | 
| 158 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 2 times. | 2 | CHECK_ERROR(err_code) | 
| 159 | |||
| 160 | 1/2✓ Branch 1 taken 2 times. ✗ Branch 2 not taken. | 2 | return validators->ValidateHeaderParams(headers, error_msg); | 
| 161 | 3 | } | |
| 162 | |||
| 163 | 12 | OASValidatorImp::~OASValidatorImp() | |
| 164 | { | ||
| 165 | #ifndef LUA_OAS_VALIDATOR // LUA manages garbage collection itself | ||
| 166 | 2/2✓ Branch 2 taken 54 times. ✓ Branch 3 taken 6 times. | 60 | for (auto& per_method_validator : oas_validators_) { | 
| 167 | 2/2✓ Branch 5 taken 528 times. ✓ Branch 6 taken 54 times. | 582 | for (auto& per_path_validator : per_method_validator.per_path_validators) { | 
| 168 | 1/2✓ Branch 0 taken 528 times. ✗ Branch 1 not taken. | 528 | delete per_path_validator.second; | 
| 169 | } | ||
| 170 | } | ||
| 171 | #endif | ||
| 172 | 6 | } | |
| 173 | |||
| 174 | 64 | ValidationError OASValidatorImp::GetValidators(const std::string& method, const std::string& http_path, | |
| 175 | ValidatorsStore*& validators, std::string& error_msg, | ||
| 176 | std::unordered_map<size_t, ParamRange>* param_idxs, std::string* query) | ||
| 177 | { | ||
| 178 | 64 | auto err_code = method_validator_.Validate(method, error_msg); | |
| 179 | 2/2✓ Branch 0 taken 1 times. ✓ Branch 1 taken 63 times. | 64 | CHECK_ERROR(err_code) | 
| 180 | |||
| 181 | 63 | err_code = GetValidators(method, method, http_path, validators, error_msg, param_idxs, query); | |
| 182 | 2/2✓ Branch 0 taken 1 times. ✓ Branch 1 taken 62 times. | 63 | if (ValidationError::INVALID_ROUTE == err_code) { | 
| 183 | try { | ||
| 184 | 1/4✗ Branch 1 not taken. ✓ Branch 2 taken 1 times. ✗ Branch 4 not taken. ✗ Branch 5 not taken. | 1 | auto mapped_methods = method_map_.at(method); | 
| 185 | ✗ | for (const auto& mapped_method : mapped_methods) { | |
| 186 | ✗ | err_code = GetValidators(method, mapped_method, http_path, validators, error_msg, param_idxs, query); | |
| 187 | ✗ | if (ValidationError::NONE == err_code) { | |
| 188 | ✗ | return err_code; | |
| 189 | } | ||
| 190 | } | ||
| 191 | 1/4✗ Branch 1 not taken. ✗ Branch 2 not taken. ✗ Branch 4 not taken. ✓ Branch 5 taken 1 times. | 1 | } catch (const std::out_of_range&) { | 
| 192 | 1 | return err_code; | |
| 193 | 1 | } | |
| 194 | } | ||
| 195 | |||
| 196 | 62 | return err_code; | |
| 197 | } | ||
| 198 | |||
| 199 | 63 | ValidationError OASValidatorImp::GetValidators(const std::string& method, const std::string& mapped_method, | |
| 200 | const std::string& http_path, ValidatorsStore*& validators, | ||
| 201 | std::string& error_msg, | ||
| 202 | std::unordered_map<size_t, ParamRange>* param_idxs, std::string* query) | ||
| 203 | { | ||
| 204 | 63 | auto enum_method = kStringToMethod.at(mapped_method); | |
| 205 | |||
| 206 | 63 | auto query_pos = http_path.find('?'); | |
| 207 | 3/4✓ Branch 0 taken 11 times. ✓ Branch 1 taken 52 times. ✓ Branch 2 taken 11 times. ✗ Branch 3 not taken. | 63 | if (std::string::npos != query_pos && query) { | 
| 208 | 11 | *query = http_path.substr(query_pos); | |
| 209 | } | ||
| 210 | |||
| 211 | try { | ||
| 212 | // 1st. try, no path params | ||
| 213 | 2/2✓ Branch 2 taken 20 times. ✓ Branch 3 taken 43 times. | 126 | validators = oas_validators_[static_cast<size_t>(enum_method)].per_path_validators.at( | 
| 214 | 4/6✓ Branch 0 taken 52 times. ✓ Branch 1 taken 11 times. ✓ Branch 3 taken 52 times. ✗ Branch 4 not taken. ✓ Branch 6 taken 11 times. ✗ Branch 7 not taken. | 169 | std::string::npos == query_pos ? http_path : http_path.substr(0, query_pos)); | 
| 215 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 43 times. | 43 | } catch (const std::out_of_range&) { | 
| 216 | // 2nd try, if path has dynamic path parameters | ||
| 217 | 43 | std::string map_key; | |
| 218 | 1/2✓ Branch 2 taken 43 times. ✗ Branch 3 not taken. | 43 | map_key.reserve(http_path.length() + 32); | 
| 219 | 43 | const char* beg = http_path.c_str(); | |
| 220 | 2/2✓ Branch 1 taken 40 times. ✓ Branch 2 taken 3 times. | 43 | const char* const end = http_path.c_str() + (std::string::npos == query_pos ? http_path.length() : query_pos); | 
| 221 | 3/4✓ Branch 0 taken 31 times. ✓ Branch 1 taken 12 times. ✓ Branch 4 taken 31 times. ✗ Branch 5 not taken. | 43 | bool found = param_idxs ? oas_validators_[static_cast<size_t>(enum_method)].path_trie.Search(beg, end, map_key, | 
| 222 | *param_idxs) | ||
| 223 | 1/2✓ Branch 2 taken 12 times. ✗ Branch 3 not taken. | 12 | : oas_validators_[static_cast<size_t>(enum_method)].path_trie.Search(beg, end, map_key); | 
| 224 | 2/2✓ Branch 0 taken 42 times. ✓ Branch 1 taken 1 times. | 43 | if (found) { | 
| 225 | try { | ||
| 226 | 1/2✓ Branch 2 taken 42 times. ✗ Branch 3 not taken. | 42 | validators = oas_validators_[static_cast<size_t>(enum_method)].per_path_validators.at(map_key); | 
| 227 | ✗ | } catch (const std::out_of_range&) { | |
| 228 | ✗ | error_msg = R"({"errorCode":"INVALID_ROUTE","details":{"description": "Invalid HTTP method ')" + | |
| 229 | ✗ | method + "' or path: '" + http_path + R"('"}})"; | |
| 230 | ✗ | return ValidationError::INVALID_ROUTE; | |
| 231 | ✗ | } | |
| 232 | } else { | ||
| 233 | 2/4✓ Branch 1 taken 1 times. ✗ Branch 2 not taken. ✓ Branch 4 taken 1 times. ✗ Branch 5 not taken. | 2 | error_msg = R"({"errorCode":"INVALID_ROUTE","details":{"description": "Invalid HTTP method ')" + method + | 
| 234 | 2/4✓ Branch 1 taken 1 times. ✗ Branch 2 not taken. ✓ Branch 4 taken 1 times. ✗ Branch 5 not taken. | 1 | "' or path: '" + http_path + R"('"}})"; | 
| 235 | 1 | return ValidationError::INVALID_ROUTE; | |
| 236 | } | ||
| 237 | 4/4✓ Branch 1 taken 42 times. ✓ Branch 2 taken 1 times. ✓ Branch 4 taken 1 times. ✓ Branch 5 taken 42 times. | 86 | } | 
| 238 | 62 | return ValidationError::NONE; | |
| 239 | } | ||
| 240 | |||
| 241 | ✗ | std::vector<std::string> OASValidatorImp::Split(const std::string& str) | |
| 242 | { | ||
| 243 | ✗ | std::vector<std::string> tokens; | |
| 244 | ✗ | std::string token; | |
| 245 | ✗ | std::istringstream token_stream(str); | |
| 246 | ✗ | while (getline(token_stream, token, '/')) { | |
| 247 | ✗ | tokens.push_back(token); | |
| 248 | } | ||
| 249 | ✗ | return tokens; | |
| 250 | ✗ | } | |
| 251 | |||
| 252 | ✗ | rapidjson::Value* OASValidatorImp::ResolvePath(rapidjson::Document& doc, const std::string& path) | |
| 253 | { | ||
| 254 | ✗ | std::vector<std::string> parts = Split(path); | |
| 255 | ✗ | rapidjson::Value* current = &doc; | |
| 256 | ✗ | for (const std::string& part : parts) { | |
| 257 | ✗ | if (!current->IsObject() || !current->HasMember(part.c_str())) { | |
| 258 | ✗ | throw ValidatorInitExc("Invalid path or missing member:" + part); | |
| 259 | } | ||
| 260 | ✗ | current = &(*current)[part.c_str()]; | |
| 261 | } | ||
| 262 | ✗ | return current; | |
| 263 | ✗ | } | |
| 264 | |||
| 265 | 7 | void OASValidatorImp::ParseSpecs(const std::string& oas_specs, rapidjson::Document& doc) | |
| 266 | { | ||
| 267 | 1/2✓ Branch 1 taken 7 times. ✗ Branch 2 not taken. | 7 | std::ifstream ifs(oas_specs); | 
| 268 | |||
| 269 | 3/4✓ Branch 1 taken 7 times. ✗ Branch 2 not taken. ✓ Branch 3 taken 6 times. ✓ Branch 4 taken 1 times. | 7 | if (ifs.is_open()) { | 
| 270 | 1/2✓ Branch 1 taken 6 times. ✗ Branch 2 not taken. | 6 | rapidjson::IStreamWrapper isw(ifs); | 
| 271 | 1/2✓ Branch 1 taken 6 times. ✗ Branch 2 not taken. | 6 | doc.ParseStream(isw); | 
| 272 | 1/2✓ Branch 1 taken 6 times. ✗ Branch 2 not taken. | 6 | ifs.close(); | 
| 273 | } else { | ||
| 274 | 1/2✓ Branch 2 taken 1 times. ✗ Branch 3 not taken. | 1 | doc.Parse(oas_specs.c_str()); | 
| 275 | } | ||
| 276 | |||
| 277 | 2/2✓ Branch 1 taken 1 times. ✓ Branch 2 taken 6 times. | 7 | if (doc.HasParseError()) { | 
| 278 | 2/4✓ Branch 1 taken 1 times. ✗ Branch 2 not taken. ✓ Branch 4 taken 1 times. ✗ Branch 5 not taken. | 3 | throw ValidatorInitExc("Unable to parse specs: " + oas_specs + | 
| 279 | 3/6✓ Branch 2 taken 1 times. ✗ Branch 3 not taken. ✓ Branch 5 taken 1 times. ✗ Branch 6 not taken. ✓ Branch 8 taken 1 times. ✗ Branch 9 not taken. | 4 | " \nError code: " + std::to_string(doc.GetParseError()) + | 
| 280 | 3/6✓ Branch 2 taken 1 times. ✗ Branch 3 not taken. ✓ Branch 5 taken 1 times. ✗ Branch 6 not taken. ✓ Branch 8 taken 1 times. ✗ Branch 9 not taken. | 4 | " at offset: " + std::to_string(doc.GetErrorOffset()) + | 
| 281 | 1/2✓ Branch 4 taken 1 times. ✗ Branch 5 not taken. | 4 | " Error message: " + rapidjson::GetParseError_En(doc.GetParseError())); | 
| 282 | } | ||
| 283 | 7 | } | |
| 284 | |||
| 285 | 528 | void OASValidatorImp::ProcessPath(const rapidjson::Value::ConstMemberIterator& path_itr, | |
| 286 | std::vector<std::string>& ref_keys) | ||
| 287 | { | ||
| 288 | 2/4✓ Branch 3 taken 528 times. ✗ Branch 4 not taken. ✓ Branch 6 taken 528 times. ✗ Branch 7 not taken. | 528 | std::string path(path_itr->name.GetString()); | 
| 289 | 2/4✓ Branch 1 taken 528 times. ✗ Branch 2 not taken. ✓ Branch 4 taken 528 times. ✗ Branch 5 not taken. | 528 | ref_keys.emplace_back(EscapeSlash(path)); | 
| 290 | 528 | const rapidjson::Value& methods = path_itr->value; | |
| 291 | |||
| 292 | 4/6✓ Branch 1 taken 528 times. ✗ Branch 2 not taken. ✓ Branch 5 taken 1056 times. ✗ Branch 6 not taken. ✓ Branch 8 taken 528 times. ✓ Branch 9 taken 528 times. | 1056 | for (auto method_itr = methods.MemberBegin(); method_itr != methods.MemberEnd(); ++method_itr) { | 
| 293 | 1/2✓ Branch 1 taken 528 times. ✗ Branch 2 not taken. | 528 | ProcessMethod(method_itr, path, ref_keys); | 
| 294 | } | ||
| 295 | |||
| 296 | 528 | ref_keys.pop_back(); // Pop the path key | |
| 297 | 528 | } | |
| 298 | |||
| 299 | 528 | void OASValidatorImp::ProcessMethod(const rapidjson::Value::ConstMemberIterator& method_itr, const std::string& path, | |
| 300 | std::vector<std::string>& ref_keys) | ||
| 301 | { | ||
| 302 | 1/2✓ Branch 3 taken 528 times. ✗ Branch 4 not taken. | 528 | ref_keys.emplace_back(method_itr->name.GetString()); | 
| 303 | 3/6✓ Branch 3 taken 528 times. ✗ Branch 4 not taken. ✓ Branch 6 taken 528 times. ✗ Branch 7 not taken. ✓ Branch 9 taken 528 times. ✗ Branch 10 not taken. | 528 | auto method(kStringToMethod.at(method_itr->name.GetString())); | 
| 304 | 528 | auto& per_method_validator = oas_validators_[static_cast<size_t>(method)]; | |
| 305 | 528 | auto& per_path_validator = per_method_validator.per_path_validators; | |
| 306 | |||
| 307 | 528 | ProcessRequestBody(method_itr, path, ref_keys, per_path_validator); | |
| 308 | 528 | ProcessParameters(method_itr, path, ref_keys, per_path_validator); | |
| 309 | |||
| 310 | 5/6✓ Branch 1 taken 186 times. ✓ Branch 2 taken 342 times. ✓ Branch 4 taken 186 times. ✗ Branch 5 not taken. ✓ Branch 6 taken 186 times. ✓ Branch 7 taken 342 times. | 528 | if (std::string::npos != path.find('{') && std::string::npos != path.find('}')) { // has path params | 
| 311 | 186 | per_method_validator.path_trie.Insert(path); | |
| 312 | } | ||
| 313 | |||
| 314 | 528 | ref_keys.pop_back(); // Pop the method key | |
| 315 | 528 | } | |
| 316 | |||
| 317 | 528 | void OASValidatorImp::ProcessRequestBody(const rapidjson::Value::ConstMemberIterator& method_itr, | |
| 318 | const std::string& path, std::vector<std::string>& ref_keys, | ||
| 319 | std::unordered_map<std::string, ValidatorsStore*>& per_path_validator) | ||
| 320 | { | ||
| 321 | 1/2✓ Branch 5 taken 126 times. ✗ Branch 6 not taken. | 654 | if ((method_itr->value.HasMember("requestBody")) && (method_itr->value["requestBody"].HasMember("content")) && | 
| 322 | 5/6✓ Branch 0 taken 126 times. ✓ Branch 1 taken 402 times. ✓ Branch 6 taken 126 times. ✗ Branch 7 not taken. ✓ Branch 8 taken 126 times. ✓ Branch 9 taken 402 times. | 780 | (method_itr->value["requestBody"]["content"].HasMember("application/json")) && | 
| 323 | 1/2✓ Branch 5 taken 126 times. ✗ Branch 6 not taken. | 126 | (method_itr->value["requestBody"]["content"]["application/json"].HasMember( | 
| 324 | "schema"))) { // if "method+path" has json body | ||
| 325 | 126 | ref_keys.emplace_back("requestBody/content/application%2Fjson/schema"); | |
| 326 | 126 | per_path_validator.emplace( | |
| 327 | path, | ||
| 328 | 2/4✓ Branch 7 taken 126 times. ✗ Branch 8 not taken. ✓ Branch 10 taken 126 times. ✗ Branch 11 not taken. | 126 | new ValidatorsStore(method_itr->value["requestBody"]["content"]["application/json"]["schema"], ref_keys)); | 
| 329 | 126 | ref_keys.pop_back(); // pop body ref | |
| 330 | } else { // Otherwise validators without body | ||
| 331 | 1/2✓ Branch 3 taken 402 times. ✗ Branch 4 not taken. | 402 | per_path_validator.emplace(path, new ValidatorsStore()); | 
| 332 | } | ||
| 333 | 528 | } | |
| 334 | |||
| 335 | 528 | void OASValidatorImp::ProcessParameters(const rapidjson::Value::ConstMemberIterator& method_itr, | |
| 336 | const std::string& path, std::vector<std::string>& ref_keys, | ||
| 337 | std::unordered_map<std::string, ValidatorsStore*>& per_path_validator) | ||
| 338 | { | ||
| 339 | 2/2✓ Branch 2 taken 396 times. ✓ Branch 3 taken 132 times. | 528 | if (method_itr->value.HasMember("parameters")) { // if "method+path" has parameters | 
| 340 | 396 | ref_keys.emplace_back("parameters"); | |
| 341 | 396 | per_path_validator.at(path)->AddParamValidators(path, method_itr->value["parameters"], ref_keys); | |
| 342 | 396 | ref_keys.pop_back(); | |
| 343 | } | ||
| 344 | 528 | } | |
| 345 | |||
| 346 | 11484 | void OASValidatorImp::ResolveReferences(rapidjson::Value& value, rapidjson::Document& doc, | |
| 347 | rapidjson::Document::AllocatorType& allocator) | ||
| 348 | { | ||
| 349 | 2/2✓ Branch 1 taken 5346 times. ✓ Branch 2 taken 6138 times. | 11484 | if (value.IsObject()) { | 
| 350 | bool should_reiterate; | ||
| 351 | 1/2✗ Branch 0 not taken. ✓ Branch 1 taken 5346 times. | 5346 | do { | 
| 352 | 5346 | should_reiterate = false; | |
| 353 | 4/6✓ Branch 1 taken 5346 times. ✗ Branch 2 not taken. ✓ Branch 4 taken 15978 times. ✗ Branch 5 not taken. ✓ Branch 7 taken 10632 times. ✓ Branch 8 taken 5346 times. | 15978 | for (auto itr = value.MemberBegin(); itr != value.MemberEnd();) { | 
| 354 | 3/8✓ Branch 2 taken 10632 times. ✗ Branch 3 not taken. ✗ Branch 4 not taken. ✓ Branch 5 taken 10632 times. ✗ Branch 8 not taken. ✗ Branch 9 not taken. ✗ Branch 10 not taken. ✓ Branch 11 taken 10632 times. | 10632 | if (strcmp(itr->name.GetString(), "$ref") == 0 && itr->value.IsString()) { | 
| 355 | ✗ | std::string ref = itr->value.GetString(); | |
| 356 | ✗ | if (ref.rfind("#/", 0) == 0) { | |
| 357 | ✗ | ref.erase(0, 2); // Remove '#/' | |
| 358 | ✗ | rapidjson::Value const* ref_value = ResolvePath(doc, ref); | |
| 359 | ✗ | if (ref_value) { | |
| 360 | ✗ | value.CopyFrom(*ref_value, allocator); | |
| 361 | ✗ | should_reiterate = true; // Signal to reiterate due to structural changes | |
| 362 | ✗ | break; // Exit the loop to avoid invalid iterator use | |
| 363 | } | ||
| 364 | } | ||
| 365 | ✗ | } else { | |
| 366 | 1/2✓ Branch 2 taken 10632 times. ✗ Branch 3 not taken. | 10632 | ResolveReferences(itr->value, doc, allocator); | 
| 367 | } | ||
| 368 | 10632 | ++itr; | |
| 369 | } | ||
| 370 | } while (should_reiterate); // Keep resolving until no more references are found | ||
| 371 | 2/2✓ Branch 1 taken 516 times. ✓ Branch 2 taken 5622 times. | 6138 | } else if (value.IsArray()) { | 
| 372 | 2/2✓ Branch 1 taken 846 times. ✓ Branch 2 taken 516 times. | 1362 | for (rapidjson::SizeType i = 0; i < value.Size(); i++) { | 
| 373 | 846 | ResolveReferences(value[i], doc, allocator); | |
| 374 | } | ||
| 375 | } | ||
| 376 | 11484 | } | |
| 377 | |||
| 378 | 7 | std::unordered_map<std::string, std::unordered_set<std::string>> OASValidatorImp::BuildCaseInsensitiveMap( | |
| 379 | const std::unordered_map<std::string, std::unordered_set<std::string>>& method_map) | ||
| 380 | { | ||
| 381 | |||
| 382 | 7 | std::unordered_map<std::string, std::unordered_set<std::string>> case_insensitive_map; | |
| 383 | |||
| 384 | 1/2✗ Branch 5 not taken. ✓ Branch 6 taken 7 times. | 7 | for (const auto& entry : method_map) { | 
| 385 | ✗ | std::string lower_key(entry.first); | |
| 386 | ✗ | std::transform(lower_key.begin(), lower_key.end(), lower_key.begin(), ::tolower); | |
| 387 | ✗ | std::string upper_key(entry.first); | |
| 388 | ✗ | std::transform(upper_key.begin(), upper_key.end(), upper_key.begin(), ::toupper); | |
| 389 | ✗ | case_insensitive_map[lower_key] = entry.second; | |
| 390 | ✗ | case_insensitive_map[upper_key] = entry.second; | |
| 391 | ✗ | } | |
| 392 | |||
| 393 | 7 | return case_insensitive_map; | |
| 394 | ✗ | } | |
| 395 | |||
| 396 | const std::unordered_map<std::string, HttpMethod> OASValidatorImp::kStringToMethod = { | ||
| 397 | {"GET", HttpMethod::GET}, {"POST", HttpMethod::POST}, {"PUT", HttpMethod::PUT}, | ||
| 398 | {"DELETE", HttpMethod::DELETE}, {"HEAD", HttpMethod::HEAD}, {"OPTIONS", HttpMethod::OPTIONS}, | ||
| 399 | {"PATCH", HttpMethod::PATCH}, {"CONNECT", HttpMethod::CONNECT}, {"TRACE", HttpMethod::TRACE}, | ||
| 400 | {"get", HttpMethod::GET}, {"post", HttpMethod::POST}, {"put", HttpMethod::PUT}, | ||
| 401 | {"delete", HttpMethod::DELETE}, {"head", HttpMethod::HEAD}, {"options", HttpMethod::OPTIONS}, | ||
| 402 | {"patch", HttpMethod::PATCH}, {"connect", HttpMethod::CONNECT}, {"trace", HttpMethod::TRACE}}; | ||
| 403 |