xref: /aosp_15_r20/external/pigweed/pw_json/public/pw_json/internal/builder_impl.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include "pw_json/builder.h"
17 #include "pw_string/type_to_string.h"
18 
19 namespace pw {
20 namespace json_impl {
21 
22 constexpr char kArray[2] = {'[', ']'};
23 constexpr char kObject[2] = {'{', '}'};
24 
WriteString(std::string_view value,char * buffer,size_t remaining)25 constexpr StatusWithSize WriteString(std::string_view value,
26                                      char* buffer,
27                                      size_t remaining) {
28   if (value.size() + 1 /* null */ > remaining) {
29     return StatusWithSize::ResourceExhausted();
30   }
31   for (char c : value) {
32     *buffer++ = c;
33   }
34   *buffer = '\0';
35   return StatusWithSize(value.size());
36 }
37 
NibbleToHex(uint8_t nibble)38 constexpr char NibbleToHex(uint8_t nibble) {
39   return nibble + (nibble < 10 ? '0' : ('a' - 10));
40 }
41 
42 // In accordance with RFC 8259, JSON strings must escape control characters,
43 // quotation marks, and reverse solidus (\). This function copies a string and
44 // escapes these characters as shown in http://www.json.org/string.gif.
45 //
46 // The return value is the number of bytes written to the destination
47 // buffer or -1 if the string does not fit in the destination buffer. Since
48 // escaped characters result in two or six bytes in the output, the return value
49 // won't necessarily equal the number of bytes read from the source buffer.
50 //
51 // The destination buffer is NEVER null-terminated!
52 //
53 // Currently this function ONLY supports ASCII! Bytes ≥128 will be escaped
54 // individually, rather than treated as multibyte Unicode characters.
EscapedStringCopy(char * destination,int copy_limit,std::string_view source)55 constexpr int EscapedStringCopy(char* destination,
56                                 int copy_limit,
57                                 std::string_view source) {
58   int destination_index = 0;
59 
60   for (char source_char : source) {
61     if (destination_index >= copy_limit) {
62       return -1;
63     }
64 
65     char escaped_character = '\0';
66 
67     if (source_char >= '\b' && source_char <= '\r' && source_char != '\v') {
68       constexpr char kControlChars[] = {'b', 't', 'n', '?', 'f', 'r'};
69       escaped_character = kControlChars[source_char - '\b'];
70     } else if (source_char == '"' || source_char == '\\') {
71       escaped_character = source_char;
72     } else if (source_char >= ' ' && source_char <= '~') {
73       // This is a printable character; no escaping is needed.
74       destination[destination_index++] = source_char;
75       continue;  // Skip the escaping step below.
76     } else {
77       // Escape control characters that haven't already been handled. These take
78       // six bytes to encode (e.g. \u0056).
79       if (copy_limit - destination_index < 6) {
80         return -1;
81       }
82 
83       destination[destination_index++] = '\\';
84       destination[destination_index++] = 'u';
85       destination[destination_index++] = '0';  // Only handle ASCII for now
86       destination[destination_index++] = '0';
87       destination[destination_index++] = NibbleToHex((source_char >> 4) & 0x0f);
88       destination[destination_index++] = NibbleToHex(source_char & 0x0f);
89       continue;  // Already escaped; skip the single-character escaping step.
90     }
91 
92     // Escape the \b, \t, \n, \f, \r, \", or \\ character, if it fits.
93     if (copy_limit - destination_index < 2) {
94       return -1;
95     }
96 
97     destination[destination_index++] = '\\';
98     destination[destination_index++] = escaped_character;
99   }
100   return destination_index;
101 }
102 
103 // Writes "<value>", escaping special characters; returns true if successful.
104 // Null terminates ONLY if successful.
WriteQuotedString(std::string_view value,char * buffer,size_t buffer_size)105 constexpr StatusWithSize WriteQuotedString(std::string_view value,
106                                            char* buffer,
107                                            size_t buffer_size) {
108   constexpr size_t kOverhead = 2 /* quotes */ + 1 /* null */;
109   if (value.size() + kOverhead > buffer_size) {
110     return StatusWithSize::ResourceExhausted();
111   }
112   // If the string might fit, try to copy it. May still run out of room due to
113   // escaping.
114   const int written =
115       EscapedStringCopy(buffer + 1 /* quote */, buffer_size - kOverhead, value);
116   if (written < 0) {
117     return StatusWithSize::ResourceExhausted();
118   }
119 
120   buffer[0] = '"';            // open quote
121   buffer[written + 1] = '"';  // close quote
122   buffer[written + 2] = '\0';
123   return StatusWithSize(written + 2);  // characters written excludes \0
124 }
125 
WriteCharPointer(const char * ptr,char * buffer,size_t buffer_size)126 constexpr StatusWithSize WriteCharPointer(const char* ptr,
127                                           char* buffer,
128                                           size_t buffer_size) {
129   if (ptr == nullptr) {
130     return WriteString("null", buffer, buffer_size);
131   }
132   return WriteQuotedString(ptr, buffer, buffer_size);
133 }
134 
135 template <typename T>
136 inline constexpr bool kIsJson =
137     std::is_base_of_v<JsonValue, T> || std::is_base_of_v<JsonArray, T> ||
138     std::is_base_of_v<JsonObject, T>;
139 
140 template <typename T>
141 struct InvalidJsonType : std::false_type {};
142 
143 struct LiteralChars {
144   const char (&open_close)[2];
145 };
146 
147 template <typename T>
SerializeJson(const T & value,char * buffer,size_t remaining)148 constexpr StatusWithSize SerializeJson(const T& value,
149                                        char* buffer,
150                                        size_t remaining) {
151   if constexpr (kIsJson<T>) {  // nested JsonBuilder, JsonArray, JsonObject
152     return WriteString(value, buffer, remaining);
153   } else if constexpr (std::is_same_v<T, LiteralChars>) {  // Nested append/add
154     return WriteString(
155         std::string_view(value.open_close, 2), buffer, remaining);
156   } else if constexpr (std::is_null_pointer_v<T> ||  // nullptr & C strings
157                        std::is_same_v<T, char*> ||
158                        std::is_same_v<T, const char*>) {
159     return WriteCharPointer(value, buffer, remaining);
160   } else if constexpr (std::is_convertible_v<T, std::string_view>) {  // strings
161     return WriteQuotedString(value, buffer, remaining);
162   } else if constexpr (std::is_floating_point_v<T>) {
163     return string::FloatAsIntToString(value, {buffer, remaining});
164   } else if constexpr (std::is_same_v<T, bool>) {  // boolean
165     return WriteString(value ? "true" : "false", buffer, remaining);
166   } else if constexpr (std::is_integral_v<T>) {  // integers
167     return string::IntToString(value, {buffer, remaining});
168   } else {
169     static_assert(InvalidJsonType<T>(),
170                   "JSON values may only be numbers, strings, JSON arrays, JSON "
171                   "objects, or null");
172     return StatusWithSize::Internal();
173   }
174 }
175 
176 }  // namespace json_impl
177 
178 // Forward NestedJsonArray, NestedJsonObject, JsonArray, and JsonObject
179 // function calls to JsonBuilder.
180 
181 #define PW_JSON_COMMON_INTERFACE_IMPL(name)                                    \
182   constexpr bool name::IsValue() const {                                       \
183     return static_cast<const JsonBuilder*>(this)->IsValue();                   \
184   }                                                                            \
185   constexpr bool name::IsArray() const {                                       \
186     return static_cast<const JsonBuilder*>(this)->IsArray();                   \
187   }                                                                            \
188   constexpr bool name::IsObject() const {                                      \
189     return static_cast<const JsonBuilder*>(this)->IsObject();                  \
190   }                                                                            \
191   constexpr name::operator std::string_view() const {                          \
192     return static_cast<const JsonBuilder*>(this)->operator std::string_view(); \
193   }                                                                            \
194   constexpr const char* name::data() const {                                   \
195     return static_cast<const JsonBuilder*>(this)->data();                      \
196   }                                                                            \
197   constexpr size_t name::size() const {                                        \
198     return static_cast<const JsonBuilder*>(this)->size();                      \
199   }                                                                            \
200   constexpr size_t name::max_size() const {                                    \
201     return static_cast<const JsonBuilder*>(this)->max_size();                  \
202   }                                                                            \
203   constexpr bool name::ok() const {                                            \
204     return static_cast<const JsonBuilder*>(this)->ok();                        \
205   }                                                                            \
206   constexpr Status name::status() const {                                      \
207     return static_cast<const JsonBuilder*>(this)->status();                    \
208   }                                                                            \
209   constexpr Status name::last_status() const {                                 \
210     return static_cast<const JsonBuilder*>(this)->last_status();               \
211   }                                                                            \
212   constexpr void name::clear() {                                               \
213     static_cast<JsonBuilder*>(this)->name##Clear();                            \
214   }                                                                            \
215   constexpr void name::clear_status() {                                        \
216     static_cast<JsonBuilder*>(this)->clear_status();                           \
217   }                                                                            \
218   static_assert(true)
219 
220 PW_JSON_COMMON_INTERFACE_IMPL(JsonValue);
221 PW_JSON_COMMON_INTERFACE_IMPL(JsonArray);
222 PW_JSON_COMMON_INTERFACE_IMPL(JsonObject);
223 
224 #undef PW_JSON_COMMON_INTERFACE_IMPL
225 
226 template <typename T>
Append(const T & value)227 constexpr NestedJsonArray& NestedJsonArray::Append(const T& value) {
228   json_.builder().NestedJsonArrayAppend(value, json_.nesting());
229   return *this;
230 }
AppendNestedArray()231 constexpr NestedJsonArray NestedJsonArray::AppendNestedArray() {
232   return json_.builder().JsonArrayAppendNested(json_impl::kArray,
233                                                json_.nesting());
234 }
AppendNestedObject()235 constexpr NestedJsonObject NestedJsonArray::AppendNestedObject() {
236   return json_.builder().JsonArrayAppendNested(json_impl::kObject,
237                                                json_.nesting());
238 }
AddNestedArray(std::string_view key)239 constexpr NestedJsonArray NestedJsonObject::AddNestedArray(
240     std::string_view key) {
241   return json_.builder().JsonObjectAddNested(
242       key, json_impl::kArray, json_.nesting());
243 }
AddNestedObject(std::string_view key)244 constexpr NestedJsonObject NestedJsonObject::AddNestedObject(
245     std::string_view key) {
246   return json_.builder().JsonObjectAddNested(
247       key, json_impl::kObject, json_.nesting());
248 }
249 template <typename T>
Add(std::string_view key,const T & value)250 constexpr NestedJsonObject& NestedJsonObject::Add(std::string_view key,
251                                                   const T& value) {
252   json_.builder().NestedJsonObjectAdd(key, value, json_.nesting());
253   return *this;
254 }
255 template <typename T>
Set(const T & value)256 constexpr Status JsonValue::Set(const T& value) {
257   return static_cast<JsonBuilder*>(this)->JsonValueSet(value);
258 }
259 template <typename T>
Append(const T & value)260 constexpr JsonArray& JsonArray::Append(const T& value) {
261   return static_cast<JsonBuilder*>(this)->JsonArrayAppend(value);
262 }
AppendNestedArray()263 constexpr NestedJsonArray JsonArray::AppendNestedArray() {
264   return static_cast<JsonBuilder*>(this)->JsonArrayAppendNested(
265       json_impl::kArray, {});
266 }
AppendNestedObject()267 constexpr NestedJsonObject JsonArray::AppendNestedObject() {
268   return static_cast<JsonBuilder*>(this)->JsonArrayAppendNested(
269       json_impl::kObject, {});
270 }
271 template <typename Iterable>
Extend(const Iterable & iterable)272 constexpr JsonArray& JsonArray::Extend(const Iterable& iterable) {
273   return static_cast<JsonBuilder*>(this)->JsonArrayExtend(std::cbegin(iterable),
274                                                           std::cend(iterable));
275 }
276 template <typename T, size_t kSize>
Extend(const T (& iterable)[kSize])277 constexpr JsonArray& JsonArray::Extend(const T (&iterable)[kSize]) {
278   return static_cast<JsonBuilder*>(this)->JsonArrayExtend(std::cbegin(iterable),
279                                                           std::cend(iterable));
280 }
281 template <typename T>
Add(std::string_view key,const T & value)282 constexpr JsonObject& JsonObject::Add(std::string_view key, const T& value) {
283   return static_cast<JsonBuilder*>(this)->JsonObjectAdd(key, value);
284 }
AddNestedArray(std::string_view key)285 constexpr NestedJsonArray JsonObject::AddNestedArray(std::string_view key) {
286   return static_cast<JsonBuilder*>(this)->JsonObjectAddNested(
287       key, json_impl::kArray, {});
288 }
AddNestedObject(std::string_view key)289 constexpr NestedJsonObject JsonObject::AddNestedObject(std::string_view key) {
290   return static_cast<JsonBuilder*>(this)->JsonObjectAddNested(
291       key, json_impl::kObject, {});
292 }
293 
294 // JsonBuilder function implementations.
295 
296 template <typename T>
SetValue(const T & value)297 constexpr Status JsonBuilder::SetValue(const T& value) {
298   return HandleSet(json_impl::SerializeJson(value, buffer_, max_size_ + 1));
299 }
300 
update_status(Status new_status)301 constexpr void JsonBuilder::update_status(Status new_status) {
302   last_status_ = new_status.code();
303   if (!new_status.ok() && status().ok()) {
304     status_ = new_status.code();
305   }
306 }
307 
308 template <typename T>
JsonValueSet(const T & value)309 constexpr Status JsonBuilder::JsonValueSet(const T& value) {
310   if constexpr (json_impl::kIsJson<T>) {
311     PW_ASSERT(this != &value);   // Self-nesting is disallowed.
312     PW_ASSERT(value.IsValue());  // Cannot set JsonValue to an array or object.
313   }
314   return SetValue(value);
315 }
316 
317 template <typename T>
JsonArrayAppend(const T & value)318 constexpr JsonArray& JsonBuilder::JsonArrayAppend(const T& value) {
319   if constexpr (json_impl::kIsJson<T>) {
320     PW_ASSERT(this != &value);  // Self-nesting is disallowed.
321   }
322 
323   const size_t starting_size = size();
324   if (JsonArrayAddElement()) {
325     // The buffer size is remaining() + 1 bytes, but drop the + 1 to leave room
326     // for the closing ].
327     HandleAdd(json_impl::SerializeJson(value, &buffer_[size()], remaining()),
328               starting_size,
329               ']');
330   }
331   return *this;
332 }
333 
334 template <typename Iterator>
JsonArrayExtend(Iterator begin,Iterator end)335 constexpr JsonArray& JsonBuilder::JsonArrayExtend(Iterator begin,
336                                                   Iterator end) {
337   const size_t starting_size = size();
338 
339   for (Iterator cur = begin; cur != end; ++cur) {
340     const Status status = Append(*cur).last_status();
341     if (!status.ok()) {  // Undo changes if there is an issue.
342       json_size_ = starting_size;
343       buffer_[size() - 1] = ']';
344       buffer_[size()] = '\0';
345       break;
346     }
347   }
348   return *this;
349 }
350 
351 template <typename T>
JsonObjectAdd(std::string_view key,const T & value)352 constexpr JsonObject& JsonBuilder::JsonObjectAdd(std::string_view key,
353                                                  const T& value) {
354   if constexpr (json_impl::kIsJson<T>) {
355     PW_ASSERT(this != &value);  // Self-nesting is disallowed.
356   }
357 
358   const size_t starting_size = size();
359   if (JsonObjectAddKey(key)) {
360     // The buffer size is remaining() + 1 bytes, but drop the + 1 to leave room
361     // for the closing }.
362     HandleAdd(json_impl::SerializeJson(value, &buffer_[size()], remaining()),
363               starting_size,
364               '}');
365   }
366   return *this;
367 }
368 
JsonArrayAddElement()369 constexpr bool JsonBuilder::JsonArrayAddElement() {
370   PW_ASSERT(IsArray());  // Attempted to append to an object or value
371 
372   // Needs space for at least 3 new characters (, 1)
373   if (size() + 3 > max_size()) {
374     update_status(Status::ResourceExhausted());
375     return false;
376   }
377 
378   // If this is the first element, just drop the ]. Otherwise, add a comma.
379   if (size() == 2) {
380     json_size_ = 1;
381   } else {
382     buffer_[json_size_ - 1] = ',';
383     buffer_[json_size_++] = ' ';
384   }
385 
386   return true;
387 }
388 
JsonObjectAddKey(std::string_view key)389 constexpr bool JsonBuilder::JsonObjectAddKey(std::string_view key) {
390   PW_ASSERT(IsObject());  // Attempted add a key-value pair to an array or value
391 
392   // Each key needs 7 more characters: (, "": ) plus at least 1 for the value.
393   // The ',' replaces the terminal '}', but a new '}' is placed at the end, so
394   // the total remains 7. The first key could get away with 5, but oh well.
395   if (size() + key.size() + 7 > max_size()) {
396     update_status(Status::ResourceExhausted());
397     return false;
398   }
399 
400   // If this is the first key, just drop the }. Otherwise, add a comma.
401   if (size() == 2) {
402     json_size_ = 1;
403   } else {
404     buffer_[json_size_ - 1] = ',';  // change the last } to ,
405     buffer_[json_size_++] = ' ';
406   }
407 
408   // Buffer size is remaining() + 1, but - 4 for at least ": 0}" after the key.
409   auto written =
410       json_impl::WriteQuotedString(key, &buffer_[json_size_], remaining() - 3);
411   if (!written.ok()) {
412     return false;
413   }
414 
415   json_size_ += written.size();  // Now have {"key" or {..., "key"
416   buffer_[json_size_++] = ':';
417   buffer_[json_size_++] = ' ';
418   return true;
419 }
420 
JsonArrayAppendNested(const char (& open_close)[2],const json_impl::Nesting & nesting)421 constexpr json_impl::NestedJson JsonBuilder::JsonArrayAppendNested(
422     const char (&open_close)[2], const json_impl::Nesting& nesting) {
423   AddNestedStart(nesting);
424   json_impl::Nesting::Type nesting_within = type();
425   JsonArrayAppend(json_impl::LiteralChars{open_close});  // [..., {}]
426   AddNestedFinish(nesting);
427   return json_impl::NestedJson(
428       *this,
429       last_status().ok()
430           ? nesting.Nest(NestedJsonOffset(nesting), nesting_within)
431           : json_impl::Nesting());
432 }
433 
JsonObjectAddNested(std::string_view key,const char (& open_close)[2],const json_impl::Nesting & nesting)434 constexpr json_impl::NestedJson JsonBuilder::JsonObjectAddNested(
435     std::string_view key,
436     const char (&open_close)[2],
437     const json_impl::Nesting& nesting) {
438   AddNestedStart(nesting);
439   json_impl::Nesting::Type nesting_within = type();
440   JsonObjectAdd(key, json_impl::LiteralChars{open_close});  // {..., "key": {}}
441   AddNestedFinish(nesting);
442   return json_impl::NestedJson(
443       *this,
444       last_status().ok()
445           ? nesting.Nest(NestedJsonOffset(nesting), nesting_within)
446           : json_impl::Nesting());
447 }
448 
AddNestedStart(const json_impl::Nesting & nesting)449 constexpr void JsonBuilder::AddNestedStart(const json_impl::Nesting& nesting) {
450   // A nested structure must be the last thing in the JSON. Back up to where the
451   // first of the closing ] or } should be, and check from there.
452   PW_ASSERT(  // JSON must not have been cleared since nesting.
453       json_size_ >= nesting.offset() + nesting.depth() + 2 /* [] or {} */);
454   PW_ASSERT(  // Nested structure must match the expected type
455       buffer_[json_size_ - nesting.depth() - 1] ==
456       buffer_[nesting.offset()] + 2 /* convert [ to ] or { to } */);
457   nesting.CheckNesting(&buffer_[json_size_ - nesting.depth()]);
458 
459   buffer_ += nesting.offset();
460   json_size_ -= nesting.offset() + nesting.depth();
461   max_size_ -= nesting.offset() + nesting.depth();
462 }
463 
AddNestedFinish(const json_impl::Nesting & nesting)464 constexpr void JsonBuilder::AddNestedFinish(const json_impl::Nesting& nesting) {
465   buffer_ -= nesting.offset();
466   max_size_ += nesting.offset() + nesting.depth();
467 
468   json_size_ += nesting.offset();
469   nesting.Terminate(&buffer_[json_size_]);
470   json_size_ += nesting.depth();
471 }
472 
473 template <typename T>
NestedJsonArrayAppend(const T & value,const json_impl::Nesting & nesting)474 constexpr void JsonBuilder::NestedJsonArrayAppend(
475     const T& value, const json_impl::Nesting& nesting) {
476   AddNestedStart(nesting);
477   JsonArrayAppend(value);
478   AddNestedFinish(nesting);
479 }
480 
481 template <typename T>
NestedJsonObjectAdd(std::string_view key,const T & value,const json_impl::Nesting & nesting)482 constexpr void JsonBuilder::NestedJsonObjectAdd(
483     std::string_view key, const T& value, const json_impl::Nesting& nesting) {
484   AddNestedStart(nesting);
485   JsonObjectAdd(key, value);
486   AddNestedFinish(nesting);
487 }
488 
HandleSet(StatusWithSize written)489 constexpr Status JsonBuilder::HandleSet(StatusWithSize written) {
490   if (written.ok()) {
491     json_size_ = written.size();
492   } else {
493     MakeNull();
494   }
495   set_statuses(written.status());  // status is always reset when setting value
496   return last_status();
497 }
498 
HandleAdd(StatusWithSize written,size_t starting_size,char terminator)499 constexpr void JsonBuilder::HandleAdd(StatusWithSize written,
500                                       size_t starting_size,
501                                       char terminator) {
502   update_status(written.status());  // save room for } or ]
503   if (last_status().ok()) {
504     json_size_ += written.size();
505   } else {
506     json_size_ = starting_size - 1;  // make room for closing character
507   }
508 
509   buffer_[json_size_++] = terminator;
510   buffer_[json_size_] = '\0';
511 }
512 
513 }  // namespace pw
514