Directory: | cvmfs/ |
---|---|
File: | cvmfs/json_document_write.h |
Date: | 2025-06-22 02:36:02 |
Exec | Total | Coverage | |
---|---|---|---|
Lines: | 70 | 92 | 76.1% |
Branches: | 43 | 101 | 42.6% |
Line | Branch | Exec | Source |
---|---|---|---|
1 | /** | ||
2 | * This file is part of the CernVM File System. | ||
3 | */ | ||
4 | |||
5 | #ifndef CVMFS_JSON_DOCUMENT_WRITE_H_ | ||
6 | #define CVMFS_JSON_DOCUMENT_WRITE_H_ | ||
7 | |||
8 | #include <cstdio> | ||
9 | #include <string> | ||
10 | #include <vector> | ||
11 | |||
12 | #include "util/exception.h" | ||
13 | #include "util/string.h" | ||
14 | |||
15 | #ifdef CVMFS_NAMESPACE_GUARD | ||
16 | namespace CVMFS_NAMESPACE_GUARD { | ||
17 | #endif | ||
18 | |||
19 | /** | ||
20 | * This class is used for marshalling JSON objects to strings. | ||
21 | * | ||
22 | * When creating simple objects is sufficient to call the `Add()` methods to add | ||
23 | * new key - values to the final JSON. | ||
24 | * | ||
25 | * When creating complex objects, (an object that contains another object) is | ||
26 | * necessary to create first the nested object and then add it to the final | ||
27 | * object with the `AddJsonObject`. This will take care of all the escaping. | ||
28 | */ | ||
29 | class JsonStringGenerator { | ||
30 | enum JsonVariant { | ||
31 | kString, | ||
32 | kInteger, | ||
33 | kFloat, | ||
34 | kJsonObject | ||
35 | }; | ||
36 | |||
37 | struct JsonEntry { | ||
38 | JsonVariant variant; | ||
39 | std::string key_escaped; | ||
40 | std::string str_val_escaped; | ||
41 | int64_t int_val; | ||
42 | float float_val; | ||
43 | |||
44 | 82 | JsonEntry(const std::string &key_escaped, const std::string &val) | |
45 | 82 | : variant(kString) | |
46 | 82 | , key_escaped(key_escaped) | |
47 |
1/2✓ Branch 1 taken 82 times.
✗ Branch 2 not taken.
|
82 | , str_val_escaped(val) |
48 | 82 | , int_val(0) | |
49 | 82 | , float_val(0.0) { } | |
50 | |||
51 | 84 | JsonEntry(const std::string &key_escaped, const std::string &val, | |
52 | const JsonVariant variant) | ||
53 | 84 | : variant(variant) | |
54 | 84 | , key_escaped(key_escaped) | |
55 |
1/2✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
|
84 | , str_val_escaped(val) |
56 | 84 | , int_val(0) | |
57 | 84 | , float_val(0.0) { } | |
58 | |||
59 | 14 | JsonEntry(const std::string &key_escaped, const int val) | |
60 | 14 | : variant(kInteger) | |
61 | 14 | , key_escaped(key_escaped) | |
62 | 14 | , str_val_escaped() | |
63 | 14 | , int_val(val) | |
64 | 14 | , float_val(0.0) { } | |
65 | |||
66 | JsonEntry(const std::string &key_escaped, const float val) | ||
67 | : variant(kFloat) | ||
68 | , key_escaped(key_escaped) | ||
69 | , str_val_escaped() | ||
70 | , int_val(0) | ||
71 | , float_val(val) { } | ||
72 | |||
73 | 123 | JsonEntry(const std::string &key_escaped, const int64_t val) | |
74 | 123 | : variant(kInteger) | |
75 | 123 | , key_escaped(key_escaped) | |
76 | 123 | , str_val_escaped() | |
77 | 123 | , int_val(val) | |
78 | 123 | , float_val(0.0) { } | |
79 | |||
80 | 303 | std::string Format() const { | |
81 |
3/5✓ Branch 0 taken 82 times.
✓ Branch 1 taken 137 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 84 times.
✗ Branch 4 not taken.
|
303 | switch (variant) { |
82 | 82 | case kString: | |
83 |
3/6✓ Branch 2 taken 82 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 82 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 82 times.
✗ Branch 9 not taken.
|
164 | return "\"" + key_escaped + "\":\"" + str_val_escaped + "\""; |
84 | 137 | case kInteger: | |
85 |
3/6✓ Branch 2 taken 137 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 137 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 137 times.
✗ Branch 9 not taken.
|
274 | return "\"" + key_escaped + "\":" + StringifyInt(int_val); |
86 | ✗ | case kFloat: | |
87 | ✗ | return "\"" + key_escaped + "\":" + StringifyDouble(float_val); | |
88 | 84 | case kJsonObject: | |
89 |
2/4✓ Branch 2 taken 84 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 84 times.
✗ Branch 6 not taken.
|
168 | return "\"" + key_escaped + "\":" + str_val_escaped; |
90 | ✗ | default: | |
91 | ✗ | PANIC(kLogStdout | kLogStderr, "JSON creation failed"); | |
92 | } | ||
93 | } | ||
94 | }; | ||
95 | |||
96 | public: | ||
97 | 82 | void Add(const std::string &key, const std::string &val) { | |
98 |
3/6✓ Branch 1 taken 82 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 82 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 82 times.
✗ Branch 8 not taken.
|
164 | const JsonEntry entry(Escape(key), Escape(val)); |
99 |
1/2✓ Branch 1 taken 82 times.
✗ Branch 2 not taken.
|
82 | entries.push_back(entry); |
100 | 82 | } | |
101 | |||
102 | 14 | void Add(const std::string &key, const int val) { | |
103 |
2/4✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 14 times.
✗ Branch 5 not taken.
|
14 | const JsonEntry entry(Escape(key), val); |
104 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
14 | entries.push_back(entry); |
105 | 14 | } | |
106 | |||
107 | void Add(const std::string &key, const float val) { | ||
108 | const JsonEntry entry(Escape(key), val); | ||
109 | entries.push_back(entry); | ||
110 | } | ||
111 | |||
112 | 123 | void Add(const std::string &key, const int64_t val) { | |
113 |
2/4✓ Branch 1 taken 123 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 123 times.
✗ Branch 5 not taken.
|
123 | const JsonEntry entry(Escape(key), val); |
114 |
1/2✓ Branch 1 taken 123 times.
✗ Branch 2 not taken.
|
123 | entries.push_back(entry); |
115 | 123 | } | |
116 | |||
117 | 84 | void AddJsonObject(const std::string &key, const std::string &json) { | |
118 | // we **do not escape** the value here | ||
119 |
2/4✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 84 times.
✗ Branch 5 not taken.
|
84 | const JsonEntry entry(Escape(key), json, kJsonObject); |
120 |
1/2✓ Branch 1 taken 84 times.
✗ Branch 2 not taken.
|
84 | entries.push_back(entry); |
121 | 84 | } | |
122 | |||
123 | 192 | std::string GenerateString() const { | |
124 | 192 | std::string output; | |
125 | |||
126 |
1/2✓ Branch 1 taken 192 times.
✗ Branch 2 not taken.
|
192 | output += "{"; |
127 |
2/2✓ Branch 1 taken 303 times.
✓ Branch 2 taken 192 times.
|
495 | for (size_t i = 0u; i < this->entries.size(); ++i) { |
128 |
2/4✓ Branch 2 taken 303 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 303 times.
✗ Branch 6 not taken.
|
303 | output += this->entries[i].Format(); |
129 |
2/2✓ Branch 1 taken 154 times.
✓ Branch 2 taken 149 times.
|
303 | if (i < this->entries.size() - 1) { |
130 |
1/2✓ Branch 1 taken 154 times.
✗ Branch 2 not taken.
|
154 | output += ','; |
131 | } | ||
132 | } | ||
133 |
2/4✓ Branch 2 taken 192 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 192 times.
✗ Branch 6 not taken.
|
192 | output += std::string("}"); |
134 | 192 | return output; | |
135 | } | ||
136 | |||
137 | 82 | void Clear() { entries.clear(); } | |
138 | |||
139 | private: | ||
140 | // this escape procedure is not as complete as it should be. | ||
141 | // we should manage ALL control chars from '\x00' to '\x1f' | ||
142 | // however this are the one that we can expect to happen | ||
143 | // More info: https://stackoverflow.com/a/33799784/869271 | ||
144 | 385 | const std::string Escape(const std::string &input) const { | |
145 | 385 | std::string result; | |
146 |
1/2✓ Branch 2 taken 385 times.
✗ Branch 3 not taken.
|
385 | result.reserve(input.size()); |
147 |
2/2✓ Branch 1 taken 3430 times.
✓ Branch 2 taken 385 times.
|
3815 | for (size_t i = 0; i < input.size(); i++) { |
148 |
2/8✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 14 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 3416 times.
|
3430 | switch (input[i]) { |
149 | ✗ | case '"': | |
150 | ✗ | result.append("\\\""); | |
151 | ✗ | break; | |
152 | ✗ | case '\\': | |
153 | ✗ | result.append("\\\\"); | |
154 | ✗ | break; | |
155 | ✗ | case '\b': | |
156 | ✗ | result.append("\\b"); | |
157 | ✗ | break; | |
158 | ✗ | case '\f': | |
159 | ✗ | result.append("\\f"); | |
160 | ✗ | break; | |
161 | 14 | case '\n': | |
162 |
1/2✓ Branch 1 taken 14 times.
✗ Branch 2 not taken.
|
14 | result.append("\\n"); |
163 | 14 | break; | |
164 | ✗ | case '\r': | |
165 | ✗ | result.append("\\r"); | |
166 | ✗ | break; | |
167 | ✗ | case '\t': | |
168 | ✗ | result.append("\\t"); | |
169 | ✗ | break; | |
170 | 3416 | default: | |
171 |
1/2✓ Branch 2 taken 3416 times.
✗ Branch 3 not taken.
|
3416 | result.push_back(input[i]); |
172 | 3416 | break; | |
173 | } | ||
174 | } | ||
175 | 385 | return result; | |
176 | } | ||
177 | |||
178 | std::vector<JsonEntry> entries; | ||
179 | }; | ||
180 | |||
181 | #ifdef CVMFS_NAMESPACE_GUARD | ||
182 | } // namespace CVMFS_NAMESPACE_GUARD | ||
183 | #endif | ||
184 | |||
185 | #endif // CVMFS_JSON_DOCUMENT_WRITE_H_ | ||
186 |