#pragma once #include #include #include #include #include namespace jvalidate { /** * @brief An Adapter-specific owning cache of documents that we need to load * from an external resource. Because Adapter objects do not actually own the * JSON objects that they wrap, we need some method of holding them in cache * to prevent any use-after-free issues. * * As you can see from the constructor chain of {@see jvalidate::Schema}, * the user can either provide their own DocumentCache, which can then be shared * between multiple root Schemas. Alternatively, they can provide a URIResolver, * which is a function that takes a URI as input, a JSON as an out-parameter, * and returns a boolean indicating success. * If the URIResolver is provided, then we automatically construct a temporary * DocumentCache around it for use in building the Schema. If neither the * URIResolver nor a DocumentCache are provided, then we will be unable to * resolve any external documents (even those on-disk). */ template class DocumentCache { public: using value_type = typename A::value_type; private: URIResolver resolve_; std::map cache_; public: /** * @brief Constructs an empty (read: cannot resolve anything) cache for * external documents. Because there is no URIResolver, this object will * always return a nullopt when trying to load anything. */ DocumentCache() = default; /** * @brief Construct a new DocumentCache from the given URIResolver function/ * function-object. * * @param resolve A function that consumes a URI and returns a boolean status * code and concrete JSON object that can be stored in the Adapter type A as * an out-parameter. * This function is under no oblications to load any specific schemes from * input URIs, so it is necessary to think carefully about the domain of * schema references that you will be working on when implementing it. * @see tests/selfvalidate_test.cxx#load_external_for_test for an example * supporting http requests with libcurl and file requests with fstreams. */ DocumentCache(URIResolver const & resolve) : resolve_(resolve) {} operator bool() const { return resolve_; } std::optional try_load(URI const & uri) { // Short circuit - without a URIResolver, we can always return nullopt, // because this library doesn't promise to know how to load external // schemas from any source (including files). if (not resolve_) { return std::nullopt; } auto [it, created] = cache_.try_emplace(uri); if (created && not resolve_(uri, it->second)) { // Doing it this way skips out on a move operation for the JSON object, // which could be useful if someone is using a legacy JSON object type. // Since std::map promises stability we don't need to concern ourselves // with reference invalidation even in a multi-threaded context - although // this code is not threadsafe. cache_.erase(it); return std::nullopt; } return A(it->second); } }; }