Coverage Report

Created: 2025-07-09 06:27

/src/glaze/include/glaze/json/schema.hpp
Line
Count
Source (jump to first uncovered line)
1
// Glaze Library
2
// For the license information refer to glaze.hpp
3
#pragma once
4
5
// We include naming metas for standard types for consistency across compilers
6
#include "glaze/api/std/array.hpp"
7
#include "glaze/api/std/deque.hpp"
8
#include "glaze/api/std/functional.hpp"
9
#include "glaze/api/std/list.hpp"
10
#include "glaze/api/std/map.hpp"
11
#include "glaze/api/std/optional.hpp"
12
#include "glaze/api/std/shared_ptr.hpp"
13
#include "glaze/api/std/string.hpp"
14
#include "glaze/api/std/tuple.hpp"
15
#include "glaze/api/std/unique_ptr.hpp"
16
#include "glaze/api/std/unordered_map.hpp"
17
#include "glaze/api/std/variant.hpp"
18
#include "glaze/api/std/vector.hpp"
19
#include "glaze/api/tuplet.hpp"
20
#include "glaze/api/type_support.hpp"
21
#include "glaze/json/wrappers.hpp"
22
#include "glaze/json/write.hpp"
23
24
namespace glz
25
{
26
   namespace detail
27
   {
28
      enum struct defined_formats : uint32_t;
29
      struct ExtUnits final
30
      {
31
         std::optional<std::string_view> unitAscii{}; // ascii representation of the unit, e.g. "m^2" for square meters
32
         std::optional<std::string_view>
33
            unitUnicode{}; // unicode representation of the unit, e.g. "m²" for square meters
34
         constexpr bool operator==(const ExtUnits&) const noexcept = default;
35
      };
36
   }
37
   struct schema final
38
   {
39
      bool reflection_helper{}; // needed to support automatic reflection, because ref is a std::optional
40
      std::optional<std::string_view> ref{};
41
      using schema_number = std::optional<std::variant<int64_t, uint64_t, double>>;
42
      using schema_any = std::variant<std::monostate, bool, int64_t, uint64_t, double, std::string_view>;
43
      // meta data keywords, ref: https://www.learnjsonschema.com/2020-12/meta-data/
44
      std::optional<std::string_view> title{};
45
      std::optional<std::string_view> description{};
46
      std::optional<schema_any> defaultValue{};
47
      std::optional<bool> deprecated{};
48
      std::optional<std::vector<std::string_view>> examples{};
49
      std::optional<bool> readOnly{};
50
      std::optional<bool> writeOnly{};
51
      // hereafter validation keywords, ref: https://www.learnjsonschema.com/2020-12/validation/
52
      std::optional<schema_any> constant{};
53
      // string only keywords
54
      std::optional<uint64_t> minLength{};
55
      std::optional<uint64_t> maxLength{};
56
      std::optional<std::string_view> pattern{};
57
      // https://www.learnjsonschema.com/2020-12/format-annotation/format/
58
      std::optional<detail::defined_formats> format{};
59
      // number only keywords
60
      schema_number minimum{};
61
      schema_number maximum{};
62
      schema_number exclusiveMinimum{};
63
      schema_number exclusiveMaximum{};
64
      schema_number multipleOf{};
65
      // object only keywords
66
      std::optional<uint64_t> minProperties{};
67
      std::optional<uint64_t> maxProperties{};
68
      // std::optional<std::map<std::string_view, std::vector<std::string_view>>> dependent_required{};
69
      std::optional<std::vector<std::string_view>> required{};
70
      // array only keywords
71
      std::optional<uint64_t> minItems{};
72
      std::optional<uint64_t> maxItems{};
73
      std::optional<uint64_t> minContains{};
74
      std::optional<uint64_t> maxContains{};
75
      std::optional<bool> uniqueItems{};
76
      // properties
77
      std::optional<std::vector<std::string_view>> enumeration{}; // enum
78
79
      // out of json schema specification
80
      std::optional<detail::ExtUnits> ExtUnits{};
81
      std::optional<bool>
82
         ExtAdvanced{}; // flag to indicate that the parameter is advanced and can be hidden in default views
83
84
      static constexpr auto schema_attributes{true}; // allowance flag to indicate metadata within glz::object(...)
85
86
      // TODO switch to using variants when we have write support to get rid of nulls
87
      // TODO We should be able to generate the json schema compiletime
88
      struct glaze
89
      {
90
         using T = schema;
91
         static constexpr std::array keys{"$ref", //
92
                                          "title", //
93
                                          "description", //
94
                                          "default", //
95
                                          "deprecated", //
96
                                          "examples", //
97
                                          "readOnly", //
98
                                          "writeOnly", //
99
                                          "const", //
100
                                          "minLength", //
101
                                          "maxLength", //
102
                                          "pattern", //
103
                                          "format", //
104
                                          "minimum", //
105
                                          "maximum", //
106
                                          "exclusiveMinimum", //
107
                                          "exclusiveMaximum", //
108
                                          "multipleOf", //
109
                                          "minProperties", //
110
                                          "maxProperties", //
111
                                          //"dependentRequired", //
112
                                          "required", //
113
                                          "minItems", //
114
                                          "maxItems", //
115
                                          "minContains", //
116
                                          "maxContains", //
117
                                          "uniqueItems", //
118
                                          "enum", //
119
                                          "ExtUnits", //
120
                                          "ExtAdvanced"};
121
122
         static constexpr glz::tuple value = {&T::ref, //
123
                                              &T::title, //
124
                                              &T::description, //
125
                                              &T::defaultValue, //
126
                                              &T::deprecated, //
127
                                              raw<&T::examples>, //
128
                                              &T::readOnly, //
129
                                              &T::writeOnly, //
130
                                              &T::constant, //
131
                                              &T::minLength, //
132
                                              &T::maxLength, //
133
                                              &T::pattern, //
134
                                              &T::format, //
135
                                              &T::minimum, //
136
                                              &T::maximum, //
137
                                              &T::exclusiveMinimum, //
138
                                              &T::exclusiveMaximum, //
139
                                              &T::multipleOf, //
140
                                              &T::minProperties, //
141
                                              &T::maxProperties, //
142
                                              // &T::dependent_required, //
143
                                              &T::required, //
144
                                              &T::minItems, //
145
                                              &T::maxItems, //
146
                                              &T::minContains, //
147
                                              &T::maxContains, //
148
                                              &T::uniqueItems, //
149
                                              &T::enumeration, //
150
                                              &T::ExtUnits, //
151
                                              &T::ExtAdvanced};
152
      };
153
   };
154
155
   namespace detail
156
   {
157
      struct schematic final
158
      {
159
         std::optional<std::vector<std::string_view>> type{};
160
         std::optional<std::map<std::string_view, schema, std::less<>>> properties{}; // glaze_object
161
         std::optional<schema> items{}; // array
162
         std::optional<std::variant<bool, schema>> additionalProperties{}; // map
163
         std::optional<std::map<std::string_view, schematic, std::less<>>> defs{};
164
         std::optional<std::vector<schematic>> oneOf{};
165
         std::optional<std::vector<std::string_view>> required{};
166
         std::optional<std::vector<std::string_view>> examples{};
167
         schema attributes{};
168
      };
169
170
      enum struct defined_formats : uint32_t {
171
         datetime, //
172
         date, //
173
         time, //
174
         duration, //
175
         email, //
176
         idn_email, //
177
         hostname, //
178
         idn_hostname, //
179
         ipv4, //
180
         ipv6, //
181
         uri, //
182
         uri_reference, //
183
         iri, //
184
         iri_reference, //
185
         uuid, //
186
         uri_template, //
187
         json_pointer, //
188
         relative_json_pointer, //
189
         regex
190
      };
191
   }
192
}
193
194
template <>
195
struct glz::meta<glz::detail::defined_formats>
196
{
197
   static constexpr std::string_view name = "defined_formats";
198
   using enum glz::detail::defined_formats;
199
   static constexpr std::array keys{
200
      "date-time", "date",          "time", "duration",     "email",        "idn-email",
201
      "hostname",  "idn-hostname",  "ipv4", "ipv6",         "uri",          "uri-reference",
202
      "iri",       "iri-reference", "uuid", "uri-template", "json-pointer", "relative-json-pointer",
203
      "regex"};
204
   static constexpr std::array value{datetime, //
205
                                     date, //
206
                                     time, //
207
                                     duration, //
208
                                     email, //
209
                                     idn_email, //
210
                                     hostname, //
211
                                     idn_hostname, //
212
                                     ipv4, //
213
                                     ipv6, //
214
                                     uri, //
215
                                     uri_reference, //
216
                                     iri, //
217
                                     iri_reference, //
218
                                     uuid, //
219
                                     uri_template, //
220
                                     json_pointer, //
221
                                     relative_json_pointer, //
222
                                     regex};
223
};
224
225
template <>
226
struct glz::meta<glz::detail::schematic>
227
{
228
   static constexpr std::string_view name = "glz::detail::schema";
229
   using T = detail::schematic;
230
   static constexpr std::array keys{"type", //
231
                                    "properties", //
232
                                    "items", //
233
                                    "additionalProperties", //
234
                                    "$defs", //
235
                                    "oneOf", //
236
                                    "examples", //
237
                                    "required", //
238
                                    "title", //
239
                                    "description", //
240
                                    "default", //
241
                                    "deprecated", //
242
                                    "readOnly", //
243
                                    "writeOnly", //
244
                                    "const", //
245
                                    "minLength", //
246
                                    "maxLength", //
247
                                    "pattern", //
248
                                    "format", //
249
                                    "minimum", //
250
                                    "maximum", //
251
                                    "exclusiveMinimum", //
252
                                    "exclusiveMaximum", //
253
                                    "multipleOf", //
254
                                    "minProperties", //
255
                                    "maxProperties", //
256
                                    // "dependentRequired", //
257
                                    "minItems", //
258
                                    "maxItems", //
259
                                    "minContains", //
260
                                    "maxContains", //
261
                                    "uniqueItems", //
262
                                    "enum", //
263
                                    "ExtUnits", //
264
                                    "ExtAdvanced"};
265
266
   [[maybe_unused]] static constexpr glz::tuple value{
267
      &T::type, //
268
      &T::properties, //
269
      &T::items, //
270
      &T::additionalProperties, //
271
      &T::defs, //
272
      &T::oneOf, //
273
      raw<&T::examples>, //
274
      &T::required, //
275
      [](auto&& s) -> auto& { return s.attributes.title; }, //
276
      [](auto&& s) -> auto& { return s.attributes.description; }, //
277
      [](auto&& s) -> auto& { return s.attributes.defaultValue; }, //
278
      [](auto&& s) -> auto& { return s.attributes.deprecated; }, //
279
      [](auto&& s) -> auto& { return s.attributes.readOnly; }, //
280
      [](auto&& s) -> auto& { return s.attributes.writeOnly; }, //
281
      [](auto&& s) -> auto& { return s.attributes.constant; }, //
282
      [](auto&& s) -> auto& { return s.attributes.minLength; }, //
283
      [](auto&& s) -> auto& { return s.attributes.maxLength; }, //
284
      [](auto&& s) -> auto& { return s.attributes.pattern; }, //
285
      [](auto&& s) -> auto& { return s.attributes.format; }, //
286
      [](auto&& s) -> auto& { return s.attributes.minimum; }, //
287
      [](auto&& s) -> auto& { return s.attributes.maximum; }, //
288
      [](auto&& s) -> auto& { return s.attributes.exclusiveMinimum; }, //
289
      [](auto&& s) -> auto& { return s.attributes.exclusiveMaximum; }, //
290
      [](auto&& s) -> auto& { return s.attributes.multipleOf; }, //
291
      [](auto&& s) -> auto& { return s.attributes.minProperties; }, //
292
      [](auto&& s) -> auto& { return s.attributes.maxProperties; }, //
293
      // [](auto&& s) -> auto& { return s.attributes.dependent_required; }, //
294
      [](auto&& s) -> auto& { return s.attributes.minItems; }, //
295
      [](auto&& s) -> auto& { return s.attributes.maxItems; }, //
296
      [](auto&& s) -> auto& { return s.attributes.minContains; }, //
297
      [](auto&& s) -> auto& { return s.attributes.maxContains; }, //
298
      [](auto&& s) -> auto& { return s.attributes.uniqueItems; }, //
299
      [](auto&& s) -> auto& { return s.attributes.enumeration; }, //
300
      [](auto&& s) -> auto& { return s.attributes.ExtUnits; }, //
301
      [](auto&& s) -> auto& { return s.attributes.ExtAdvanced; }};
302
};
303
304
namespace glz
305
{
306
   namespace detail
307
   {
308
      template <class T = void>
309
      struct to_json_schema
310
      {
311
         template <auto Opts>
312
         static void op(auto& s, auto& defs)
313
         {
314
            // &T::member
315
            if constexpr (glaze_t<T> && std::is_member_object_pointer_v<meta_wrapper_t<T>>) {
316
               using val_t = member_t<T, meta_wrapper_t<T>>;
317
               to_json_schema<val_t>::template op<Opts>(s, defs);
318
            }
319
            else if constexpr (glaze_const_value_t<T>) { // &T::constexpr_member
320
               using constexpr_val_t = member_t<T, meta_wrapper_t<T>>;
321
               static constexpr auto val_v{*glz::meta_wrapper_v<T>};
322
               if constexpr (glz::glaze_enum_t<constexpr_val_t>) {
323
                  s.attributes.constant = glz::enum_name_v<val_v>;
324
               }
325
               else {
326
                  // General case, needs to be convertible to schema_any
327
                  s.attributes.constant = val_v;
328
               }
329
               to_json_schema<constexpr_val_t>::template op<Opts>(s, defs);
330
            }
331
            else {
332
               s.type = {"number", "string", "boolean", "object", "array", "null"};
333
            }
334
         }
335
      };
336
337
      template <class T>
338
         requires(std::same_as<T, bool> || std::same_as<T, std::vector<bool>::reference> ||
339
                  std::same_as<T, std::vector<bool>::const_reference>)
340
      struct to_json_schema<T>
341
      {
342
         template <auto Opts>
343
         static void op(auto& s, auto&)
344
         {
345
            s.type = {"boolean"};
346
         }
347
      };
348
349
      template <num_t T>
350
      struct to_json_schema<T>
351
      {
352
         template <auto Opts>
353
         static void op(auto& s, auto&)
354
         {
355
            using V = std::decay_t<T>;
356
            if constexpr (std::integral<V>) {
357
               s.type = {"integer"};
358
               s.attributes.minimum = static_cast<std::int64_t>(std::numeric_limits<V>::lowest());
359
               s.attributes.maximum = static_cast<std::uint64_t>((std::numeric_limits<V>::max)());
360
            }
361
            else {
362
               s.type = {"number"};
363
               s.attributes.minimum = std::numeric_limits<V>::lowest();
364
               s.attributes.maximum = (std::numeric_limits<V>::max)();
365
            }
366
         }
367
      };
368
369
      template <class T>
370
         requires str_t<T> || char_t<T>
371
      struct to_json_schema<T>
372
      {
373
         template <auto Opts>
374
         static void op(auto& s, auto&)
375
         {
376
            s.type = {"string"};
377
         }
378
      };
379
380
      template <always_null_t T>
381
      struct to_json_schema<T>
382
      {
383
         template <auto Opts>
384
         static void op(auto& s, auto&)
385
         {
386
            s.type = {"null"};
387
            s.attributes.constant = std::monostate{};
388
         }
389
      };
390
391
      template <glaze_enum_t T>
392
      struct to_json_schema<T>
393
      {
394
         template <auto Opts>
395
         static void op(auto& s, auto&)
396
         {
397
            s.type = {"string"};
398
399
            // TODO use oneOf instead of enum to handle doc comments
400
            static constexpr auto N = reflect<T>::size;
401
            // s.enumeration = std::vector<std::string_view>(N);
402
            // for_each<N>([&]<auto I>() {
403
            //    static constexpr auto item = std::get<I>(meta_v<V>);
404
            //    (*s.enumeration)[I] = std::get<0>(item);
405
            // });
406
            s.oneOf = std::vector<schematic>(N);
407
            for_each<N>([&]<auto I>() {
408
               auto& enumeration = (*s.oneOf)[I];
409
               // Do not override if already set
410
               if (!enumeration.attributes.constant.has_value()) {
411
                  enumeration.attributes.constant = reflect<T>::keys[I];
412
               }
413
               if (!enumeration.attributes.title.has_value()) {
414
                  enumeration.attributes.title = reflect<T>::keys[I];
415
               }
416
            });
417
         }
418
      };
419
420
      template <class T>
421
      struct to_json_schema<basic_raw_json<T>>
422
      {
423
         template <auto Opts>
424
         static void op(auto& s, auto&)
425
         {
426
            s.type = {"number", "string", "boolean", "object", "array", "null"};
427
         }
428
      };
429
430
      template <array_t T>
431
      struct to_json_schema<T>
432
      {
433
         template <auto Opts>
434
         static void op(auto& s, auto& defs)
435
         {
436
            using V = std::decay_t<range_value_t<std::decay_t<T>>>;
437
            s.type = {"array"};
438
            if constexpr (has_fixed_size_container<std::decay_t<T>>) {
439
               s.attributes.minItems = get_size<std::decay_t<T>>();
440
               s.attributes.maxItems = get_size<std::decay_t<T>>();
441
            }
442
            auto& def = defs[name_v<V>];
443
            if (!def.type) {
444
               to_json_schema<V>::template op<Opts>(def, defs);
445
            }
446
            s.items = schema{true, join_v<chars<"#/$defs/">, name_v<V>>};
447
         }
448
      };
449
450
      template <writable_map_t T>
451
      struct to_json_schema<T>
452
      {
453
         template <auto Opts>
454
         static void op(auto& s, auto& defs)
455
         {
456
            using V = std::decay_t<glz::tuple_element_t<1, range_value_t<std::decay_t<T>>>>;
457
            s.type = {"object"};
458
            auto& def = defs[name_v<V>];
459
            if (!def.type) {
460
               to_json_schema<V>::template op<Opts>(def, defs);
461
            }
462
            s.additionalProperties = schema{true, join_v<chars<"#/$defs/">, name_v<V>>};
463
         }
464
      };
465
466
      template <nullable_t T>
467
      struct to_json_schema<T>
468
      {
469
         template <auto Opts>
470
         static void op(auto& s, auto& defs)
471
         {
472
            using V = std::decay_t<decltype(*std::declval<std::decay_t<T>>())>;
473
            to_json_schema<V>::template op<Opts>(s, defs);
474
            // to_json_schema above should populate the correct type, let's throw if it wasn't set
475
            auto& type = s.type.value();
476
            auto it = std::find_if(type.begin(), type.end(), [&](const auto& str) { return str == "null"; });
477
            if (it == type.end()) {
478
               type.emplace_back("null");
479
            }
480
         }
481
      };
482
483
      template <is_variant T>
484
      struct to_json_schema<T>
485
      {
486
         template <auto Opts>
487
         static void op(auto& s, auto& defs)
488
         {
489
            static constexpr auto N = std::variant_size_v<T>;
490
            using type_counts = variant_type_count<T>;
491
            s.type = std::vector<sv>{};
492
            if constexpr (type_counts::n_number) {
493
               (*s.type).emplace_back("number");
494
            }
495
            if constexpr (type_counts::n_string) {
496
               (*s.type).emplace_back("string");
497
            }
498
            if constexpr (type_counts::n_bool) {
499
               (*s.type).emplace_back("boolean");
500
            }
501
            if constexpr (type_counts::n_object) {
502
               (*s.type).emplace_back("object");
503
            }
504
            if constexpr (type_counts::n_array) {
505
               (*s.type).emplace_back("array");
506
            }
507
            if constexpr (type_counts::n_null) {
508
               (*s.type).emplace_back("null");
509
            }
510
            s.oneOf = std::vector<schematic>(N);
511
512
            const auto& ids = ids_v<T>;
513
514
            for_each<N>([&]<auto I>() {
515
               using V = std::decay_t<std::variant_alternative_t<I, T>>;
516
               auto& schema_val = (*s.oneOf)[I];
517
               to_json_schema<V>::template op<Opts>(schema_val, defs);
518
519
               if (not schema_val.attributes.title) {
520
                  schema_val.attributes.title = ids[I];
521
               }
522
523
               if constexpr ((glaze_object_t<V> || reflectable<V>) && not tag_v<T>.empty()) {
524
                  if (not schema_val.required) {
525
                     schema_val.required = std::vector<sv>{}; // allocate
526
                  }
527
                  schema_val.required->emplace_back(tag_v<T>);
528
                  auto& tag = (*schema_val.properties)[tag_v<T>];
529
                  tag.constant = ids[I];
530
               }
531
            });
532
         }
533
      };
534
535
      template <class T>
536
         requires glaze_array_t<std::decay_t<T>> || tuple_t<std::decay_t<T>>
537
      struct to_json_schema<T>
538
      {
539
         template <auto Opts>
540
         static void op(auto& s, auto&)
541
         {
542
            // TODO: Actually handle this. We can specify a schema per item in items
543
            //      We can also do size restrictions on static arrays
544
            s.type = {"array"};
545
         }
546
      };
547
548
      template <class T>
549
      consteval bool json_schema_matches_object_keys()
550
      {
551
         if constexpr (json_schema_t<T> && (count_members<json_schema_type<T>> > 0)) {
552
            constexpr auto& json_schema_names = member_names<json_schema_type<T>>;
553
            auto fields = reflect<T>::keys;
554
            std::sort(fields.begin(), fields.end());
555
556
            for (const auto& key : json_schema_names) {
557
               if (!std::binary_search(fields.begin(), fields.end(), key)) {
558
                  return false;
559
               }
560
            }
561
            return true;
562
         }
563
         else {
564
            return true;
565
         }
566
      }
567
568
      auto consteval has_slash(std::string_view str) noexcept -> bool
569
      {
570
0
         return std::ranges::any_of(str, [](const auto character) { return character == '/'; });
571
      }
572
      template <const std::string_view& ref>
573
      auto consteval validate_ref() noexcept -> void
574
      {
575
#if (__cpp_static_assert >= 202306L) && (__cplusplus > 202302L)
576
         static_assert(!has_slash(ref),
577
                       join_v<chars<"Slash in name: \"">, ref, chars<"\" in json schema references is not allowed">>);
578
#else
579
         static_assert(!has_slash(ref), "Slashes in json schema references are not allowed");
580
#endif
581
      }
582
583
      template <class T>
584
         requires((glaze_object_t<T> || reflectable<T>))
585
      struct to_json_schema<T>
586
      {
587
         template <auto Opts>
588
         static void op(auto& s, auto& defs)
589
         {
590
            static_assert(json_schema_matches_object_keys<T>());
591
592
            s.type = {"object"};
593
594
            using V = std::decay_t<T>;
595
596
            if constexpr (requires { meta<V>::required; }) {
597
               if (!s.required) {
598
                  s.required = std::vector<sv>{};
599
               }
600
               for (auto& v : meta<V>::required) {
601
                  s.required->emplace_back(v);
602
               }
603
            }
604
605
            if constexpr (requires { meta<V>::examples; }) {
606
               if (!s.examples) {
607
                  s.examples = std::vector<sv>{};
608
               }
609
               for (auto& v : meta<V>::examples) {
610
                  s.examples->emplace_back(v);
611
               }
612
            }
613
614
            static constexpr auto N = reflect<T>::size;
615
            static constexpr auto json_schema_size = reflect<json_schema_type<T>>::size;
616
617
            s.properties = std::map<sv, schema, std::less<>>();
618
            for_each<N>([&]<auto I>() {
619
               using val_t = std::decay_t<refl_t<T, I>>;
620
621
               auto& def = defs[name_v<val_t>];
622
623
               static constexpr sv key = reflect<T>::keys[I];
624
625
               schema ref_val{};
626
               if constexpr (N > 0 && json_schema_size > 0) {
627
                  // We need schema_index to be the index within json_schema_type<T>,
628
                  // but this struct may have fewer keys that don't match the full set of keys in the struct T
629
                  // we therefore can't use `decode_hash_with_size` for either structure.
630
                  // Instead we just loop over the keys, looking for a match:
631
632
                  constexpr auto schema_index = [] {
633
                     size_t i{};
634
                     const auto& schema_keys = reflect<json_schema_type<T>>::keys;
635
                     for (; i < json_schema_size; ++i) {
636
                        if (schema_keys[i] == key) {
637
                           return i;
638
                        }
639
                     }
640
                     return json_schema_size;
641
                  }();
642
643
                  if constexpr (schema_index < json_schema_size) {
644
                     // Experimented with a to_array approach, but the compilation times were significantly higher
645
                     // even when converting this access to a run-time access
646
                     // Tested with both creating a std::array and a heap allocated C-style array and storing in a
647
                     // unique_ptr
648
                     static const auto schema_v = json_schema_type<T>{};
649
                     ref_val = get<schema_index>(to_tie(schema_v));
650
                  }
651
               }
652
               if (!ref_val.ref) {
653
                  validate_ref<name_v<val_t>>();
654
                  ref_val.ref = join_v<chars<"#/$defs/">, name_v<val_t>>;
655
               }
656
657
               if (!def.type) {
658
                  to_json_schema<val_t>::template op<Opts>(def, defs);
659
               }
660
661
               (*s.properties)[key] = ref_val;
662
            });
663
            s.additionalProperties = false;
664
         }
665
      };
666
   }
667
668
   // Moved definition outside of write_json_schema to fix MSVC bug
669
   template <class Opts>
670
   struct opts_write_type_info_off : std::decay_t<Opts>
671
   {
672
      bool write_type_info = false;
673
   };
674
675
   template <class T, auto Opts = opts{}, class Buffer>
676
   [[nodiscard]] error_ctx write_json_schema(Buffer&& buffer)
677
   {
678
      detail::schematic s{};
679
      s.defs.emplace();
680
      detail::to_json_schema<std::decay_t<T>>::template op<Opts>(s, *s.defs);
681
      // Making this static constexpr options to fix MSVC bug
682
      static constexpr opts options = opts_write_type_info_off<decltype(Opts)>{{Opts}};
683
      return write<options>(std::move(s), std::forward<Buffer>(buffer));
684
   }
685
686
   template <class T, auto Opts = opts{}>
687
   [[nodiscard]] glz::expected<std::string, error_ctx> write_json_schema()
688
   {
689
      std::string buffer{};
690
      const error_ctx ec = write_json_schema<T, Opts>(buffer);
691
      if (bool(ec)) [[unlikely]] {
692
         return glz::unexpected(ec);
693
      }
694
      return {buffer};
695
   }
696
}