1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
|
#pragma once
/// @file Lix-specific JSON handling. We do not use plain `nlohmann::json`
/// because we want to override serializer behavior without imposing these
/// overrides on out-of-tree users of libutil, as we'd be required to when
/// specializing templates in the nlohmann namespace. nlohmann::json can't
/// deal with `std::optional<T>` types until 3.11.3 at, and we need those.
/// We also customize enum serialization to not automatically cast to int;
/// nlohmann can be told to disable this only via a special global define.
#include "lix/libutil/error.hh"
#include "lix/libutil/fmt.hh"
#include "lix/libutil/json-fwd.hh" // IWYU pragma: export
#include <concepts>
#include <list>
#include <string_view>
#include <type_traits>
#define JSON_THROW_USER(exception) \
[](auto _lix_e) { throw ::nix::json::JSONError(_lix_e.id, _lix_e.what()); }(exception)
#define JSON_TRY_USER try
#define JSON_CATCH_USER(exception) catch (exception)
namespace nix::json {
class JSONError : public Error
{
public:
const int id; // nlohmann error id
JSONError(int id, const char * what) : Error("JSON processing error: %s", what), id(id) {}
};
}
// NOLINTNEXTLINE(lix-forbidden-includes): we *are* the wrapper
#include <nlohmann/json.hpp> // IWYU pragma: export
namespace nix {
/**
* Ensure the type of a json object is what you expect, failing
* with a Nix Error if it isn't.
*
* Use before type conversions and element access to avoid ugly exceptions.
*/
const JSON & ensureType(
const JSON & value,
JSON::value_type expectedType);
namespace json {
/**
* Handle numbers and enums in default impl
*/
template<typename T>
struct avoids_null
: std::bool_constant<std::is_integral_v<T> || std::is_floating_point_v<T> || IntegralEnum<T>>
{};
template<>
struct avoids_null<std::nullptr_t> : std::false_type {};
template<>
struct avoids_null<bool> : std::true_type {};
template<>
struct avoids_null<std::string> : std::true_type {};
template<typename T>
struct avoids_null<std::vector<T>> : std::true_type {};
template<typename T>
struct avoids_null<std::list<T>> : std::true_type {};
template<typename K, typename V>
struct avoids_null<std::map<K, V>> : std::true_type {};
namespace detail {
template<typename Json, typename T>
requires requires(Json & j, T value) { to_json(j, value); }
void call_to_json(Json & j, const T & value)
{
to_json(j, value);
}
template<typename Json, typename T>
requires requires(Json && j, T & value) { from_json(std::forward<Json>(j), value); }
void call_from_json(Json && j, T & value)
{
from_json(std::forward<Json>(j), value);
}
}
template<typename T>
struct adl_serializer<T, void>
{
template<typename Json>
requires requires(Json & j, T value) { detail::call_to_json(j, value); }
static void to_json(Json & j, const T & value)
{
detail::call_to_json(j, value);
}
template<typename Json>
requires requires(Json && j, T & value) {
detail::call_from_json(std::forward<Json>(j), value);
}
static void from_json(Json && j, T & value)
{
detail::call_from_json(std::forward<Json>(j), value);
}
// Following https://github.com/nlohmann/json#how-can-i-use-get-for-non-default-constructiblenon-copyable-types
template<typename Json>
requires requires(Json & j, T value) { T::to_json(j, value); }
static void to_json(Json & j, const T & value)
{
T::to_json(j, value);
}
template<typename Json>
requires requires(Json && j) {
{
T::from_json(std::forward<Json>(j))
} -> std::same_as<T>;
}
static auto from_json(Json && j)
{
return T::from_json(std::forward<Json>(j));
}
template<typename Json>
requires IntegralEnum<T>
static void to_json(Json && json, const T & value)
{
json = static_cast<std::underlying_type_t<T>>(value);
}
template<typename Json>
requires IntegralEnum<T>
static void from_json(const Json & json, T & value)
{
value = static_cast<T>(json.template get<std::underlying_type_t<T>>());
}
};
template<typename T>
struct adl_serializer<std::optional<T>>
{
/**
* @brief Convert a JSON type to an `optional<T>` treating
* `null` as `std::nullopt`.
*/
static void from_json(const auto & json, std::optional<T> & t)
{
static_assert(avoids_null<T>::value, "null is already in use for underlying type's JSON");
t = json.is_null() ? std::nullopt : std::make_optional(json.template get<T>());
}
/**
* @brief Convert an optional type to a JSON type treating `std::nullopt`
* as `null`.
*/
static void to_json(auto & json, const std::optional<T> & t)
{
static_assert(avoids_null<T>::value, "null is already in use for underlying type's JSON");
if (t) {
json = *t;
} else {
json = nullptr;
}
}
};
/**
* Parse some JSON and throw an `Error` on failure. This make nlohmann errors
* more inspectable and lets us add meaningful backtraces to any json errors.
*/
template<typename Source>
JSON parse(Source && source, std::optional<std::string_view> context = {})
{
try {
// NOLINTNEXTLINE(lix-disallowed-decls): this is the wrapper for that
return JSON::parse(std::forward<Source>(source));
} catch (JSONError & error) {
if (context) {
error.addTrace(nullptr, fmt("while parsing %s", *context));
}
throw;
}
}
}
}
|