• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wirenboard / wb-mqtt-serial / 2

29 Dec 2025 12:28PM UTC coverage: 76.817% (+4.0%) from 72.836%
2

Pull #1045

github

54aa0c
pgasheev
up changelog
Pull Request #1045: Fix firmware version in WB-M1W2 template

6873 of 9161 branches covered (75.02%)

12966 of 16879 relevant lines covered (76.82%)

1651.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.24
/src/config_schema_generator.cpp
1
#include "config_schema_generator.h"
2

3
#include "config_merge_template.h"
4
#include "expression_evaluator.h"
5
#include "json_common.h"
6

7
#include "subdevices_config/config_schema_generator.h"
8

9
using namespace WBMQTT::JSON;
10
using Expressions::TExpressionsCache;
11

12
namespace
13
{
14
    bool IsRequiredSetupRegister(const Json::Value& setupRegister)
28✔
15
    {
16
        return setupRegister.get("required", false).asBool();
28✔
17
    }
18

19
    //  {
20
    //      "type": "number",
21
    //      "minimum": MIN,
22
    //      "maximum": MAX,
23
    //      "enum": [ ... ]
24
    //  }
25
    Json::Value MakeParameterSchema(const Json::Value& setupRegister)
28✔
26
    {
27
        Json::Value r;
28✔
28
        r["type"] = "number";
28✔
29
        SetIfExists(r, "enum", setupRegister, "enum");
28✔
30
        SetIfExists(r, "minimum", setupRegister, "min");
28✔
31
        SetIfExists(r, "maximum", setupRegister, "max");
28✔
32
        return r;
28✔
33
    }
34

35
    //  {
36
    //      "allOf": [ {"$ref": "#/definitions/channelSettings"} ],
37
    //      "properties": {
38
    //          "name": {
39
    //              "type": "string",
40
    //              "enum": [ CHANNEL_NAME ]
41
    //          }
42
    //      },
43
    //      "required": ["name"]
44
    //  }
45
    Json::Value MakeTabChannelSchema(const Json::Value& channelTemplate)
248✔
46
    {
47
        Json::Value r;
248✔
48
        MakeArray("allOf", r).append(MakeObject("$ref", "#/definitions/channelSettings"));
248✔
49
        r["properties"]["name"] = MakeSingleValueProperty(channelTemplate["name"].asString());
248✔
50
        MakeArray("required", r).append("name");
248✔
51
        return r;
248✔
52
    }
53

54
    //  {
55
    //      "allOf": [
56
    //          {
57
    //              "not": {
58
    //                  "properties": {
59
    //                      "name": {
60
    //                          "type": "string",
61
    //                          "enum": [ CHANNEL_NAMES ]
62
    //                      }
63
    //                  }
64
    //              }
65
    //          },
66
    //          {
67
    //              "oneOf": [
68
    //                  { "$ref": CUSTOM_CHANNEL_SCHEMA },
69
    //                  We have old configs with channels missing in new template versions
70
    //                  Accept those configs and process errors by wb-mqtt-serial's code not by schema
71
    //                  This is not a good solution. Should be deleted after template versions implementation
72
    //                  {
73
    //                      "properties": {
74
    //                          "name": { "type": "string" },
75
    //                          "address": { "not": {} },
76
    //                          "consists_of": { "not": {} }
77
    //                      }
78
    //                  }
79
    //              ]
80
    //          }
81
    //      ]
82
    //  }
83
    Json::Value MakeCustomChannelsSchema(const std::vector<std::string>& names,
58✔
84
                                         const std::string& customChannelsSchemaRef)
85
    {
86
        Json::Value n;
116✔
87
        n["not"]["properties"]["name"]["type"] = "string";
58✔
88
        auto& en = MakeArray("enum", n["not"]["properties"]["name"]);
58✔
89
        for (const auto& name: names) {
306✔
90
            en.append(name);
248✔
91
        }
92

93
        Json::Value channelTypes;
116✔
94
        auto& oneOf = MakeArray("oneOf", channelTypes);
58✔
95
        oneOf.append(MakeObject("$ref", customChannelsSchemaRef));
58✔
96
        Json::Value oldChannels;
116✔
97
        oldChannels["properties"]["name"]["type"] = "string";
58✔
98
        oldChannels["properties"]["address"]["not"] = Json::Value(Json::objectValue);
58✔
99
        oldChannels["properties"]["consists_of"]["not"] = Json::Value(Json::objectValue);
58✔
100
        oneOf.append(std::move(oldChannels));
58✔
101

102
        Json::Value r;
58✔
103
        auto& allOf = MakeArray("allOf", r);
58✔
104
        allOf.append(std::move(n));
58✔
105
        allOf.append(std::move(channelTypes));
58✔
106
        return r;
116✔
107
    }
108

109
    //  {
110
    //      "type": "array",
111
    //      "items": {
112
    //          "oneOf": [
113
    //              CHANNELS_SCHEMAS,
114
    //              CUSTOM_CHANNELS_SCHEMA
115
    //          ]
116
    //      }
117
    //  }
118
    Json::Value MakeDeviceChannelsSchema(const Json::Value& channels, const std::string& customChannelsSchemaRef)
58✔
119
    {
120
        Json::Value r;
58✔
121
        std::vector<std::string> names;
116✔
122

123
        r["type"] = "array";
58✔
124
        auto& items = MakeArray("oneOf", r["items"]);
58✔
125
        for (const auto& channel: channels) {
306✔
126
            auto channelSchema = MakeTabChannelSchema(channel);
248✔
127
            items.append(channelSchema);
248✔
128
            names.push_back(channel["name"].asString());
248✔
129
        }
130
        if (!names.empty() && !customChannelsSchemaRef.empty()) {
58✔
131
            items.append(MakeCustomChannelsSchema(names, customChannelsSchemaRef));
58✔
132
        }
133
        return r;
116✔
134
    }
135

136
    void MakeDeviceParametersSchema(const Json::Value& config,
60✔
137
                                    Json::Value& properties,
138
                                    Json::Value& requiredArray,
139
                                    const Json::Value& deviceTemplate,
140
                                    TExpressionsCache& exprCache)
141
    {
142
        TJsonParams exprParams(config);
120✔
143
        if (deviceTemplate.isMember("parameters")) {
60✔
144
            const auto& params = deviceTemplate["parameters"];
60✔
145
            for (Json::ValueConstIterator it = params.begin(); it != params.end(); ++it) {
100✔
146
                auto name = params.isArray() ? (*it)["id"].asString() : it.name();
84✔
147
                if (CheckCondition(*it, exprParams, &exprCache)) {
42✔
148
                    if (properties.isMember(name)) {
30✔
149
                        throw std::runtime_error("Validation failed.\nError 1\n  context: <root>\n  desc: "
2✔
150
                                                 "duplicate definition of parameter \"" +
×
151
                                                 name + "\" in device template");
4✔
152
                    }
153
                    properties[name] = MakeParameterSchema(*it);
28✔
154
                    if (IsRequiredSetupRegister(*it)) {
28✔
155
                        requiredArray.append(name);
8✔
156
                    }
157
                }
158
            }
159
        }
160
    }
58✔
161

162
    //  {
163
    //      "type": "object",
164
    //      "allOf": [
165
    //          { "$ref": COMMON_DEVICE_SCHEMA }
166
    //      ],
167
    //      "properties": {
168
    //          "device_type": {
169
    //              "type": "string",
170
    //              "enum": [ DEVICE_TYPE ]
171
    //          },
172
    //          "parameter1": PARAMETER_SCHEMA,
173
    //          ...
174
    //          "channels": CHANNELS_SCHEMA
175
    //      },
176
    //      "required": ["device_type", "slave_id", "parameter1", ...]
177
    //  }
178
    Json::Value MakeSchemaForDeviceConfigValidation(const Json::Value& commonDeviceSchema,
60✔
179
                                                    const Json::Value& deviceConfig,
180
                                                    TDeviceTemplate& deviceTemplate,
181
                                                    TSerialDeviceFactory& deviceFactory,
182
                                                    TExpressionsCache& exprCache)
183
    {
184
        auto schema(commonDeviceSchema);
60✔
185
        auto protocolName = GetProtocolName(deviceTemplate.GetTemplate());
120✔
186

187
        auto& req = MakeArray("required", schema);
60✔
188
        req.append("device_type");
60✔
189
        if (!deviceFactory.GetProtocol(protocolName)->SupportsBroadcast()) {
60✔
190
            req.append("slave_id");
58✔
191
        }
192

193
        schema["properties"] = Json::Value(Json::objectValue);
60✔
194
        schema["properties"]["device_type"] = MakeSingleValueProperty(deviceTemplate.Type);
60✔
195

196
        if (deviceTemplate.GetTemplate().isMember("parameters")) {
60✔
197
            MakeDeviceParametersSchema(deviceConfig,
60✔
198
                                       schema["properties"],
199
                                       req,
200
                                       deviceTemplate.GetTemplate(),
201
                                       exprCache);
202
        }
203

204
        if (deviceTemplate.GetTemplate().isMember("channels")) {
58✔
205
            auto customChannelsSchemaRef = deviceFactory.GetCustomChannelSchemaRef(protocolName);
58✔
206
            schema["properties"]["channels"] =
58✔
207
                MakeDeviceChannelsSchema(deviceTemplate.GetTemplate()["channels"], customChannelsSchemaRef);
116✔
208
        }
209

210
        auto deviceSchemaRef = deviceFactory.GetCommonDeviceSchemaRef(protocolName);
116✔
211
        const auto NO_CHANNELS_SUFFIX = "_no_channels";
58✔
212
        if (!WBMQTT::StringHasSuffix(deviceSchemaRef, NO_CHANNELS_SUFFIX)) {
58✔
213
            deviceSchemaRef += NO_CHANNELS_SUFFIX;
58✔
214
        }
215
        MakeArray("allOf", schema).append(MakeObject("$ref", deviceSchemaRef));
58✔
216
        return schema;
116✔
217
    }
218

219
    std::string ReplaceSubstrings(std::string str, const std::string& pattern, const std::string& repl)
16✔
220
    {
221
        size_t index = 0;
16✔
222
        while (pattern.length()) {
124✔
223
            index = str.find(pattern, index);
124✔
224
            if (index == std::string::npos)
124✔
225
                break;
16✔
226
            str.replace(index, pattern.length(), repl);
108✔
227
            index += repl.length();
108✔
228
        }
229
        return str;
16✔
230
    }
231

232
    class TDeviceTypeValidator
233
    {
234
        const Json::Value& CommonDeviceSchema;
235
        TTemplateMap& Templates;
236
        TSerialDeviceFactory& DeviceFactory;
237
        TExpressionsCache ExprCache;
238

239
    public:
240
        TDeviceTypeValidator(const Json::Value& commonDeviceSchema,
180✔
241
                             TTemplateMap& templates,
242
                             TSerialDeviceFactory& deviceFactory)
243
            : CommonDeviceSchema(commonDeviceSchema),
180✔
244
              Templates(templates),
245
              DeviceFactory(deviceFactory)
180✔
246
        {}
180✔
247

248
        void Validate(const Json::Value& deviceConfig)
76✔
249
        {
250
            auto deviceTemplate = Templates.GetTemplate(deviceConfig["device_type"].asString());
152✔
251
            auto schema = deviceTemplate->WithSubdevices()
76✔
252
                              ? Subdevices::MakeSchemaForDeviceConfigValidation(CommonDeviceSchema,
253
                                                                                *deviceTemplate,
16✔
254
                                                                                DeviceFactory)
255
                              : MakeSchemaForDeviceConfigValidation(CommonDeviceSchema,
256
                                                                    deviceConfig,
257
                                                                    *deviceTemplate,
60✔
258
                                                                    DeviceFactory,
259
                                                                    ExprCache);
150✔
260

261
            ::Validate(deviceConfig, schema);
74✔
262
        }
62✔
263
    };
264

265
    class TProtocolValidator
266
    {
267
        // Use the same schema for validation and confed
268
        TProtocolConfedSchemasMap& Schemas;
269

270
        //! Device type to validator for custom devices declared with protocol property
271
        std::unordered_map<std::string, std::shared_ptr<TValidator>> validators;
272

273
    public:
274
        TProtocolValidator(TProtocolConfedSchemasMap& protocolSchemas): Schemas(protocolSchemas)
180✔
275
        {}
180✔
276

277
        void Validate(const Json::Value& deviceConfig)
314✔
278
        {
279
            auto protocol = deviceConfig.get("protocol", "modbus").asString();
630✔
280
            auto protocolIt = validators.find(protocol);
314✔
281
            if (protocolIt != validators.end()) {
314✔
282
                protocolIt->second->Validate(deviceConfig);
176✔
283
                return;
176✔
284
            }
285
            auto validator = std::make_shared<TValidator>(Schemas.GetSchema(protocol));
276✔
286
            validators.insert({protocol, validator});
138✔
287
            validator->Validate(deviceConfig);
138✔
288
        }
289
    };
290

291
}
292

293
void ValidateConfig(const Json::Value& config,
180✔
294
                    TSerialDeviceFactory& deviceFactory,
295
                    const Json::Value& commonDeviceSchema,
296
                    const Json::Value& portsSchema,
297
                    TTemplateMap& templates,
298
                    TProtocolConfedSchemasMap& protocolSchemas)
299
{
300
    Validate(config, portsSchema);
180✔
301

302
    TProtocolValidator protocolValidator(protocolSchemas);
360✔
303
    TDeviceTypeValidator deviceTypeValidator(commonDeviceSchema, templates, deviceFactory);
360✔
304
    size_t portIndex = 0;
180✔
305
    for (const auto& port: config["ports"]) {
372✔
306
        size_t deviceIndex = 0;
208✔
307
        for (const auto& device: port["devices"]) {
582✔
308
            try {
309
                if (device.isMember("device_type")) {
390✔
310
                    deviceTypeValidator.Validate(device);
76✔
311
                } else {
312
                    protocolValidator.Validate(device);
314✔
313
                }
314
            } catch (const std::runtime_error& e) {
32✔
315
                std::stringstream newContext;
32✔
316
                newContext << "<root>[ports][" << portIndex << "][devices][" << deviceIndex << "]";
16✔
317
                throw std::runtime_error(ReplaceSubstrings(e.what(), "<root>", newContext.str()));
16✔
318
            }
319
            ++deviceIndex;
374✔
320
        }
321
        ++portIndex;
192✔
322
    }
323
}
164✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc