Skip to content

Commit a777d36

Browse files
author
MatthewColvin
committed
Rework Schema builder int NTTP schema threading size through as a top level template param
Just need to fix some kind of off by one error.
1 parent 2b362bc commit a777d36

4 files changed

Lines changed: 144 additions & 116 deletions

File tree

Platformio/lib/Device/Custom/RokuHttp.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ constexpr auto const RokuSchemaArray = RokuSchemaBuilder.Build();
2424

2525
static constexpr std::string_view BuiltRokuSchema(RokuSchemaArray.data(), RokuSchemaArray.size() - 1);
2626

27-
static constexpr auto sSize = RokuSchemaBuilder.SchemaSize;
28-
2927
auto RokuConfigSchemaRegistered = ActiveDeviceConfig::ConfigJsonValidator::Register(DeviceId::Roku, BuiltRokuSchema);
3028

3129
auto RokuCreationRegistered = DeviceFactory::RegisterDevice(

Platformio/lib/Device/Custom/RokuHttp.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
*/
1515
class RokuHttp : public IDevice {
1616
public:
17-
static constexpr auto const IpAddressKey = "ip_address";
18-
static constexpr auto const NameKey = "name";
17+
static constexpr char const IpAddressKey[] = "ip_address";
18+
static constexpr char const NameKey[] = "name";
1919
/**
2020
* @brief Constructor
2121
* @param name Display name for the Roku device

Platformio/lib/RapidJsonUtility/ObjectSchemaBuilder.hpp

Lines changed: 90 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,100 @@
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 {
1646
public:
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+
156155
constexpr auto ObjectSchema() {
157-
return ObjectSchemaBuilder<>{};
158-
}
156+
return ObjectSchemaBuilder<BaseSize>{};
157+
}

Platformio/test/test_AbstractedHW/test_ObjectSchemaBuilder.cpp

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
static constexpr auto schemaBuilderOne = ObjectSchema()
66
.Require("aTestKey", "string")
77
.Require("aTestIntKey", "integer");
8+
89
static constexpr const char *hardCodeSchemaMatchingOne = R"({
910
"type":"object",
1011
"required":["aTestKey","aTestIntKey"],
@@ -14,6 +15,25 @@ static constexpr const char *hardCodeSchemaMatchingOne = R"({
1415
}
1516
})";
1617

18+
static constexpr auto schemaBuilderTwo = ObjectSchema()
19+
.Require("aTestKey", "string")
20+
.Require("aTestIntKey", "integer")
21+
.Optional("aTestOptKey", "boolean")
22+
.Optional("aTestOptIntKey", "integer")
23+
.Require("aTestKey2", "string");
24+
25+
static constexpr const char *hardCodeSchemaMatchingTwo = R"({
26+
"type":"object",
27+
"required":["aTestKey","aTestIntKey","aTestKey2"],
28+
"properties":{
29+
"aTestKey":{"type":"string"},
30+
"aTestIntKey":{"type":"integer"},
31+
"aTestOptKey":{"type":"boolean"},
32+
"aTestOptIntKey":{"type":"integer"},
33+
"aTestKey2":{"type":"string"}
34+
}
35+
})";
36+
1737
// Simple test for ObjectSchemaBuilder functionality
1838
TEST(ObjectSchemaBuilderTest, BasicFunctionality) {
1939
// Test that we can create a schema with required and optional fields
@@ -28,23 +48,23 @@ TEST(ObjectSchemaBuilderTest, BasicFunctionality) {
2848

2949
// Test the size calculation works correctly
3050
TEST(ObjectSchemaBuilderTest, SizeCalculation) {
31-
auto schema = ObjectSchema()
32-
.Require("id", "string")
33-
.Build();
51+
auto basicSchema = ObjectSchema()
52+
.Require("id", "string")
53+
.Build();
3454

35-
size_t size = schema.size();
55+
size_t size = basicSchema.size();
3656
ASSERT_GT(size, 0);
3757
}
3858

3959
TEST(ObjectSchemaBuilderTest, CalculateRequiredListSizeTest) {
4060
// Strings keys will be quoted and comma-separated.
41-
constexpr ObjectSchemaBuilder<>::Member m1{"testKey", "string", true};
42-
constexpr auto expectedSize = ObjectSchemaBuilder<>::CalculateRequiredListSize(m1);
61+
constexpr ObjectSchemaBuilderBase::Member m1{"testKey", "string", true};
62+
constexpr auto expectedSize = ObjectSchemaBuilderBase::CalculateRequiredListSize(m1);
4363
ASSERT_EQ(expectedSize, 7 + 2);
4464
// (7)"testKey" + (2)quotes
4565

46-
constexpr ObjectSchemaBuilder<>::Member m2{"anotherKey", "integer", true};
47-
constexpr auto expectedSizeTwoMems = ObjectSchemaBuilder<>::CalculateRequiredListSize(m1, m2);
66+
constexpr ObjectSchemaBuilderBase::Member m2{"anotherKey", "integer", true};
67+
constexpr auto expectedSizeTwoMems = ObjectSchemaBuilderBase::CalculateRequiredListSize(m1, m2);
4868
ASSERT_EQ(expectedSizeTwoMems, 7 + 2 + 1 + 10 + 2);
4969
// (7)"testKey" + (2)quotes + (1)comma + (10)"anotherKey" + (2)quotes
5070
}
@@ -53,33 +73,44 @@ TEST(ObjectSchemaBuilderTest, CalculateTypeObjectSizeTest) {
5373
// Expected format for properties members in test:
5474
// "testKey":{"type":"string"},
5575
// "anotherKey":{"type":"integer"}
56-
constexpr ObjectSchemaBuilder<>::Member m1{"testKey", "string", true};
57-
constexpr ObjectSchemaBuilder<>::Member m2{"anotherKey", "integer", true};
58-
constexpr auto expectedSize = ObjectSchemaBuilder<>::CalculateTypeObjectSize(m1, m2);
76+
constexpr ObjectSchemaBuilderBase::Member m1{"testKey", "string", true};
77+
constexpr ObjectSchemaBuilderBase::Member m2{"anotherKey", "integer", true};
78+
constexpr auto expectedSize = ObjectSchemaBuilderBase::CalculateTypeObjectSize(m1, m2);
5979

6080
auto firstObject = R"("testKey":{"type":"string"})";
6181
auto secondObject = R"("anotherKey":{"type":"integer"})";
6282
// Expected size is the sum of the two objects plus a comma between them
6383
ASSERT_EQ(expectedSize, strlen(firstObject) + 1 + strlen(secondObject));
6484
}
6585

66-
TEST(ObjectSchemaBuilderTest, SchemaSizeTest) {
67-
std::string cleanSchema = hardCodeSchemaMatchingOne;
86+
void validateSizeCalculations(auto builder, const char *hardcoded) {
87+
std::string cleanSchema = hardcoded;
6888
cleanSchema.erase(std::remove_if(cleanSchema.begin(), cleanSchema.end(), ::isspace), cleanSchema.end());
69-
auto expectedSize = cleanSchema.length() + 1; // +1 for null terminator
89+
auto expectedSize = cleanSchema.length();
7090

7191
// Check we can calculate the schema size at compile time.
72-
constexpr auto calculatedSize = schemaBuilderOne.CalculateSize();
92+
constexpr auto calculatedSize = builder.CalculateSize();
7393
ASSERT_EQ(calculatedSize, expectedSize);
7494

75-
// Build the schema into a constexpr array and check its size matches the expected size.
76-
constexpr auto builtSchema = schemaBuilderOne.Build();
95+
// Check that the built schema size matches the expected size.
96+
// Cant be constexpr?
97+
auto builtSchema = builder.Build();
7798
ASSERT_EQ(builtSchema.size(), expectedSize);
7899
}
79100

80-
TEST(ObjectSchemaBuilderTest, FullSchemaBuildTest) {
81-
constexpr auto schema = schemaBuilderOne.Build();
82-
std::string cleanSchema = hardCodeSchemaMatchingOne;
101+
void validateSchemaFormat(auto builder, const char *hardcoded) {
102+
std::string cleanSchema = hardcoded;
83103
cleanSchema.erase(std::remove_if(cleanSchema.begin(), cleanSchema.end(), ::isspace), cleanSchema.end());
84-
ASSERT_STREQ(cleanSchema.c_str(), schema.data());
104+
std::string schemaStr(builder.Build().data(), builder.Build().size());
105+
ASSERT_STREQ(cleanSchema.c_str(), schemaStr.c_str());
106+
}
107+
108+
TEST(ObjectSchemaBuilderTest, SchemaOneTest) {
109+
validateSizeCalculations(schemaBuilderOne, hardCodeSchemaMatchingOne);
110+
validateSchemaFormat(schemaBuilderOne, hardCodeSchemaMatchingOne);
111+
}
112+
113+
TEST(ObjectSchemaBuilderTest, SchemaTwoTest) {
114+
validateSizeCalculations(schemaBuilderTwo, hardCodeSchemaMatchingTwo);
115+
validateSchemaFormat(schemaBuilderTwo, hardCodeSchemaMatchingTwo);
85116
}

0 commit comments

Comments
 (0)