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

wirenboard / wb-mqtt-serial / 714

31 Oct 2025 05:56AM UTC coverage: 76.986% (+3.6%) from 73.348%
714

push

github

web-flow
Allow using domain names in RPC requests (#1010)

6860 of 9092 branches covered (75.45%)

12949 of 16820 relevant lines covered (76.99%)

750.66 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)
14✔
15
    {
16
        return setupRegister.get("required", false).asBool();
14✔
17
    }
18

19
    //  {
20
    //      "type": "number",
21
    //      "minimum": MIN,
22
    //      "maximum": MAX,
23
    //      "enum": [ ... ]
24
    //  }
25
    Json::Value MakeParameterSchema(const Json::Value& setupRegister)
14✔
26
    {
27
        Json::Value r;
14✔
28
        r["type"] = "number";
14✔
29
        SetIfExists(r, "enum", setupRegister, "enum");
14✔
30
        SetIfExists(r, "minimum", setupRegister, "min");
14✔
31
        SetIfExists(r, "maximum", setupRegister, "max");
14✔
32
        return r;
14✔
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)
124✔
46
    {
47
        Json::Value r;
124✔
48
        MakeArray("allOf", r).append(MakeObject("$ref", "#/definitions/channelSettings"));
124✔
49
        r["properties"]["name"] = MakeSingleValueProperty(channelTemplate["name"].asString());
124✔
50
        MakeArray("required", r).append("name");
124✔
51
        return r;
124✔
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,
29✔
84
                                         const std::string& customChannelsSchemaRef)
85
    {
86
        Json::Value n;
58✔
87
        n["not"]["properties"]["name"]["type"] = "string";
29✔
88
        auto& en = MakeArray("enum", n["not"]["properties"]["name"]);
29✔
89
        for (const auto& name: names) {
153✔
90
            en.append(name);
124✔
91
        }
92

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

102
        Json::Value r;
29✔
103
        auto& allOf = MakeArray("allOf", r);
29✔
104
        allOf.append(std::move(n));
29✔
105
        allOf.append(std::move(channelTypes));
29✔
106
        return r;
58✔
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)
29✔
119
    {
120
        Json::Value r;
29✔
121
        std::vector<std::string> names;
58✔
122

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

136
    void MakeDeviceParametersSchema(const Json::Value& config,
30✔
137
                                    Json::Value& properties,
138
                                    Json::Value& requiredArray,
139
                                    const Json::Value& deviceTemplate,
140
                                    TExpressionsCache& exprCache)
141
    {
142
        TJsonParams exprParams(config);
60✔
143
        if (deviceTemplate.isMember("parameters")) {
30✔
144
            const auto& params = deviceTemplate["parameters"];
30✔
145
            for (Json::ValueConstIterator it = params.begin(); it != params.end(); ++it) {
50✔
146
                auto name = params.isArray() ? (*it)["id"].asString() : it.name();
42✔
147
                if (CheckCondition(*it, exprParams, &exprCache)) {
21✔
148
                    if (properties.isMember(name)) {
15✔
149
                        throw std::runtime_error("Validation failed.\nError 1\n  context: <root>\n  desc: "
1✔
150
                                                 "duplicate definition of parameter \"" +
×
151
                                                 name + "\" in device template");
2✔
152
                    }
153
                    properties[name] = MakeParameterSchema(*it);
14✔
154
                    if (IsRequiredSetupRegister(*it)) {
14✔
155
                        requiredArray.append(name);
4✔
156
                    }
157
                }
158
            }
159
        }
160
    }
29✔
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,
30✔
179
                                                    const Json::Value& deviceConfig,
180
                                                    TDeviceTemplate& deviceTemplate,
181
                                                    TSerialDeviceFactory& deviceFactory,
182
                                                    TExpressionsCache& exprCache)
183
    {
184
        auto schema(commonDeviceSchema);
30✔
185
        auto protocolName = GetProtocolName(deviceTemplate.GetTemplate());
60✔
186

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

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

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

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

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

219
    std::string ReplaceSubstrings(std::string str, const std::string& pattern, const std::string& repl)
8✔
220
    {
221
        size_t index = 0;
8✔
222
        while (pattern.length()) {
62✔
223
            index = str.find(pattern, index);
62✔
224
            if (index == std::string::npos)
62✔
225
                break;
8✔
226
            str.replace(index, pattern.length(), repl);
54✔
227
            index += repl.length();
54✔
228
        }
229
        return str;
8✔
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,
90✔
241
                             TTemplateMap& templates,
242
                             TSerialDeviceFactory& deviceFactory)
243
            : CommonDeviceSchema(commonDeviceSchema),
90✔
244
              Templates(templates),
245
              DeviceFactory(deviceFactory)
90✔
246
        {}
90✔
247

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

261
            ::Validate(deviceConfig, schema);
37✔
262
        }
31✔
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)
90✔
275
        {}
90✔
276

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

291
}
292

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

302
    TProtocolValidator protocolValidator(protocolSchemas);
180✔
303
    TDeviceTypeValidator deviceTypeValidator(commonDeviceSchema, templates, deviceFactory);
180✔
304
    size_t portIndex = 0;
90✔
305
    for (const auto& port: config["ports"]) {
186✔
306
        size_t deviceIndex = 0;
104✔
307
        for (const auto& device: port["devices"]) {
291✔
308
            try {
309
                if (device.isMember("device_type")) {
195✔
310
                    deviceTypeValidator.Validate(device);
38✔
311
                } else {
312
                    protocolValidator.Validate(device);
157✔
313
                }
314
            } catch (const std::runtime_error& e) {
16✔
315
                std::stringstream newContext;
16✔
316
                newContext << "<root>[ports][" << portIndex << "][devices][" << deviceIndex << "]";
8✔
317
                throw std::runtime_error(ReplaceSubstrings(e.what(), "<root>", newContext.str()));
8✔
318
            }
319
            ++deviceIndex;
187✔
320
        }
321
        ++portIndex;
96✔
322
    }
323
}
82✔
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