12#include <glaze/glaze.hpp>
35using Json = glz::generic_u64;
48 [[nodiscard]] std::string
message()
const {
53 out +=
"at " + (
path.empty() ? std::string{
"/"} :
path) +
": " +
reason;
68[[nodiscard]]
inline std::string
pointerAppend(std::string_view base, std::string_view token) {
69 std::string out{base};
71 for (
char const chr : token) {
74 }
else if (chr ==
'/') {
87[[nodiscard]]
inline std::string
pointerAppend(std::string_view base, std::size_t index) {
88 return std::string{base} +
"/" + std::to_string(index);
103 return value.numerator == other.value.numerator &&
value.denominator == other.value.denominator &&
104 value.decimalPlaces == other.value.decimalPlaces;
148 constexpr std::array<std::string_view, 9> kNames = {
"null",
"bool",
"int",
"string",
"decimal",
149 "quantity",
"timestamp",
"list",
"record"};
150 return kNames.at(
static_cast<std::size_t
>(kind));
164 Value(std::nullptr_t null) noexcept {
static_cast<void>(null); }
168 Value(
bool flag) noexcept : _data{flag} {}
173 template <std::
signed_
integral I>
174 Value(I number) noexcept : _data{
static_cast<std::int64_t
>(number)} {}
199 : _data{std::make_shared<
ValueList const>(std::move(items))} {}
204 : _data{std::make_shared<
ValueRecord const>(std::move(fields))} {}
212 [[nodiscard]]
bool isNull() const noexcept {
return _data.index() == 0; }
216 [[nodiscard]]
bool const*
asBool() const noexcept {
return std::get_if<bool>(&_data); }
220 [[nodiscard]] std::int64_t
const*
asInt() const noexcept {
return std::get_if<std::int64_t>(&_data); }
224 [[nodiscard]] std::string
const*
asString() const noexcept {
return std::get_if<std::string>(&_data); }
228 [[nodiscard]]
Decimal const*
asDecimal() const noexcept {
return std::get_if<Decimal>(&_data); }
241 auto const* held = std::get_if<std::shared_ptr<ValueList const>>(&_data);
242 return held ==
nullptr ? nullptr : held->get();
248 auto const* held = std::get_if<std::shared_ptr<ValueRecord const>>(&_data);
249 return held ==
nullptr ? nullptr : held->get();
255 [[nodiscard]]
Value const*
field(std::string_view name)
const {
257 if (record ==
nullptr) {
260 auto const found = record->find(name);
261 return found == record->end() ? nullptr : &found->second;
269 if (_data.index() != other._data.index()) {
273 return *mine == *other.
asList();
278 return _data == other._data;
283 std::shared_ptr<ValueList const>, std::shared_ptr<ValueRecord const>>
289#if defined(__GNUC__) && !defined(__clang__)
290#pragma GCC diagnostic push
291#pragma GCC diagnostic ignored "-Wshadow"
306#if defined(__GNUC__) && !defined(__clang__)
307#pragma GCC diagnostic pop
347 [[nodiscard]] std::string
name()
const {
348 constexpr std::array<std::string_view, 10> kNames = {
"any",
"bool",
"int",
"string",
"decimal",
349 "quantity",
"timestamp",
"key",
"record",
"list"};
350 std::string out{kNames.at(
static_cast<std::size_t
>(
kind))};
352 out +=
"<" + (
element ?
element->name() : std::string{
"any"}) +
">";
384 constexpr std::array<ValueKind, 10> kHolds = {
387 return kHolds.at(
static_cast<std::size_t
>(
kind)) == held;
390 if (items ==
nullptr) {
394 return std::ranges::all_of(*items, [
this](
Value const& item) {
return !
element ||
element->admits(item); });
420 constexpr int kMaxDepth = 8;
421 if (depth > kMaxDepth) {
422 return std::unexpected(std::string{
"type nests list<> too deeply"});
424 static constexpr std::array<std::pair<std::string_view, TypeKind>, 9> kScalars = {{
436 if (
text.starts_with(
"list<")) {
437 text.remove_prefix(5);
442 if (!
text.starts_with(
'>')) {
443 return std::unexpected(std::string{
"list<...> is not closed with >"});
445 text.remove_prefix(1);
448 std::size_t length = 0;
449 while (length <
text.size() &&
text.at(length) >=
'a' &&
text.at(length) <=
'z') {
452 std::string_view
const word =
text.substr(0, length);
454 for (
auto const& [spelling, kind] : kScalars) {
455 if (spelling == word) {
462 return std::unexpected(
"unknown type `" + std::string{word} +
"`");
464 text.remove_prefix(length);
466 if (
text.starts_with(
'?')) {
467 text.remove_prefix(1);
479[[nodiscard]]
inline std::expected<Type, std::string>
parseType(std::string_view
text) {
480 std::string_view rest =
text;
486 return std::unexpected(
"unexpected `" + std::string{rest} +
"` after the type");
496[[nodiscard]]
inline std::optional<std::int64_t>
jsonInt(
Json const& json)
noexcept {
497 if (
auto const*
const signedValue = std::get_if<std::int64_t>(&json.data)) {
500 if (
auto const*
const unsignedValue = std::get_if<std::uint64_t>(&json.data)) {
501 if (*unsignedValue <=
static_cast<std::uint64_t
>(std::numeric_limits<std::int64_t>::max())) {
502 return static_cast<std::int64_t
>(*unsignedValue);
512[[nodiscard]]
inline std::optional<std::expected<Value, std::string>>
exactFromJson(Json::object_t
const&
object) {
513 if (
object.size() != 3 &&
object.size() != 4) {
516 Json const* num =
nullptr;
517 Json const* den =
nullptr;
518 Json const* places =
nullptr;
519 Json const* unit =
nullptr;
520 for (
auto const& [name, member] :
object) {
523 }
else if (name ==
"den") {
525 }
else if (name ==
"dp") {
527 }
else if (name ==
"unit") {
533 if (num ==
nullptr || den ==
nullptr || places ==
nullptr) {
536 auto const numerator =
jsonInt(*num);
537 auto const denominator =
jsonInt(*den);
538 auto const decimals =
jsonInt(*places);
539 if (!numerator || !denominator || !decimals) {
540 return std::unexpected(std::string{
"num, den and dp of an exact value are integers"});
543 return std::unexpected(std::string{
"dp of an exact value is between 0 and 18"});
548 return std::unexpected(std::string{
"den of an exact value is not zero"});
550 if (unit ==
nullptr) {
553 auto const*
const unitText = std::get_if<std::string>(&unit->data);
554 if (unitText ==
nullptr) {
555 return std::unexpected(std::string{
"unit of a quantity is a string"});
557 return Value{
Quantity{.value = *rational, .unit = *unitText}};
572 std::expected<Value, std::string> operator()(std::nullptr_t)
const {
return Value{}; }
573 std::expected<Value, std::string> operator()(
bool flag)
const {
return Value{flag}; }
574 std::expected<Value, std::string> operator()(std::uint64_t number)
const {
575 if (number >
static_cast<std::uint64_t
>(std::numeric_limits<std::int64_t>::max())) {
576 return std::unexpected(std::string{
"integer does not fit int64"});
578 return Value{
static_cast<std::int64_t
>(number)};
580 std::expected<Value, std::string> operator()(std::int64_t number)
const {
return Value{number}; }
581 std::expected<Value, std::string> operator()(
double number)
const {
582 static_cast<void>(number);
583 return std::unexpected(
584 std::string{R
"(a number with a fraction is not exact; write a decimal as {"num", "den", "dp"})"});
586 std::expected<Value, std::string> operator()(std::string const&
text)
const {
return Value{
text}; }
587 std::expected<Value, std::string> operator()(Json::array_t
const& array)
const {
589 items.reserve(array.size());
590 for (
Json const& element : array) {
595 items.push_back(std::move(*item));
597 return Value{std::move(items)};
599 std::expected<Value, std::string> operator()(Json::object_t
const&
object)
const {
601 return std::move(*exact);
604 for (
auto const& [name, member] :
object) {
609 fields.insert_or_assign(name, std::move(*field));
611 return Value{std::move(fields)};
614 return std::visit(Visitor{}, json.data);
627 if (
auto const*
const text = std::get_if<std::string>(&json.data)) {
630 return std::unexpected(
"`" + *
text +
"` is not an ISO-8601 UTC timestamp");
632 return Value{*instant};
636 if (
auto const*
const array = std::get_if<Json::array_t>(&json.data)) {
638 items.reserve(array->size());
639 for (
Json const& element : *array) {
644 items.push_back(std::move(*item));
646 return Value{std::move(items)};
654 if (std::int64_t
const*
const whole = value->asInt()) {
658 if (!type.
admits(*value)) {
659 return std::unexpected(
"expected " + type.
name() +
", got " + std::string{kindName(value->kind())});
671 Json::object_t object;
672 object.insert({
"num",
Json(rational.numerator)});
673 object.insert({
"den",
Json(rational.denominator)});
674 object.insert({
"dp",
Json(
static_cast<std::int64_t
>(rational.decimalPlaces.value))});
677 if (
bool const*
const flag = value.
asBool()) {
679 }
else if (std::int64_t
const*
const number = value.
asInt()) {
681 }
else if (std::string
const*
const text = value.
asString()) {
684 out.data = exact(decimal->value);
686 auto object = exact(quantity->value);
687 object.insert({
"unit",
Json(quantity->unit)});
688 out.data = std::move(
object);
690 out.data = instant->toIso8601();
693 for (
Value const& item : *items) {
696 out.data = std::move(array);
698 Json::object_t object;
699 for (
auto const& [name, field] : *fields) {
702 out.data = std::move(
object);
722 std::size_t depth = 0;
723 bool inString =
false;
724 bool escaped =
false;
725 for (
char const character :
text) {
729 }
else if (character ==
'\\') {
731 }
else if (character ==
'"') {
734 }
else if (character ==
'"') {
736 }
else if (character ==
'{' || character ==
'[') {
740 }
else if ((character ==
'}' || character ==
']') && depth > 0) {
752[[nodiscard]]
inline std::expected<Json, std::string>
parseJson(std::string_view
text) {
754 return std::unexpected(
"the JSON nests deeper than " + std::to_string(
kMaxJsonDepth) +
" levels");
757 if (
auto const error = glz::read_json(json,
text)) {
758 return std::unexpected(
"not JSON: " + glz::format_error(error,
text));
The document's typed value (spec 5 §3): what every expression evaluates to.
Value(Decimal number) noexcept
Constructs a decimal.
Value(time::DateTime instant) noexcept
Constructs a timestamp.
bool operator==(Value const &other) const
Deep equality: same kind and equal content; a decimal's places take part.
Quantity const * asQuantity() const noexcept
The quantity, if held.
Value() noexcept=default
Constructs null.
Decimal const * asDecimal() const noexcept
The decimal, if held.
Value(I number) noexcept
Constructs an int from any signed integer.
Value(bool flag) noexcept
Constructs a bool.
bool isNull() const noexcept
Whether the value is null.
Value(ValueRecord fields)
Constructs a record.
Value(Quantity quantity) noexcept
Constructs a quantity.
Value(char const *text)
Constructs a string from a C string, which would otherwise convert to bool.
Value const * field(std::string_view name) const
A field of a record.
Value(ValueList items)
Constructs a list.
ValueRecord const * asRecord() const noexcept
The record, if held.
time::DateTime const * asTimestamp() const noexcept
The timestamp, if held.
ValueKind kind() const noexcept
The kind of value held.
bool const * asBool() const noexcept
The bool, if held.
std::int64_t const * asInt() const noexcept
The int, if held.
Value(std::string text) noexcept
Constructs a string.
std::string const * asString() const noexcept
The string, if held.
ValueList const * asList() const noexcept
The list, if held.
UTC timestamps for morph actions.
Exact rational arithmetic for morph model/action values.
constexpr std::uint32_t kMaxDecimalPlaces
Largest decimal precision the type supports: 10^18 is the greatest power of ten that fits in std::int...
Strong type for a decimal-precision count.
Strong type for a Rational denominator.
Strong type for a Rational numerator.
Exact rational number with a runtime decimal-precision tag.
static constexpr std::expected< Rational, RationalError > from(Numerator wantedNumerator, Denominator wantedDenominator, DecimalPlaces wantedPrecision) noexcept
Validating factory.
A UTC instant with millisecond precision.
static std::optional< DateTime > fromIso8601(std::string_view text) noexcept
Strictly parses ISO-8601 UTC: YYYY-MM-DDTHH:MM:SS, optionally followed by .m/.mm/....
An exact decimal: a math::Rational whose DecimalPlaces are the value's declared places.
math::Rational value
The exact value, with its declared places.
bool operator==(Decimal const &other) const noexcept
Equality of magnitude and places.
An exact decimal with a unit: the id of a unit of the server's unit systems (x-unitAlternatives).
bool operator==(Quantity const &other) const noexcept
Equality of magnitude, places and unit.
math::Rational value
The exact magnitude, with its declared places.
std::string unit
The unit id, such as mg/L.
Why a document, an expression or a value was refused: where, and the reason.
bool operator==(Refusal const &other) const =default
Field-wise equality.
std::string document
The id of the document refused; empty when the refusal is not about a whole document.
std::string path
A JSON Pointer (RFC 6901) to the refused part, "" for the whole input.
std::string reason
The reason, in one sentence.
std::string message() const
The refusal as one line of text.
A document type (spec 5 §3): a kind, whether null is allowed, and a list's element type.
std::string name() const
The type as a document spells it.
bool optional
Whether the type is T?, admitting null.
static Type any()
The type the document cannot know.
static Type of(TypeKind kind, bool optional=false)
A scalar or record type.
std::shared_ptr< Type const > element
The element type of a list<T>; nullptr for any other kind.
bool operator==(Type const &other) const
Deep equality.
bool admits(Value const &value) const
Whether a value conforms to the type.
static Type listOf(Type element, bool optional=false)
A list type.
bool isAny() const noexcept
Whether the type is any.
std::expected< Json, std::string > parseJson(std::string_view text)
Parses JSON text into the document layer's JSON tree.
TypeKind
The kinds of a Type.
@ Record
record: a record whose fields the document does not declare.
@ Key
key: an int or a string.
@ Any
Not known to the document: a query result, a reply, an element's value.
glz::generic_u64 Json
The JSON tree the document layer reads and writes: glaze's generic value in its integer-preserving mo...
Json valueToJson(Value const &value)
Writes a value in its JSON form: the inverse of valueFromJson, with a timestamp as ISO-8601 text.
std::expected< Type, std::string > parseTypePrefix(std::string_view &text, int depth)
Parses one type at the front of text, advancing it.
std::expected< Type, std::string > parseType(std::string_view text)
Parses a document type: bool, int, string, decimal, quantity, timestamp, key, record,...
std::vector< Value > ValueList
A list value.
ValueKind
The kinds a Value holds.
@ Int
int: a 64-bit signed integer, exact.
@ String
string: UTF-8 text.
@ Quantity
quantity: a decimal with a unit.
@ Record
A record of named fields.
@ Decimal
decimal: an exact rational with declared places.
@ Timestamp
timestamp: a UTC instant with millisecond precision.
@ Null
No value: absent, not entered, or the result of an error.
std::expected< Value, std::string > valueFromJson(Json const &json)
Reads a value from its JSON form, structurally.
std::optional< std::expected< Value, std::string > > exactFromJson(Json::object_t const &object)
Reads the wire form of a decimal or quantity: an object of exactly num, den, dp (and unit).
bool nestsTooDeep(std::string_view text)
Whether JSON text nests objects and arrays deeper than kMaxJsonDepth.
constexpr std::size_t kMaxJsonDepth
The deepest a document's JSON may nest, counting each object and array as one level.
std::map< std::string, Value, std::less<> > ValueRecord
A record value: field name to value, ordered by name.
std::optional< std::int64_t > jsonInt(Json const &json) noexcept
Reads a JSON integer as an int64.
std::string pointerAppend(std::string_view base, std::string_view token)
Appends one reference token to a JSON Pointer, escaping ~ and / as RFC 6901 requires.
constexpr std::string_view kindName(ValueKind kind)
The name of a value kind, as a document spells its type.
Node text(Text spec)
Static or bound text.
std::variant< std::int64_t, std::string > Key
The identity of a Switch case, a Select option, a ForEach or Table row and a drag payload.