adapter.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. #pragma once
  2. #include <cstdint>
  3. #include <filesystem>
  4. #include <fstream>
  5. #include <optional>
  6. #include <ostream>
  7. #include <jvalidate/detail/array_iterator.h>
  8. #include <jvalidate/detail/number.h>
  9. #include <jvalidate/detail/object_iterator.h>
  10. #include <jvalidate/enum.h>
  11. #include <jvalidate/forward.h>
  12. #include <jvalidate/status.h>
  13. namespace jvalidate::adapter {
  14. /**
  15. * @brief An interface for a type-erased reference-wrapper around a JSON node.
  16. *
  17. * Unlike languages like python, there are dozens of different C++ Libraries
  18. * for JSON parsing/construction. Each of these libraries has its own set of
  19. * getter functions, rules for handling missing values, and degree to which it
  20. * can engage in fuzziness of types.
  21. *
  22. * Adapter has two main groups of methods:
  23. * - as_*() and *_size() virtual functions
  24. * - maybe_*() concrete functions
  25. *
  26. * Most interaction with Adapter will be done via the maybe_*() functions,
  27. * with or without strictness enabled depending on what constraint is being
  28. * checked.
  29. */
  30. class Adapter {
  31. public:
  32. virtual ~Adapter() = default;
  33. /**
  34. * @brief Get the jvalidate::adapter::Type that this adapter represents.
  35. * This represents the types recognized by json-schema:
  36. * null, bool, integer, number, string, array, object
  37. * This function is meant to be used internally - and not by any of the
  38. * Constraint objects.
  39. */
  40. virtual Type type() const = 0;
  41. /**
  42. * @brief Obtain an immutable copy of the current node.
  43. * Because an Adapter stores a reference to the underlying JSON, it cannot
  44. * be stored by e.g. a Const/Enum Constraint without risking a Segfault.
  45. */
  46. virtual std::unique_ptr<Const const> freeze() const = 0;
  47. /**
  48. * @brief Extract a boolean value from this JSON node.
  49. * @pre type() == Type::Boolean
  50. *
  51. * @throws If the pre-condition is not valid, then this function may throw
  52. * or produce other undefined behavior, depending on the implementation
  53. * details of the underlying type.
  54. */
  55. virtual bool as_boolean() const = 0;
  56. /**
  57. * @brief Extract an integer value from this JSON node.
  58. * @pre type() == Type::Integer
  59. *
  60. * @throws If the pre-condition is not valid, then this function may throw
  61. * or produce other undefined behavior, depending on the implementation
  62. * details of the underlying type.
  63. */
  64. virtual int64_t as_integer() const = 0;
  65. /**
  66. * @brief Extract a decimal value from this JSON node.
  67. * @pre type() == Type::Number
  68. *
  69. * @throws If the pre-condition is not valid, then this function may throw
  70. * or produce other undefined behavior, depending on the implementation
  71. * details of the underlying type.
  72. */
  73. virtual double as_number() const = 0;
  74. /**
  75. * @brief Extract a string value from this JSON node.
  76. * @pre type() == Type::String
  77. *
  78. * @throws If the pre-condition is not valid, then this function may throw
  79. * or produce other undefined behavior, depending on the implementation
  80. * details of the underlying type.
  81. */
  82. virtual std::string as_string() const = 0;
  83. /**
  84. * @brief Get the size of the JSON array in this node.
  85. * @pre type() == Type::Array
  86. *
  87. * @throws If the pre-condition is not valid, then this function may throw
  88. * or produce other undefined behavior, depending on the implementation
  89. * details of the underlying type.
  90. */
  91. virtual size_t array_size() const = 0;
  92. /**
  93. * @brief Get the size of the JSON object in this node.
  94. * @pre type() == Type::Object
  95. *
  96. * @throws If the pre-condition is not valid, then this function may throw
  97. * or produce other undefined behavior, depending on the implementation
  98. * details of the underlying type.
  99. */
  100. virtual size_t object_size() const = 0;
  101. /**
  102. * @brief Loop through every element of the JSON array in this node, applying
  103. * the given callback function to them.
  104. *
  105. * @param cb A callback of the form Adapter => Status
  106. *
  107. * @return Status::Accept iff there are no errors
  108. */
  109. virtual Status apply_array(AdapterCallback const & cb) const = 0;
  110. /**
  111. * @brief Loop through every element of the JSON object in this node, applying
  112. * the given callback function to them.
  113. *
  114. * @param cb A callback of the form (string, Adapter) => Status
  115. *
  116. * @return Status::Accept iff there are no errors
  117. */
  118. virtual Status apply_object(ObjectAdapterCallback const & cb) const = 0;
  119. virtual bool equals(Adapter const & lhs, bool strict) const = 0;
  120. /**
  121. * @brief Test if this object is null-like
  122. *
  123. * @param strict Does this function allow for fuzzy comparisons with strings?
  124. */
  125. bool maybe_null(bool strict) const {
  126. switch (type()) {
  127. case Type::Null:
  128. return true;
  129. case Type::String:
  130. return not strict and as_string().empty();
  131. default:
  132. return false;
  133. }
  134. }
  135. /**
  136. * @brief Attempts to extract a boolean value from this JSON node
  137. *
  138. * @param strict Does this function allow for fuzzy comparisons with strings?
  139. *
  140. * @return The boolean value contained if it is possible to deduce
  141. * (or type() == Type::Boolean), else nullopt.
  142. */
  143. std::optional<bool> maybe_boolean(bool strict) const {
  144. switch (type()) {
  145. case Type::Boolean:
  146. return as_boolean();
  147. case Type::String:
  148. if (not strict) {
  149. auto str = as_string();
  150. if (str == "true") {
  151. return true;
  152. }
  153. if (str == "false") {
  154. return false;
  155. }
  156. }
  157. return std::nullopt;
  158. default:
  159. return std::nullopt;
  160. }
  161. }
  162. /**
  163. * @brief Attempts to extract a integer value from this JSON node
  164. *
  165. * @param strict Does this function allow for fuzzy comparisons with strings
  166. * and/or booleans?
  167. *
  168. * @return The integer value contained if it is possible to deduce from an
  169. * integer, number, boolean, or string. Else nullopt
  170. */
  171. std::optional<int64_t> maybe_integer(bool strict) const {
  172. switch (type()) {
  173. case Type::Number:
  174. if (double value = as_number(); jvalidate::detail::fits_in_integer(value)) {
  175. return static_cast<int64_t>(value);
  176. }
  177. return std::nullopt;
  178. case Type::Integer:
  179. return as_integer();
  180. case Type::Boolean:
  181. if (not strict) {
  182. return as_boolean() ? 1 : 0;
  183. }
  184. return std::nullopt;
  185. case Type::String:
  186. if (not strict) {
  187. auto str = as_string();
  188. size_t end = 0;
  189. int64_t rval = std::stoll(str, &end);
  190. if (end == str.size()) {
  191. return rval;
  192. }
  193. }
  194. return std::nullopt;
  195. default:
  196. return std::nullopt;
  197. }
  198. }
  199. /**
  200. * @brief Attempts to extract a number value from this JSON node
  201. *
  202. * @param strict Does this function allow for fuzzy comparisons with strings?
  203. *
  204. * @return The number value contained if it is possible to deduce
  205. * (or type() == Type::Integer || type() == Type::Number), else nullopt.
  206. */
  207. std::optional<double> maybe_number(bool strict) const {
  208. switch (type()) {
  209. case Type::Number:
  210. return as_number();
  211. case Type::Integer:
  212. return as_integer();
  213. case Type::String:
  214. if (not strict) {
  215. auto str = as_string();
  216. size_t end = 0;
  217. double rval = std::stod(str, &end);
  218. if (end == str.size()) {
  219. return rval;
  220. }
  221. }
  222. return std::nullopt;
  223. default:
  224. return std::nullopt;
  225. }
  226. }
  227. /**
  228. * @brief Attempts to extract a string value from this JSON node
  229. *
  230. * @param strict Does this function allow for fuzzy comparisons with other
  231. * types?
  232. *
  233. * @return The string value contained if it is possible to deduce from a
  234. * scalar type, else nullopt.
  235. */
  236. std::optional<std::string> maybe_string(bool strict) const {
  237. switch (type()) {
  238. case Type::Null:
  239. return strict ? std::nullopt : std::make_optional("");
  240. case Type::String:
  241. return as_string();
  242. case Type::Number:
  243. if (not strict) {
  244. return std::to_string(as_number());
  245. }
  246. return std::nullopt;
  247. case Type::Integer:
  248. if (not strict) {
  249. return std::to_string(as_integer());
  250. }
  251. return std::nullopt;
  252. case Type::Boolean:
  253. if (not strict) {
  254. return as_boolean() ? "true" : "false";
  255. }
  256. return std::nullopt;
  257. default:
  258. return std::nullopt;
  259. }
  260. }
  261. /**
  262. * @brief Attempts to extract the array length from this JSON node
  263. *
  264. * @param strict Does this function allow for fuzzy comparisons with other
  265. * types?
  266. *
  267. * @return array_size() if this is an array, else 0 or nullopt, depending
  268. * on some factors.
  269. */
  270. std::optional<size_t> maybe_array_size(bool strict) const {
  271. switch (type()) {
  272. case Type::Array:
  273. return array_size();
  274. case Type::Null:
  275. return strict ? std::nullopt : std::make_optional(0UL);
  276. case Type::Object:
  277. return (strict || object_size() != 0) ? std::nullopt : std::make_optional(0UL);
  278. default:
  279. return std::nullopt;
  280. }
  281. }
  282. /**
  283. * @brief Attempts to extract the object length from this JSON node
  284. *
  285. * @param strict Does this function allow for fuzzy comparisons with other
  286. * types?
  287. *
  288. * @return object_size() if this is an object, else 0 or nullopt, depending
  289. * on some factors.
  290. */
  291. std::optional<size_t> maybe_object_size(bool strict) const {
  292. switch (type()) {
  293. case Type::Object:
  294. return object_size();
  295. case Type::Null:
  296. return strict ? std::nullopt : std::make_optional(0UL);
  297. case Type::Array:
  298. return (strict || array_size() != 0) ? std::nullopt : std::make_optional(0UL);
  299. default:
  300. return std::nullopt;
  301. }
  302. }
  303. virtual void write(std::ostream & os) const {
  304. std::string_view div;
  305. switch (type()) {
  306. case Type::Null:
  307. os << "null";
  308. break;
  309. case Type::Boolean:
  310. os << (as_boolean() ? "true" : "false");
  311. break;
  312. case Type::Integer:
  313. os << as_integer();
  314. break;
  315. case Type::Number:
  316. os << as_number();
  317. break;
  318. case Type::String:
  319. os << '"' << as_string() << '"';
  320. break;
  321. case Type::Array:
  322. os << '[';
  323. apply_array([&os, &div](Adapter const & elem) {
  324. os << std::exchange(div, ", ") << elem;
  325. return Status::Accept;
  326. });
  327. os << ']';
  328. break;
  329. case Type::Object:
  330. os << '{';
  331. apply_object([&os, &div](std::string const & key, Adapter const & elem) {
  332. os << std::exchange(div, ", ") << '"' << key << '"' << ':' << elem;
  333. return Status::Accept;
  334. });
  335. os << '}';
  336. break;
  337. }
  338. }
  339. friend std::ostream & operator<<(std::ostream & os, Adapter const & self) {
  340. self.write(os);
  341. return os;
  342. }
  343. };
  344. /**
  345. * @brief An interface for an immutable, owning handle to a type-erased JSON
  346. * node. {@see Adapter::freeze} for more explaination why this is necessary.
  347. */
  348. class Const {
  349. public:
  350. virtual ~Const() = default;
  351. /**
  352. * @brief Perform an action on this object, such as copying or testing
  353. * equality.
  354. *
  355. * @param cb A callback function of the form Adapter => Status
  356. *
  357. * @return the result of cb on the contained JSON
  358. */
  359. virtual Status apply(AdapterCallback const & cb) const = 0;
  360. friend std::ostream & operator<<(std::ostream & os, Const const & self) {
  361. self.apply([&os](Adapter const & adapter) {
  362. adapter.write(os);
  363. return Status::Accept;
  364. });
  365. return os;
  366. }
  367. };
  368. template <typename JSON>
  369. bool load_file(std::filesystem::path const & path, JSON & out, std::string & error) noexcept {
  370. std::ifstream in(path);
  371. if (in.bad()) {
  372. error = "file error";
  373. return false;
  374. }
  375. return load_stream(in, out, error);
  376. }
  377. }
  378. namespace jvalidate::adapter::detail {
  379. /**
  380. * @brief The simplest implementation of Const.
  381. * Depends on the AdapterTraits struct.
  382. *
  383. * @tparam JSON The type being adapted
  384. */
  385. template <typename JSON> class GenericConst final : public Const {
  386. public:
  387. explicit GenericConst(JSON const & value) : value_(value) {}
  388. Status apply(AdapterCallback const & cb) const {
  389. return cb(typename AdapterTraits<JSON>::ConstAdapter(value_));
  390. }
  391. JSON const & value() const { return value_; }
  392. private:
  393. JSON value_;
  394. };
  395. }
  396. namespace std {
  397. inline ostream & operator<<(ostream & os, unique_ptr<jvalidate::adapter::Const const> const & ptr) {
  398. return os << *ptr;
  399. }
  400. inline ostream & operator<<(ostream & os,
  401. vector<unique_ptr<jvalidate::adapter::Const const>> const & items) {
  402. std::string_view div;
  403. os << '[';
  404. for (auto const & ptr : items) {
  405. os << std::exchange(div, ", ") << *ptr;
  406. }
  407. return os << ']';
  408. }
  409. }