document_cache.h 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. #pragma once
  2. #include <map>
  3. #include <optional>
  4. #include <jvalidate/detail/expect.h>
  5. #include <jvalidate/forward.h>
  6. #include <jvalidate/uri.h>
  7. namespace jvalidate {
  8. /**
  9. * @brief An Adapter-specific owning cache of documents that we need to load
  10. * from an external resource. Because Adapter objects do not actually own the
  11. * JSON objects that they wrap, we need some method of holding them in cache
  12. * to prevent any use-after-free issues.
  13. *
  14. * As you can see from the constructor chain of {@see jvalidate::Schema},
  15. * the user can either provide their own DocumentCache, which can then be shared
  16. * between multiple root Schemas. Alternatively, they can provide a URIResolver,
  17. * which is a function that takes a URI as input, a JSON as an out-parameter,
  18. * and returns a boolean indicating success.
  19. * If the URIResolver is provided, then we automatically construct a temporary
  20. * DocumentCache around it for use in building the Schema. If neither the
  21. * URIResolver nor a DocumentCache are provided, then we will be unable to
  22. * resolve any external documents (even those on-disk).
  23. */
  24. template <Adapter A> class DocumentCache {
  25. public:
  26. using value_type = typename A::value_type;
  27. private:
  28. URIResolver<A> resolve_;
  29. std::map<URI, value_type> cache_;
  30. public:
  31. /**
  32. * @brief Constructs an empty (read: cannot resolve anything) cache for
  33. * external documents. Because there is no URIResolver, this object will
  34. * always return a nullopt when trying to load anything.
  35. */
  36. DocumentCache() = default;
  37. /**
  38. * @brief Construct a new DocumentCache from the given URIResolver function/
  39. * function-object.
  40. *
  41. * @param resolve A function that consumes a URI and returns a boolean status
  42. * code and concrete JSON object that can be stored in the Adapter type A as
  43. * an out-parameter.
  44. * This function is under no oblications to load any specific schemes from
  45. * input URIs, so it is necessary to think carefully about the domain of
  46. * schema references that you will be working on when implementing it.
  47. * @see tests/selfvalidate_test.cxx#load_external_for_test for an example
  48. * supporting http requests with libcurl and file requests with fstreams.
  49. */
  50. DocumentCache(URIResolver<A> const & resolve) : resolve_(resolve) {}
  51. operator bool() const { return resolve_; }
  52. std::optional<A> try_load(URI const & uri) {
  53. // Short circuit - without a URIResolver, we can always return nullopt,
  54. // because this library doesn't promise to know how to load external
  55. // schemas from any source (including files).
  56. if (not resolve_) {
  57. return std::nullopt;
  58. }
  59. auto [it, created] = cache_.try_emplace(uri);
  60. if (created && not resolve_(uri, it->second)) {
  61. // Doing it this way skips out on a move operation for the JSON object,
  62. // which could be useful if someone is using a legacy JSON object type.
  63. // Since std::map promises stability we don't need to concern ourselves
  64. // with reference invalidation even in a multi-threaded context - although
  65. // this code is not threadsafe.
  66. cache_.erase(it);
  67. return std::nullopt;
  68. }
  69. return A(it->second);
  70. }
  71. };
  72. }