Skip to content

Commit

Permalink
Merge pull request #189 from eclipse-thingweb/thing-description-const…
Browse files Browse the repository at this point in the history
…ructor

Improve handling of defaults and serialization/deserialization of Thing Descriptions
  • Loading branch information
JKRhb authored Nov 1, 2024
2 parents 3765571 + e698015 commit 252ca33
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 24 deletions.
1 change: 1 addition & 0 deletions lib/src/core/definitions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ library definitions;
export "package:dcaf/dcaf.dart" show AuthServerRequestCreationHint;

export "definitions/additional_expected_response.dart";
export "definitions/context.dart";

export "definitions/credentials/ace_credentials.dart";
export "definitions/credentials/apikey_credentials.dart";
Expand Down
28 changes: 21 additions & 7 deletions lib/src/core/definitions/data_schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class DataSchema implements Serializable {
this.unit,
this.oneOf,
this.enumeration,
this.readOnly = false,
this.writeOnly = false,
bool? readOnly,
bool? writeOnly,
this.format,
this.type,
this.minimum,
Expand All @@ -50,7 +50,8 @@ class DataSchema implements Serializable {
this.contentEncoding,
this.contentMediaType,
this.additionalFields = const {},
});
}) : readOnly = readOnly ?? _defaultReadOnly,
writeOnly = writeOnly ?? _defaultWriteOnly;

// TODO: Consider creating separate classes for each data type.
// Also see https://github.com/w3c/wot-thing-description/issues/1390
Expand Down Expand Up @@ -142,6 +143,10 @@ class DataSchema implements Serializable {
);
}

static const _defaultWriteOnly = false;

static const _defaultReadOnly = false;

/// JSON-LD keyword (@type) to label the object with semantic tags (or types).
final List<String>? atType;

Expand Down Expand Up @@ -175,10 +180,10 @@ class DataSchema implements Serializable {
final List<Object?>? enumeration;

/// Indicates if a value is read only.
final bool? readOnly;
final bool readOnly;

/// Indicates if a value is write only.
final bool? writeOnly;
final bool writeOnly;

/// Allows validation based on a format pattern.
///
Expand Down Expand Up @@ -292,8 +297,6 @@ class DataSchema implements Serializable {
("const", constant),
("default", defaultValue),
("enum", enumeration),
("readOnly", readOnly),
("writeOnly", writeOnly),
("format", format),
("unit", unit),
("type", type),
Expand Down Expand Up @@ -332,6 +335,17 @@ class DataSchema implements Serializable {
result[key] = convertedValue;
}

final keyValuePairsWithDefault = [
("readOnly", readOnly, _defaultReadOnly),
("writeOnly", writeOnly, _defaultWriteOnly),
];

for (final (key, value, defaultValue) in keyValuePairsWithDefault) {
if (value != defaultValue) {
result[key] = value;
}
}

return result;
}
}
9 changes: 7 additions & 2 deletions lib/src/core/definitions/form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Form implements Serializable {
/// An [href] has to be provided. A [contentType] is optional.
Form(
this.href, {
this.contentType = "application/json",
this.contentType = _defaultContentType,
this.contentCoding,
this.subprotocol,
this.security,
Expand Down Expand Up @@ -86,6 +86,8 @@ class Form implements Serializable {
);
}

static const _defaultContentType = "application/json";

/// The [href] pointing to the resource.
///
/// Can be a relative or absolute URI.
Expand Down Expand Up @@ -134,7 +136,6 @@ class Form implements Serializable {
Map<String, dynamic> toJson() {
final result = {
"href": href.toString(),
"contentType": contentType,
...additionalFields,
};

Expand All @@ -148,6 +149,10 @@ class Form implements Serializable {
op.map((opValue) => opValue.toString()).toList(growable: false);
}

if (contentType != _defaultContentType) {
result["contentType"] = contentType;
}

if (contentCoding != null) {
result["contentCoding"] = contentCoding;
}
Expand Down
33 changes: 28 additions & 5 deletions lib/src/core/definitions/interaction_affordances/property.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Property extends InteractionAffordance implements DataSchema {
super.uriVariables,
super.additionalFields,
required this.dataSchema,
this.observable = false,
this.observable = _defaultObservableValue,
});

/// Creates a new [Property] from a [json] object.
Expand Down Expand Up @@ -51,6 +51,22 @@ class Property extends InteractionAffordance implements DataSchema {
return property;
}

@override
Map<String, dynamic> toJson() {
final result = {
...super.toJson(),
...dataSchema.toJson(),
};

if (observable != _defaultObservableValue) {
result["observable"] = observable;
}

return result;
}

static const _defaultObservableValue = false;

/// The internal [DataSchema] this property is based on.
final DataSchema dataSchema;

Expand Down Expand Up @@ -85,7 +101,7 @@ class Property extends InteractionAffordance implements DataSchema {
List<DataSchema>? get oneOf => dataSchema.oneOf;

@override
bool get readOnly => dataSchema.readOnly ?? false;
bool get readOnly => dataSchema.readOnly;

@override
String? get type => dataSchema.type;
Expand All @@ -94,7 +110,7 @@ class Property extends InteractionAffordance implements DataSchema {
String? get unit => dataSchema.unit;

@override
bool get writeOnly => dataSchema.writeOnly ?? false;
bool get writeOnly => dataSchema.writeOnly;

@override
String? get contentEncoding => dataSchema.contentEncoding;
Expand Down Expand Up @@ -148,8 +164,15 @@ class Property extends InteractionAffordance implements DataSchema {

@override
Map<String, dynamic> get additionalFields {
final additionalDataSchemaFields = dataSchema.additionalFields.entries
.where((entry) => super.additionalFields.containsKey(entry.key));
final additionalDataSchemaFields =
dataSchema.additionalFields.entries.where(
(entry) => [
...super.additionalFields.keys,
"observable",
].contains(
entry.key,
),
);

return Map.fromEntries(additionalDataSchemaFields);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/src/core/definitions/thing_description.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import "version_info.dart";
@immutable
class ThingDescription {
/// Creates a new Thing Description object.
const ThingDescription._({
const ThingDescription({
required this.context,
required this.title,
required this.security,
Expand Down Expand Up @@ -114,7 +114,7 @@ class ThingDescription {
final additionalFields =
json.parseAdditionalFields(prefixMapping, parsedFields);

return ThingDescription._(
return ThingDescription(
context: context,
title: title,
titles: titles,
Expand Down
1 change: 0 additions & 1 deletion lib/src/core/implementation/servient.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import "package:meta/meta.dart";
import "package:uuid/uuid.dart";

import "../definitions.dart";
import "../definitions/context.dart";
import "../exceptions.dart";
import "../protocol_interfaces.dart";
import "../scripting_api.dart" as scripting_api;
Expand Down
5 changes: 0 additions & 5 deletions test/core/definitions/serialization_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ void main() {
"op": [
"readmultipleproperties",
],
// TODO: Should defaults actually be set?
"contentType": "application/json",
},
],
"properties": {},
Expand Down Expand Up @@ -103,7 +101,6 @@ void main() {
"href": "https://example.org",
"subprotocol": "foobar",
"contentCoding": "test",
"contentType": "application/json",
"security": ["test"],
"response": {
"contentType": "application/json",
Expand All @@ -123,7 +120,6 @@ void main() {
test("AugmentedForms", () async {
final formJson = {
"href": "https://example.org",
"contentType": "application/json",
};

final thingDescription = {
Expand Down Expand Up @@ -186,7 +182,6 @@ void main() {
"forms": [
{
"href": "https://example.org",
"contentType": "application/json",
}
],
};
Expand Down
1 change: 0 additions & 1 deletion test/core/definitions_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import "dart:convert";

import "package:curie/curie.dart";
import "package:dart_wot/core.dart";
import "package:dart_wot/src/core/definitions/context.dart";
import "package:dart_wot/src/core/definitions/extensions/json_parser.dart";
import "package:test/test.dart";

Expand Down
1 change: 0 additions & 1 deletion test/core/servient_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
// SPDX-License-Identifier: BSD-3-Clause

import "package:dart_wot/core.dart";
import "package:dart_wot/src/core/definitions/context.dart";
import "package:dart_wot/src/core/implementation/servient.dart";
import "package:test/test.dart";

Expand Down
68 changes: 68 additions & 0 deletions test/core/thing_description_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,74 @@ void main() {
expect(thingDescriptionJson, thingDescription.toJson());
});

test(
"be able to be created via its constructor and converted to a "
"Map<String, dynamic>", () {
const thingDescriptionJson = {
"@context": [
"https://www.w3.org/2022/wot/td/v1.1",
{"@language": "de"},
],
"title": "Test Thing",
"securityDefinitions": {
"nosec_sc": {"scheme": "nosec"},
},
"security": ["nosec_sc"],
"properties": {
"status": {
"type": "string",
"readOnly": true,
"observable": true,
"forms": [
{
"href": "https://example.org",
"contentType": "application/cbor",
}
],
},
},
};

final thingDescription = ThingDescription(
context: Context(
[
SingleContextEntry(
Uri.parse("https://www.w3.org/2022/wot/td/v1.1"),
),
const StringMapContextEntry(
"@language",
"de",
),
],
),
title: "Test Thing",
security: const [
"nosec_sc",
],
securityDefinitions: const {
"nosec_sc": NoSecurityScheme(),
},
properties: {
"status": Property(
forms: [
Form(
Uri.parse("https://example.org"),
contentType: "application/cbor",
),
],
observable: true,
dataSchema: const DataSchema(
type: "string",
readOnly: true,
writeOnly: false,
),
),
},
);

expect(thingDescriptionJson, thingDescription.toJson());
});

test("throw a FormatException when it is invalid during parsing", () {
const thingDescriptionJson = {
"@context": [
Expand Down

0 comments on commit 252ca33

Please sign in to comment.