44#include < string_view>
55#include < tuple>
66
7- // Helper to count elements
8- template <typename ... Args>
9- struct Counter {
10- static constexpr size_t value = sizeof ...(Args);
7+ // Base struct holding Member type and static size calculation helpers,
8+ // independent of N so tests can access them as ObjectSchemaBuilderBase::Member etc.
9+ struct ObjectSchemaBuilderBase {
10+ struct Member {
11+ std::string_view key = " " ;
12+ std::string_view type = " " ;
13+ bool required = false ;
14+ };
15+
16+ static constexpr std::string_view BeginningString = R"( {"type":"object","required":[)" ;
17+ static constexpr std::string_view PostRequiredMembersArrayString = R"( ],"properties":{)" ;
18+ static constexpr std::string_view MemberTypeString = R"( ":{"type":")" ;
19+ static constexpr std::string_view EndMemberTypeString = R"( "})" ;
20+ static constexpr std::string_view EndString = R"( }})" ;
21+ static constexpr std::string_view QuotationMark = " \" " ;
22+ static constexpr std::string_view CommaQuotationMark = " ,\" " ;
23+
24+ template <typename ... Ms>
25+ static constexpr size_t CalculateRequiredListSize (const Ms &...ms) {
26+ size_t size = 0 ;
27+ bool firstRequired = true ;
28+ (void )std::initializer_list<int >{(ms.required ? (firstRequired ? (size += QuotationMark.size () + ms.key .size () + QuotationMark.size (), firstRequired = false )
29+ : (size += CommaQuotationMark.size () + ms.key .size () + QuotationMark.size (), 0 ))
30+ : 0 )...};
31+ return size;
32+ }
33+
34+ template <typename ... Ms>
35+ static constexpr size_t CalculateTypeObjectSize (const Ms &...ms) {
36+ size_t size = 0 ;
37+ bool first = true ;
38+ [[maybe_unused]] auto dummy = {(first ? (size += QuotationMark.size () + ms.key .size () + MemberTypeString.size () + ms.type .size () + EndMemberTypeString.size (), first = false )
39+ : (size += CommaQuotationMark.size () + ms.key .size () + MemberTypeString.size () + ms.type .size () + EndMemberTypeString.size (), 0 ))...};
40+ return size;
41+ }
1142};
1243
13- // Immutable builder using variadic templates
14- template <typename ... Members>
15- struct ObjectSchemaBuilder {
44+ template <size_t N, typename ... Members>
45+ struct ObjectSchemaBuilder : public ObjectSchemaBuilderBase {
1646public:
1747 constexpr ObjectSchemaBuilder (Members... ms) : members(ms...) {}
1848
19- constexpr auto Require (std::string_view key, std::string_view type) {
20- return AddMember (key, type, true );
49+ template <size_t KeyLen, size_t TypeLen>
50+ constexpr auto Require (const char (&key)[KeyLen], const char (&type)[TypeLen]) const {
51+ return AddMember<KeyLen - 1 , TypeLen - 1 , true >(key, type);
2152 }
2253
23- constexpr auto Optional (std::string_view key, std::string_view type) {
24- return AddMember (key, type, false );
54+ template <size_t KeyLen, size_t TypeLen>
55+ constexpr auto Optional (const char (&key)[KeyLen], const char (&type)[TypeLen]) const {
56+ return AddMember<KeyLen - 1 , TypeLen - 1 , false >(key, type);
2557 }
2658
2759 constexpr auto Build () const {
28- return BuildImpl ();
60+ return BuildImpl<N> ();
2961 }
3062
31- // private:
32- struct Member {
33- std::string_view key = " " ;
34- std::string_view type = " " ;
35- bool required = false ;
36- };
63+ constexpr size_t CalculateSize () const {
64+ return N;
65+ }
3766
38- // Add a member - returns NEW builder with added member
39- constexpr auto AddMember (std::string_view key, std::string_view type, bool required = false ) const {
40- Member m{key, type, required };
67+ template < size_t KeyLen, size_t TypeLen, bool isMemberRequired>
68+ constexpr auto AddMember (std::string_view key, std::string_view type) const {
69+ Member m{key, type, isMemberRequired };
4170 auto newMembers = std::tuple_cat (members, std::make_tuple (m));
42- return std::apply ([](auto &&...ms ) { return ObjectSchemaBuilder<Members..., Member>(ms...); }, newMembers);
71+
72+ constexpr size_t existingRequired = CountRequired<Members...>();
73+ constexpr size_t keyLengthInRequiredArray = existingRequired == 0
74+ ? QuotationMark.size () + KeyLen + QuotationMark.size () // First one just has quotes no need for commas
75+ : CommaQuotationMark.size () + KeyLen + QuotationMark.size (); // Subsequent required members need a comma and quotes around the key
76+ constexpr size_t reqContrib = isMemberRequired
77+ ? keyLengthInRequiredArray
78+ : 0 ; // Not required then does not go in the required array
79+
80+ constexpr size_t existingTotal = sizeof ...(Members);
81+ constexpr size_t propContrib = existingTotal == 0
82+ ? QuotationMark.size () + KeyLen + MemberTypeString.size () + TypeLen + EndMemberTypeString.size ()
83+ : CommaQuotationMark.size () + KeyLen + MemberTypeString.size () + TypeLen + EndMemberTypeString.size ();
84+
85+ constexpr size_t newN = N + reqContrib + propContrib;
86+
87+ return std::apply (
88+ [](auto &&...ms ) {
89+ // Build a new object builder to add the new member and update the total size we need for the schema.
90+ return ObjectSchemaBuilder<newN, Members..., Member>(ms...); },
91+ newMembers);
4392 }
4493
4594 template <typename ... Ms>
46- static constexpr size_t CalculateRequiredListSize (const Ms &...ms) {
47- // Required array
48- size_t size = 0 ;
49- bool firstRequired = true ;
50- (void )std::initializer_list<int >{(ms.required ? (firstRequired ? (size += QuotationMark.size () + ms.key .size () + QuotationMark.size (), firstRequired = false )
51- : (size += CommaQuotationMark.size () + ms.key .size () + QuotationMark.size (), 0 ))
52- : 0 )...};
53- return size;
95+ static constexpr size_t CountRequired () {
96+ if constexpr (sizeof ...(Ms) == 0 ) {
97+ return 0 ;
98+ } else {
99+ return (size_t {0 } + ... + (Ms{}.required ? 1 : 0 ));
100+ }
54101 }
55102
56103 constexpr void AppendRequiredMembers (const auto &aAppendFunc) const {
@@ -64,16 +111,6 @@ struct ObjectSchemaBuilder {
64111 members);
65112 }
66113
67- template <typename ... Ms>
68- static constexpr size_t CalculateTypeObjectSize (const Ms &...ms) {
69- // Properties
70- size_t size = 0 ;
71- bool first = true ;
72- [[maybe_unused]] auto dummy = {(first ? (size += QuotationMark.size () + ms.key .size () + MemberTypeString.size () + ms.type .size () + EndMemberTypeString.size (), first = false )
73- : (size += CommaQuotationMark.size () + ms.key .size () + MemberTypeString.size () + ms.type .size () + EndMemberTypeString.size (), 0 ))...};
74- return size;
75- }
76-
77114 constexpr void AppendTypeSchema (const auto &aAppendFunc) const {
78115 bool first = true ;
79116 std::apply ([&](auto &&...ms ) {
@@ -84,45 +121,14 @@ struct ObjectSchemaBuilder {
84121 members);
85122 }
86123
87- // Calculate size at compile time - helper that works with unpacked members
88- template <typename ... Ms>
89- static constexpr size_t CalculateSizeHelper (const Ms &...ms) {
90- size_t size = 0 ;
91- size += BeginningString.size ();
92- size += CalculateRequiredListSize (ms...);
93- size += PostRequiredMembersArrayString.size ();
94- size += CalculateTypeObjectSize (ms...);
95- size += EndString.size ();
96- size += 1 ; // \0 terminator
97-
98- // constexpr auto THE_MAGIC_NUMBER_BECAUSE_CALCUATION_ABOVE_IS_WRONG = 45;
99- // return size + THE_MAGIC_NUMBER_BECAUSE_CALCUATION_ABOVE_IS_WRONG;
100- return size;
101- }
102-
103- // Calculate size at compile time by unpacking the tuple
104- constexpr size_t CalculateSize () const {
105- return std::apply ([](const auto &...ms ) { return CalculateSizeHelper (ms...); }, members);
106- }
107-
108- // Calculate size from member types directly (compile-time)
109- static constexpr size_t SchemaSize = std::apply(
110- [](auto &&...ms) { return CalculateSizeHelper (ms...); },
111- std::tuple<Members...>{});
112-
113- constexpr size_t GetSchemaSize () { return SchemaSize; };
114-
115- // Build the schema string with exact size from template parameter
116- template <size_t N = SchemaSize>
124+ template <size_t Sz>
117125 constexpr auto BuildImpl () const {
118- std::array<char , N > result = {}; // Exact size at compile time
126+ std::array<char , Sz > result = {};
119127 size_t pos = 0 ;
120128
121129 auto append = [&](std::string_view str) {
122130 for (char c : str) {
123- if (pos < N - 1 ) { // Ensure we never exceed array bounds (leave one byte for null terminator)
124- result[pos++] = c;
125- }
131+ result[pos++] = c;
126132 }
127133 };
128134
@@ -132,27 +138,20 @@ struct ObjectSchemaBuilder {
132138 AppendTypeSchema (append);
133139 append (EndString);
134140
135- if (pos < N) { // Ensure null terminator is written, but not beyond bounds
136- result[pos] = ' \0 ' ;
137- }
138-
139141 return result;
140142 }
141143
142144 std::tuple<Members...> members;
143-
144- static constexpr std::string_view BeginningString = R"( {"type":"object","required":[)" ;
145- static constexpr std::string_view PostRequiredMembersArrayString = R"( ],"properties":{)" ;
146- static constexpr std::string_view MemberTypeString = R"( ":{"type":")" ;
147- static constexpr std::string_view EndMemberTypeString = R"( "})" ;
148- static constexpr std::string_view EndString = R"( }})" ;
149- static constexpr std::string_view Comma = " ," ;
150- static constexpr std::string_view Colon = " :" ;
151- static constexpr std::string_view QuotationMark = " \" " ;
152- static constexpr std::string_view CommaQuotationMark = " ,\" " ;
153145};
154146
155- // Helper to start building
147+ // Start with the base size as an empty object schema, then as each member
148+ // is added we construct a new builder with the updated size based on the passed member.
149+ static constexpr size_t BaseSize =
150+ ObjectSchemaBuilderBase::BeginningString.size() +
151+ ObjectSchemaBuilderBase::PostRequiredMembersArrayString.size() +
152+ ObjectSchemaBuilderBase::EndString.size() +
153+ 1; // null terminator
154+
156155constexpr auto ObjectSchema () {
157- return ObjectSchemaBuilder<>{};
158- }
156+ return ObjectSchemaBuilder<BaseSize >{};
157+ }
0 commit comments