Line data Source code
1 : // SPDX-FileCopyrightText: 2013-2025 Paul Colby <git@colby.id.au>
2 : // SPDX-License-Identifier: LGPL-3.0-or-later
3 :
4 : /*!
5 : * \file
6 : * Defines the Shape and ShapePrivate classes.
7 : */
8 :
9 : #include <qtsmithy/shape.h>
10 : #include "shape_p.h"
11 :
12 : #include <QJsonArray>
13 :
14 : QTSMITHY_BEGIN_NAMESPACE
15 :
16 : /*!
17 : * \class Shape
18 : *
19 : * The Shape class provides a Qt representation of a Smithy semantic shape. That is, essentially a
20 : * tagged union or variant class, with some specialised conversions for native Qt types.
21 : *
22 : * \see https://awslabs.github.io/smithy/2.0/spec/shape.html#semantic-shape
23 : */
24 :
25 : /*!
26 : * Constructs a new, empty Smithy shape.
27 : */
28 0 : Shape::Shape() : d_ptr(new ShapePrivate(this))
29 0 : {
30 0 : Q_D(Shape);
31 0 : d->error = Error::NoError;
32 0 : d->type = Type::Undefined;
33 0 : }
34 :
35 0 : Shape::Shape(const QJsonObject &ast, const ShapeId &id) : d_ptr(new ShapePrivate(this))
36 0 : {
37 0 : Q_D(Shape);
38 0 : d->ast = ast;
39 0 : d->error = Error::NoError;
40 0 : d->id = id;
41 0 : d->type = ShapePrivate::getType(ast);
42 0 : if (d->type == Type::Undefined) {
43 0 : d->error = Error::UndefinedShapeType;
44 0 : }
45 :
46 : // Warn on any unsupported properties.
47 0 : const QStringList supportedProperties = ShapePrivate::supportedProperties(d->type);
48 0 : const QStringList keys = ast.keys();
49 0 : for (const QString &key: keys) {
50 0 : if (!supportedProperties.contains(key)) {
51 0 : qCWarning(d->lc).noquote() << tr("Ignoring unsupported Shape property: %1").arg(key);
52 0 : }
53 0 : }
54 :
55 : // Error on any missing required properties.
56 0 : const QStringList requiredProperties = ShapePrivate::requiredProperties(d->type);
57 0 : for (const QString &key: requiredProperties) {
58 0 : if (!keys.contains(key)) {
59 0 : qCritical(d->lc).noquote() << tr("Missing required Shape property: %1").arg(key);
60 0 : d->error = Error::MissingRequiredProperty;
61 0 : }
62 0 : }
63 :
64 : // Validate all present properties.
65 0 : for (auto iter = ast.constBegin(); iter != ast.constEnd(); ++iter) {
66 0 : if (!supportedProperties.contains(iter.key())) {
67 0 : continue;
68 0 : }
69 0 : if (!ShapePrivate::validateProperty(iter.key(), iter.value())) {
70 0 : if (d->error == Error::NoError) {
71 0 : d->error = Error::InvalidPropertyValue;
72 0 : }
73 0 : }
74 0 : }
75 0 : }
76 :
77 0 : Shape::Shape(Shape &&other) : d_ptr(new ShapePrivate(this))
78 0 : {
79 0 : Q_D(Shape);
80 0 : d->ast = std::move(other.d_ptr->ast);
81 0 : d->error = std::move(other.d_ptr->error);
82 0 : d->id = std::move(other.d_ptr->id);
83 0 : d->type = std::move(other.d_ptr->type);
84 0 : }
85 :
86 0 : Shape::Shape(const Shape &other) : d_ptr(new ShapePrivate(this))
87 0 : {
88 0 : Q_D(Shape);
89 0 : d->ast = other.d_ptr->ast;
90 0 : d->error = other.d_ptr->error;
91 0 : d->id = other.d_ptr->id;
92 0 : d->type = other.d_ptr->type;
93 0 : }
94 :
95 0 : Shape& Shape::operator=(const Shape &shape)
96 0 : {
97 0 : Q_D(Shape);
98 0 : d->ast = shape.d_ptr->ast;
99 0 : d->error = shape.d_ptr->error;
100 0 : d->id = shape.d_ptr->id;
101 0 : d->type = shape.d_ptr->type;
102 0 : return *this;
103 0 : }
104 :
105 0 : Shape& Shape::operator=(const Shape &&shape)
106 0 : {
107 0 : Q_D(Shape);
108 0 : d->ast = std::move(shape.d_ptr->ast);
109 0 : d->error = std::move(shape.d_ptr->error);
110 0 : d->id = std::move(shape.d_ptr->id);
111 0 : d->type = std::move(shape.d_ptr->type);
112 0 : return *this;
113 0 : }
114 :
115 : /*!
116 : * Destroys this Shape object.
117 : */
118 0 : Shape::~Shape()
119 0 : {
120 0 : delete d_ptr;
121 0 : }
122 :
123 0 : Shape::Error Shape::error() const
124 0 : {
125 0 : Q_D(const Shape);
126 0 : return d->error;
127 0 : }
128 :
129 0 : bool Shape::isValid() const
130 0 : {
131 0 : Q_D(const Shape);
132 0 : return ((d->error == Error::NoError) && (d->type != Type::Undefined));
133 0 : }
134 :
135 :
136 0 : ShapeId Shape::id() const
137 0 : {
138 0 : Q_D(const Shape);
139 0 : return d->id;
140 0 : }
141 :
142 0 : Shape::Type Shape::type() const
143 0 : {
144 0 : Q_D(const Shape);
145 0 : return d->type;
146 0 : }
147 :
148 0 : Shape::TraitsMap Shape::traits() const
149 0 : {
150 0 : Q_D(const Shape);
151 0 : return ShapePrivate::getTraitsMap(d->ast, QStringLiteral("traits"));
152 0 : }
153 :
154 0 : Shape::Member Shape::member() const
155 0 : {
156 0 : Q_D(const Shape);
157 0 : return ShapePrivate::getMember(d->ast, QStringLiteral("member"));
158 0 : }
159 :
160 0 : Shape::Member Shape::key() const
161 0 : {
162 0 : Q_D(const Shape);
163 0 : return ShapePrivate::getMember(d->ast, QStringLiteral("key"));
164 0 : }
165 :
166 0 : Shape::Member Shape::value() const
167 0 : {
168 0 : Q_D(const Shape);
169 0 : return ShapePrivate::getMember(d->ast, QStringLiteral("value"));
170 0 : }
171 :
172 0 : Shape::StringMemberMap Shape::members() const
173 0 : {
174 0 : Q_D(const Shape);
175 0 : return ShapePrivate::getStrMemberMap(d->ast, QStringLiteral("members"));
176 0 : }
177 :
178 0 : QString Shape::version() const
179 0 : {
180 0 : Q_D(const Shape);
181 0 : return ShapePrivate::getString(d->ast, QStringLiteral("version"));
182 0 : }
183 :
184 0 : Shape::ShapeReferences Shape::operations() const
185 0 : {
186 0 : Q_D(const Shape);
187 0 : return ShapePrivate::getShapeRefs(d->ast, QStringLiteral("operations"));
188 0 : }
189 :
190 0 : Shape::ShapeReferences Shape::resources() const {
191 0 : Q_D(const Shape);
192 0 : return ShapePrivate::getShapeRefs(d->ast, QStringLiteral("resources"));
193 0 : }
194 :
195 0 : Shape::ShapeReferences Shape::errors() const {
196 0 : Q_D(const Shape);
197 0 : return ShapePrivate::getShapeRefs(d->ast, QStringLiteral("errors"));
198 0 : }
199 :
200 0 : ShapeIdStringMap Shape::rename() const
201 0 : {
202 0 : Q_D(const Shape);
203 0 : return ShapePrivate::getShapeIdStrMap(d->ast, QStringLiteral("rename"));
204 0 : }
205 :
206 0 : Shape::StringShapeRefMap Shape::identifiers() const
207 0 : {
208 0 : Q_D(const Shape);
209 0 : return ShapePrivate::getStrShapeRefMap(d->ast, QStringLiteral("identifiers"));
210 0 : }
211 :
212 0 : Shape::StringShapeRefMap Shape::properties() const
213 0 : {
214 0 : Q_D(const Shape);
215 0 : return ShapePrivate::getStrShapeRefMap(d->ast, QStringLiteral("properties"));
216 0 : }
217 :
218 0 : Shape::ShapeReference Shape::create() const
219 0 : {
220 0 : Q_D(const Shape);
221 0 : return ShapePrivate::getShapeRef(d->ast, QStringLiteral("create"));
222 0 : }
223 :
224 0 : Shape::ShapeReference Shape::put() const
225 0 : {
226 0 : Q_D(const Shape);
227 0 : return ShapePrivate::getShapeRef(d->ast, QStringLiteral("put"));
228 0 : }
229 :
230 0 : Shape::ShapeReference Shape::read() const
231 0 : {
232 0 : Q_D(const Shape);
233 0 : return ShapePrivate::getShapeRef(d->ast, QStringLiteral("read"));
234 0 : }
235 :
236 0 : Shape::ShapeReference Shape::update() const
237 0 : {
238 0 : Q_D(const Shape);
239 0 : return ShapePrivate::getShapeRef(d->ast, QStringLiteral("update"));
240 0 : }
241 :
242 0 : Shape::ShapeReference Shape::Delete() const
243 0 : {
244 0 : Q_D(const Shape);
245 0 : return ShapePrivate::getShapeRef(d->ast, QStringLiteral("delete"));
246 0 : }
247 :
248 0 : Shape::ShapeReference Shape::list() const
249 0 : {
250 0 : Q_D(const Shape);
251 0 : return ShapePrivate::getShapeRef(d->ast, QStringLiteral("list"));
252 0 : }
253 :
254 0 : Shape::ShapeReferences Shape::collectionOperations() const
255 0 : {
256 0 : Q_D(const Shape);
257 0 : return ShapePrivate::getShapeRefs(d->ast, QStringLiteral("collectionOperations"));
258 0 : }
259 :
260 0 : Shape::ShapeReference Shape::input() const
261 0 : {
262 0 : Q_D(const Shape);
263 0 : return ShapePrivate::getShapeRef(d->ast, QStringLiteral("input"));
264 0 : }
265 :
266 0 : Shape::ShapeReference Shape::output() const
267 0 : {
268 0 : Q_D(const Shape);
269 0 : return ShapePrivate::getShapeRef(d->ast, QStringLiteral("output"));
270 0 : }
271 :
272 0 : Shape::ShapeReferences Shape::mixins() const
273 0 : {
274 0 : Q_D(const Shape);
275 0 : return ShapePrivate::getShapeRefs(d->ast, QStringLiteral("mixins"));
276 0 : }
277 :
278 0 : QJsonObject Shape::rawAst() const
279 0 : {
280 0 : Q_D(const Shape);
281 0 : return d->ast;
282 0 : }
283 :
284 : #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
285 0 : uint qHash(const Shape::ShapeReference &key, uint seed)
286 : #else
287 0 : size_t qHash(const Shape::ShapeReference &key, size_t seed)
288 : #endif
289 0 : {
290 0 : return ::qHash(key.target, seed);
291 0 : }
292 :
293 0 : bool operator==(const Shape::ShapeReference &lhs, const Shape::ShapeReference &rhs)
294 0 : {
295 0 : return lhs.target == rhs.target;
296 0 : }
297 :
298 : /*!
299 : * \cond internal
300 : * \class ShapePrivate
301 : *
302 : * The ShapePrivate class provides private implementation for Shape.
303 : */
304 :
305 : /*!
306 : * \internal
307 : * Constructs a new ShapePrivate object with public implementation \a q.
308 : */
309 0 : ShapePrivate::ShapePrivate(Shape * const q) : q_ptr(q)
310 0 : {
311 :
312 0 : }
313 :
314 0 : Shape::Type ShapePrivate::getType(const QJsonObject &ast)
315 0 : {
316 0 : const QJsonValue value = ast.value(QLatin1String("type"));
317 0 : if (value.isUndefined()) {
318 0 : qCCritical(lc).noquote() << tr("Shape has no type property");
319 0 : return Shape::Type::Undefined;
320 0 : }
321 0 : if (value.type() != QJsonValue::String) {
322 0 : qCCritical(lc).noquote() << tr("Shape type property is not a JSON string:") << value;
323 0 : return Shape::Type::Undefined;
324 0 : }
325 0 : return getType(value.toString());
326 0 : }
327 :
328 0 : Shape::Type ShapePrivate::getType(const QString &type)
329 0 : {
330 : // Simple Types
331 0 : if (type == QLatin1String("blob")) return Shape::Type::Blob;
332 0 : if (type == QLatin1String("boolean")) return Shape::Type::Boolean;
333 0 : if (type == QLatin1String("string")) return Shape::Type::String;
334 0 : if (type == QLatin1String("enum")) return Shape::Type::Enum;
335 0 : if (type == QLatin1String("byte")) return Shape::Type::Byte;
336 0 : if (type == QLatin1String("short")) return Shape::Type::Short;
337 0 : if (type == QLatin1String("integer")) return Shape::Type::Integer;
338 0 : if (type == QLatin1String("intEnum")) return Shape::Type::IntEnum;
339 0 : if (type == QLatin1String("long")) return Shape::Type::Long;
340 0 : if (type == QLatin1String("float")) return Shape::Type::Float;
341 0 : if (type == QLatin1String("double")) return Shape::Type::Double;
342 0 : if (type == QLatin1String("bigInteger")) return Shape::Type::BigInteger;
343 0 : if (type == QLatin1String("bigDecimal")) return Shape::Type::BigDecimal;
344 0 : if (type == QLatin1String("timestamp")) return Shape::Type::Timestamp;
345 0 : if (type == QLatin1String("document")) return Shape::Type::Document;
346 :
347 : // Aggregate Types
348 0 : if (type == QLatin1String("list")) return Shape::Type::List;
349 0 : if (type == QLatin1String("set")) return Shape::Type::Set;
350 0 : if (type == QLatin1String("map")) return Shape::Type::Map;
351 0 : if (type == QLatin1String("structure")) return Shape::Type::Structure;
352 0 : if (type == QLatin1String("union")) return Shape::Type::Union;
353 :
354 :
355 : // Service Types
356 0 : if (type == QLatin1String("service")) return Shape::Type::Service;
357 0 : if (type == QLatin1String("operation")) return Shape::Type::Operation;
358 0 : if (type == QLatin1String("resource")) return Shape::Type::Resource;
359 :
360 : // Special Types
361 0 : if (type == QLatin1String("apply")) return Shape::Type::Apply;
362 :
363 0 : qCWarning(lc).noquote() << tr("Unknown shape type: %1").arg(type);
364 0 : return Shape::Type::Undefined;
365 0 : }
366 :
367 0 : Shape::Member ShapePrivate::getMember(const QJsonObject &ast, const QString &name)
368 0 : {
369 0 : Shape::Member member;
370 0 : Q_UNUSED(ast);
371 0 : Q_UNUSED(name);
372 0 : Q_UNIMPLEMENTED();
373 0 : return member;
374 0 : }
375 :
376 0 : ShapeIdStringMap ShapePrivate::getShapeIdStrMap(const QJsonObject &ast, const QString &name)
377 0 : {
378 0 : ShapeIdStringMap shapeIdMap;
379 0 : Q_UNUSED(ast);
380 0 : Q_UNUSED(name);
381 0 : Q_UNIMPLEMENTED();
382 0 : return shapeIdMap;
383 0 : }
384 :
385 0 : Shape::ShapeReference ShapePrivate::getShapeRef(const QJsonObject &ast, const QString &name)
386 0 : {
387 0 : const auto iter = ast.constFind(name);
388 0 : if (iter == ast.constEnd()) {
389 0 : return Shape::ShapeReference{};
390 0 : }
391 0 : const QJsonObject shapeRefObj = iter->toObject();
392 0 : return Shape::ShapeReference{ shapeRefObj.value(QLatin1String("target")).toString() };
393 0 : }
394 :
395 0 : Shape::ShapeReferences ShapePrivate::getShapeRefs(const QJsonObject &ast, const QString &name)
396 0 : {
397 0 : const auto iter = ast.constFind(name);
398 0 : if (iter == ast.constEnd()) {
399 0 : return Shape::ShapeReferences{};
400 0 : }
401 0 : const QJsonArray shapeRefsArray = iter->toArray();
402 0 : Shape::ShapeReferences shapeRefs;
403 0 : for (const QJsonValue &shapeRef: shapeRefsArray) {
404 0 : shapeRefs.insert(Shape::ShapeReference{
405 0 : shapeRef.toObject().value(QLatin1String("target")).toString()
406 0 : });
407 0 : }
408 0 : return shapeRefs;
409 0 : }
410 :
411 0 : Shape::StringMemberMap ShapePrivate::getStrMemberMap(const QJsonObject &ast, const QString &name)
412 0 : {
413 0 : Shape::StringMemberMap memberMap;
414 0 : Q_UNUSED(ast);
415 0 : Q_UNUSED(name);
416 0 : Q_UNIMPLEMENTED();
417 0 : return memberMap;
418 0 : }
419 :
420 0 : Shape::StringShapeRefMap ShapePrivate::getStrShapeRefMap(const QJsonObject &ast, const QString &name)
421 0 : {
422 0 : Shape::StringShapeRefMap shapeRefMap;
423 0 : Q_UNUSED(ast);
424 0 : Q_UNUSED(name);
425 0 : Q_UNIMPLEMENTED();
426 0 : return shapeRefMap;
427 0 : }
428 :
429 0 : Shape::TraitsMap ShapePrivate::getTraitsMap(const QJsonObject &ast, const QString &name)
430 0 : {
431 0 : Shape::TraitsMap traitsMap;
432 0 : const QJsonObject traits = ast.value(name).toObject();
433 0 : for (auto iter = traits.constBegin(); iter != traits.constEnd(); ++iter) {
434 0 : traitsMap.insert(iter.key(), iter.value());
435 0 : }
436 0 : return traitsMap;
437 0 : }
438 :
439 0 : QString ShapePrivate::getString(const QJsonObject &ast, const QString &name)
440 0 : {
441 0 : const auto iter = ast.constFind(name);
442 0 : return (iter == ast.constEnd()) ? QString() : iter->toString();
443 0 : }
444 :
445 0 : QStringList ShapePrivate::supportedProperties(const Shape::Type &type)
446 0 : {
447 0 : switch (type) {
448 0 : case Shape::Type::Undefined:
449 0 : return QStringList{};
450 :
451 : // Simple Types:
452 0 : case Shape::Type::Blob:
453 0 : case Shape::Type::Boolean:
454 0 : case Shape::Type::String:
455 : //case Shape::Type::Enum: <- Same as strucutre and union below.
456 0 : case Shape::Type::Byte:
457 0 : case Shape::Type::Short:
458 0 : case Shape::Type::Integer:
459 : //case Shape::Type::IntEnum: <- Same as strucutre and union below.
460 0 : case Shape::Type::Long:
461 0 : case Shape::Type::Float:
462 0 : case Shape::Type::Double:
463 0 : case Shape::Type::BigInteger:
464 0 : case Shape::Type::BigDecimal:
465 0 : case Shape::Type::Timestamp:
466 0 : case Shape::Type::Document:
467 0 : return QStringList{
468 0 : QLatin1String("type"),
469 0 : QLatin1String("traits"),
470 0 : QLatin1String("mixins"),
471 0 : };
472 0 : break;
473 :
474 : // Aggregate Types
475 0 : case Shape::Type::List:
476 : //case Shape::Type::Set: <- Set is synonym for List.
477 0 : return QStringList{
478 0 : QLatin1String("type"),
479 0 : QLatin1String("traits"),
480 0 : QLatin1String("member"),
481 0 : QLatin1String("mixins"),
482 0 : };
483 0 : break;
484 0 : case Shape::Type::Map:
485 0 : return QStringList{
486 0 : QLatin1String("type"),
487 0 : QLatin1String("traits"),
488 0 : QLatin1String("key"),
489 0 : QLatin1String("value"),
490 0 : QLatin1String("mixins"),
491 0 : };
492 0 : break;
493 0 : case Shape::Type::Structure:
494 0 : case Shape::Type::Union:
495 0 : case Shape::Type::Enum:
496 0 : case Shape::Type::IntEnum:
497 0 : return QStringList{
498 0 : QLatin1String("type"),
499 0 : QLatin1String("traits"),
500 0 : QLatin1String("members"),
501 0 : QLatin1String("mixins"),
502 0 : };
503 :
504 : // Service Types
505 0 : case Shape::Type::Service:
506 0 : return QStringList{
507 0 : QLatin1String("type"),
508 0 : QLatin1String("traits"),
509 0 : QLatin1String("version"),
510 0 : QLatin1String("operations"),
511 0 : QLatin1String("resources"),
512 0 : QLatin1String("errors"),
513 0 : QLatin1String("rename"),
514 0 : QLatin1String("mixins"),
515 0 : };
516 0 : case Shape::Type::Operation:
517 0 : return QStringList{
518 0 : QLatin1String("type"),
519 0 : QLatin1String("traits"),
520 0 : QLatin1String("input"),
521 0 : QLatin1String("output"),
522 0 : QLatin1String("errors"),
523 0 : QLatin1String("mixins"),
524 0 : };
525 0 : case Shape::Type::Resource:
526 0 : return QStringList{
527 0 : QLatin1String("type"),
528 0 : QLatin1String("traits"),
529 0 : QLatin1String("identifiers"),
530 0 : QLatin1String("properties"),
531 0 : QLatin1String("create"),
532 0 : QLatin1String("put"),
533 0 : QLatin1String("read"),
534 0 : QLatin1String("update"),
535 0 : QLatin1String("delete"),
536 0 : QLatin1String("list"),
537 0 : QLatin1String("operations"),
538 0 : QLatin1String("collectionOperations"),
539 0 : QLatin1String("resources"),
540 0 : QLatin1String("mixins"),
541 0 : };
542 :
543 : // Special Types
544 0 : case Shape::Type::Apply:
545 0 : return QStringList{
546 0 : QLatin1String("type"),
547 0 : QLatin1String("traits"),
548 0 : };
549 0 : break;
550 0 : }
551 0 : qCWarning(lc).noquote() << tr("Unknown shape type: 0x%1").arg((int)type, 0, 16);
552 0 : return QStringList{};
553 0 : }
554 :
555 0 : QStringList ShapePrivate::requiredProperties(const Shape::Type &type)
556 0 : {
557 0 : switch (type) {
558 0 : case Shape::Type::List:
559 : //case Shape::Type::Set: <- Set is synonym for List.
560 0 : return QStringList{
561 0 : QLatin1String("type"),
562 0 : QLatin1String("member"),
563 0 : };
564 0 : break;
565 :
566 0 : case Shape::Type::Map:
567 0 : return QStringList{
568 0 : QLatin1String("type"),
569 0 : QLatin1String("key"),
570 0 : QLatin1String("value"),
571 0 : };
572 0 : break;
573 :
574 0 : case Shape::Type::Union:
575 0 : case Shape::Type::Enum:
576 0 : case Shape::Type::IntEnum:
577 0 : return QStringList{
578 0 : QLatin1String("type"),
579 0 : QLatin1String("members"),
580 0 : };
581 :
582 0 : case Shape::Type::Service:
583 0 : return QStringList{
584 0 : QLatin1String("type"),
585 0 : QLatin1String("version"),
586 0 : };
587 :
588 0 : case Shape::Type::Apply:
589 0 : return QStringList{
590 0 : QLatin1String("type"),
591 0 : QLatin1String("traits"),
592 0 : };
593 0 : break;
594 :
595 0 : default:
596 0 : return QStringList{
597 0 : QLatin1String("type"),
598 0 : };
599 0 : }
600 0 : }
601 :
602 0 : bool ShapePrivate::validateIdentifier(const QString &id)
603 0 : {
604 : // Lazily relying on the fact that ShapeId will validate the idenitifer regex pattern for now.
605 0 : const ShapeId shapeId(id);
606 0 : return ((shapeId.isValid()) && (!shapeId.hasNameSpace()) && (!shapeId.hasMemberName()));
607 0 : }
608 :
609 0 : bool ShapePrivate::validateProperty(const QString &name, const QJsonValue &value)
610 0 : {
611 : // Type string
612 0 : if (name == QLatin1String("type")) {
613 0 : if (!value.isString()) {
614 0 : qCCritical(lc).noquote() << tr("%1 property is not a JSON string").arg(name);
615 0 : return false;
616 0 : }
617 0 : return (getType(value.toString()) != Shape::Type::Undefined);
618 0 : }
619 :
620 : // TraitsMap
621 0 : else if (name == QLatin1String("traits")) {
622 0 : if (!value.isObject()) {
623 0 : qCCritical(lc).noquote() << tr("%1 property is not a JSON object").arg(name);
624 0 : return false;
625 0 : }
626 0 : const QJsonObject traits = value.toObject();
627 0 : for (auto iter = traits.constBegin(); iter != traits.constEnd(); ++iter) {
628 0 : const ShapeId id(iter.key());
629 0 : if (!id.isValid()) {
630 0 : qCCritical(lc).noquote() << tr("%1 property has trait with invalid shape ID %2")
631 0 : .arg(name, iter.key());
632 0 : return false;
633 0 : }
634 0 : if (id.isRelativeShapeId()) {
635 0 : qCCritical(lc).noquote() << tr("%1 property has trait with relative shape ID %2")
636 0 : .arg(name, iter.key());
637 0 : return false;
638 0 : }
639 : // It doesn't matter (at this point) what type iter->value() is.
640 0 : }
641 0 : }
642 :
643 : // Member (aka ShapeReference plus optional TraitsMap)
644 0 : else if ((name == QLatin1String("member")) ||
645 0 : (name == QLatin1String("key")) ||
646 0 : (name == QLatin1String("value")))
647 0 : {
648 0 : if (!value.isObject()) {
649 0 : qCCritical(lc).noquote() << tr("%1 property is not a JSON object").arg(name);
650 0 : return false;
651 0 : }
652 0 : if (!validateProperty(QStringLiteral("ShapeReference"), value)) {
653 0 : qCCritical(lc).noquote() << tr("%1 property is not valid ShapeReference").arg(name);
654 0 : return false;
655 0 : }
656 0 : const QJsonObject member = value.toObject();
657 0 : const auto traits = member.constFind(QLatin1String("traits")); // Optional.
658 0 : if ((traits != member.constEnd()) && (!validateProperty(traits.key(), traits.value()))) {
659 0 : qCCritical(lc).noquote() << tr("%1 property has invalid traits property").arg(name);
660 0 : return false;
661 0 : }
662 0 : }
663 :
664 : // Members (ie Set of Member instances)
665 0 : else if (name == QLatin1String("members")) {
666 0 : if (!value.isObject()) {
667 0 : qCCritical(lc).noquote() << tr("%1 property is not a JSON object").arg(name);
668 0 : return false;
669 0 : }
670 0 : QStringList names;
671 0 : const QJsonObject members = value.toObject();
672 0 : for (auto iter = members.constBegin(); iter != members.constEnd(); ++iter) {
673 0 : if (!validateIdentifier(iter.key())) {
674 0 : qCCritical(lc).noquote() << tr("%1 property has invalid member name %2")
675 0 : .arg(name, iter.key());
676 0 : return false;
677 0 : }
678 0 : if (names.contains(iter.key().toLower())) {
679 0 : qCCritical(lc).noquote() << tr("%1 property has non-unique member name %2")
680 0 : .arg(name, iter.key().toLower());
681 0 : return false;
682 0 : }
683 0 : names.append(iter.key().toLower());
684 0 : if (!validateProperty(QLatin1String("member"), iter.value())) {
685 0 : qCCritical(lc).noquote() << tr("%1 property has invalid value for %2 property")
686 0 : .arg(name, iter.key());
687 0 : return false;
688 0 : }
689 0 : }
690 0 : }
691 :
692 : // Plain string
693 0 : else if (name == QLatin1String("version")) {
694 0 : if (!value.isString()) {
695 0 : qCCritical(lc).noquote() << tr("%1 property is not a JSON string").arg(name);
696 0 : return false;
697 0 : }
698 : // No further validation required; the string is free-form.
699 0 : }
700 :
701 : // ShapeReferences
702 0 : else if ((name == QLatin1String("operations")) ||
703 0 : (name == QLatin1String("collectionOperations")) ||
704 0 : (name == QLatin1String("resources")) ||
705 0 : (name == QLatin1String("errors")) ||
706 0 : (name == QLatin1String("mixins"))) {
707 0 : if (!value.isArray()) {
708 0 : qCCritical(lc).noquote() << tr("%1 property is not a JSON array").arg(name);
709 0 : return false;
710 0 : }
711 0 : const QJsonArray shapeRefs = value.toArray();
712 0 : for (const QJsonValue &shapeRef: shapeRefs) {
713 0 : if (!validateProperty(QLatin1String("ShapeReference"), shapeRef)) {
714 0 : qCCritical(lc).noquote() << tr("%1 property has invalid entry").arg(name);
715 0 : }
716 0 : }
717 0 : }
718 :
719 : // ShapeIdStringMap
720 0 : else if (name == QLatin1String("rename")) {
721 0 : if (!value.isObject()) {
722 0 : qCCritical(lc).noquote() << tr("%1 property is not a JSON object").arg(name);
723 0 : return false;
724 0 : }
725 0 : const QJsonObject rename = value.toObject();
726 0 : for (auto iter = rename.constBegin(); iter != rename.constEnd(); ++iter) {
727 0 : const ShapeId shapeId(iter.key());
728 0 : if (!shapeId.isValid()) {
729 0 : qCCritical(lc).noquote() << tr("%1 property has invalid shape ID %2")
730 0 : .arg(name, iter.key());
731 0 : return false;
732 0 : }
733 0 : if (!iter.value().isString()) {
734 0 : qCCritical(lc).noquote() << tr("%1 property has shape ID %2 with invalid value")
735 0 : .arg(name, iter.key());
736 0 : return false;
737 0 : }
738 0 : const QString indentifier = iter.value().toString();
739 0 : if (validateIdentifier(indentifier)) {
740 0 : qCCritical(lc).noquote() << tr("%1 property has shape ID %2 with invalid identifier %3")
741 0 : .arg(name, iter.key(), indentifier);
742 0 : return false;
743 0 : }
744 0 : }
745 0 : }
746 :
747 : // ShapeReference
748 0 : else if ((name == QLatin1String("ShapeReference")) ||
749 0 : (name == QLatin1String("create")) ||
750 0 : (name == QLatin1String("put")) ||
751 0 : (name == QLatin1String("read")) ||
752 0 : (name == QLatin1String("update")) ||
753 0 : (name == QLatin1String("delete")) ||
754 0 : (name == QLatin1String("list")) ||
755 0 : (name == QLatin1String("input")) ||
756 0 : (name == QLatin1String("output"))) {
757 0 : if (!value.isObject()) {
758 0 : qCCritical(lc).noquote() << tr("%1 property is not a JSON object").arg(name);
759 0 : return false;
760 0 : }
761 0 : const QJsonObject shapeRef = value.toObject();
762 0 : const auto target = shapeRef.constFind(QLatin1String("target")); // Required.
763 0 : if (target == shapeRef.constEnd()) {
764 0 : qCCritical(lc).noquote() << tr("%1 property has no target property").arg(name);
765 0 : return false;
766 0 : }
767 0 : if (!target.value().isString()) {
768 0 : qCCritical(lc).noquote() << tr("%1 property has target value that not a JSON string")
769 0 : .arg(name);
770 0 : return false;
771 0 : }
772 0 : const QString shapeIdString = target.value().toString();
773 0 : const ShapeId shapeId(shapeIdString);
774 0 : if (!shapeId.isValid()) {
775 0 : qCCritical(lc).noquote() << tr("%1 property has target with invalid shape ID %2")
776 0 : .arg(name, shapeIdString);
777 0 : return false;
778 0 : }
779 0 : if (shapeId.isRelativeShapeId()) {
780 0 : qCCritical(lc).noquote() << tr("%1 property has target with relative shape ID %2")
781 0 : .arg(name, shapeIdString);
782 0 : return false;
783 0 : }
784 0 : }
785 :
786 : // StringShapeRefMap
787 0 : else if ((name == QLatin1String("identifiers")) ||
788 0 : (name == QLatin1String("properties"))) {
789 0 : if (!value.isObject()) {
790 0 : qCCritical(lc).noquote() << tr("%1 property is not a JSON object").arg(name);
791 0 : return false;
792 0 : }
793 0 : const QJsonObject identifiers = value.toObject();
794 0 : for (auto iter = identifiers.constBegin(); iter != identifiers.constEnd(); ++iter) {
795 0 : if (!validateIdentifier(iter.key())) {
796 0 : qCCritical(lc).noquote() << tr("%1 property has invalid member name %2")
797 0 : .arg(name, iter.key());
798 0 : return false;
799 0 : }
800 0 : if (!validateProperty(QLatin1String("ShapeReference"), iter.value())) {
801 0 : qCCritical(lc).noquote() << tr("%1 property has invalid value for %2 property")
802 0 : .arg(name, iter.key());
803 0 : return false;
804 0 : }
805 0 : }
806 0 : }
807 :
808 0 : else {
809 0 : qCWarning(lc).noquote() << tr("Validation of %1 property not yet implemented").arg(name);
810 0 : return false;
811 0 : }
812 0 : return true;
813 0 : }
814 :
815 : /// \endcond
816 :
817 : QTSMITHY_END_NAMESPACE
|