Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve handling of defaults and serialization/deserialization of Thing Descriptions #189

Merged
merged 6 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading