diff --git a/include/valijson/internal/json_reference.hpp b/include/valijson/internal/json_reference.hpp index c182188..8e277fe 100644 --- a/include/valijson/internal/json_reference.hpp +++ b/include/valijson/internal/json_reference.hpp @@ -9,6 +9,106 @@ #include +namespace { + +/** + * @brief Recursively locate the value referenced by a JSON Pointer + * + * @param node current node in recursive evaluation of JSON Pointer + * @param jsonPointer string containing complete JSON Pointer + * @param jsonPointerItr string iterator pointing the part of the string + * currently being evaluated + * + * @return an instance AdapterType in the specified document + */ +template +inline AdapterType resolveJsonPointer( + const AdapterType &node, + const std::string &jsonPointer, + std::string::const_iterator jsonPointerItr) +{ + // TODO: This function will probably need to implement support for + // fetching documents referenced by JSON Pointers, similar to the + // populateSchema function. + + const std::string::const_iterator jsonPointerEnd = jsonPointer.end(); + + // Check for leading forward slash + if (std::find(jsonPointerItr, jsonPointerEnd, '/') == jsonPointerEnd) { + throw std::runtime_error( + "Expected '/' while parsing JSON Pointer."); + } + + // Proceed past leading slash + jsonPointerItr++; + + // Recursion bottoms out here + if (jsonPointerItr == jsonPointerEnd) { + return node; + } + + // Find iterator that points to next slash, or end of string + const std::string::const_iterator jsonPointerNext = + std::find(jsonPointerItr, jsonPointerEnd, '/'); + + // Extract the next 'directive' + const std::string directive(jsonPointerItr, jsonPointerNext); + if (directive.empty()) { + throw std::runtime_error( + "Expected at least one non-delimiting character in directive."); + } + + if (node.isArray()) { + try { + // Fragment must be non-negative integer + const uint64_t index = boost::lexical_cast(jsonPointer); + typedef typename AdapterType::Array Array; + typename Array::const_iterator itr = node.asArray().begin(); + + // TODO: Check for array bounds + itr.advance(index); + + if (jsonPointerNext == jsonPointerEnd) { + // Bottom out recursion since this is the last directive + return *itr; + } else { + // Recursively process the next directive in the JSON Pointer + return resolveJsonPointer(*itr, jsonPointer, jsonPointerNext); + } + + } catch (boost::bad_lexical_cast &) { + throw std::runtime_error("Expected directive to contain " + "non-negative integer to allow for array indexing; " + "actual value: " + directive); + } + + } else if (node.maybeObject()) { + // Fragment must identify a member of the candidate object + typedef typename AdapterType::Object Object; + typename Object::const_iterator itr = node.asObject().find(directive); + if (itr == node.asObject().end()) { + throw std::runtime_error("Expected directive to identify an " + "element in the current object; " + "actual value: " + directive); + } + + if (jsonPointerNext == jsonPointerEnd) { + // Bottom out recursion since this is the last directive + return itr->second; + } else { + // Recursively process the next directive in the JSON Pointer + return resolveJsonPointer(itr->second, jsonPointer, + jsonPointerNext); + } + } + + throw std::runtime_error("Expected end of JSON Pointer, but at least " + "one directive has not been processed; " + "actual directive: " + directive); +} + +} // end anonymous namespace + namespace valijson { namespace internal { namespace json_reference { @@ -37,71 +137,19 @@ inline std::string getJsonReferencePointer(const std::string &jsonRef) } /** - * @brief Return reference to part of document referenced by JSON Pointer + * @brief Return the JSON Value referenced by a JSON Pointer * - * @param node node to use as root for JSON Pointer resolution + * @param rootNode node to use as root for JSON Pointer resolution * @param jsonPointer string containing JSON Pointer * - * @return reference to an instance AdapterType in the specified document + * @return an instance AdapterType in the specified document */ template -inline AdapterType resolveJsonPointer(const AdapterType &node, - std::string jsonPointer) +inline AdapterType resolveJsonPointer( + const AdapterType &rootNode, + const std::string &jsonPointer) { - // TODO: This function will probably need to implement support for - // fetching documents referenced by JSON Pointers, similar to the - // populateSchema function. - - // Check for leading forward slash - if (jsonPointer.find("/") != 0) { - throw std::runtime_error( - "JSON Pointer must begin with reference to root node"); - } - - // Remove leading slash - jsonPointer = jsonPointer.substr(1); - - // Recursion bottoms out here - if (jsonPointer.empty()) { - return node; - } - - // Extract next directive - const std::string directive = jsonPointer.substr(1, jsonPointer.find("/")); - if (directive.empty()) { - throw std::runtime_error( - "JSON Pointer contains zero-length directive"); - } - - // Remove directive from remainder of JSON pointer - jsonPointer = jsonPointer.substr(directive.length()); - - if (node.isArray()) { - try { - // Fragment must be non-negative integer - const uint64_t index = boost::lexical_cast(jsonPointer); - typedef typename AdapterType::Array Array; - typename Array::const_iterator itr = node.asArray().begin(); - itr.advance(index); - // TODO: Check for array bounds - return resolveJsonPointer(*itr, jsonPointer); - - } catch (boost::bad_lexical_cast &) { - throw std::runtime_error("Invalid array index in JSON Reference: " + - directive); - } - - } else if (node.maybeObject()) { - typedef typename AdapterType::Object Object; - typename Object::const_iterator itr = node.asObject().find(directive); - if (itr == node.asObject().end()) { - throw std::runtime_error("Could not find element"); - } - return resolveJsonPointer(itr->second, jsonPointer); - - } - - throw std::runtime_error("Directive applied to simple value type"); + return ::resolveJsonPointer(rootNode, jsonPointer, jsonPointer.begin()); } } // namespace json_reference