From cb6e35e79296d07c9c0211d4f9b7a490743947aa Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Sat, 22 Jan 2022 00:41:29 +0100 Subject: [PATCH 1/2] feat: let TD parse links field --- lib/src/definitions/link.dart | 95 ++++++++++++++++++++++ lib/src/definitions/thing_description.dart | 16 ++++ 2 files changed, 111 insertions(+) create mode 100644 lib/src/definitions/link.dart diff --git a/lib/src/definitions/link.dart b/lib/src/definitions/link.dart new file mode 100644 index 00000000..6f4391d1 --- /dev/null +++ b/lib/src/definitions/link.dart @@ -0,0 +1,95 @@ +// Copyright 2022 The NAMIB Project Developers. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// SPDX-License-Identifier: BSD-3-Clause + +/// Represents an element of the `links` array in a Thing Description. +/// +/// A link can be viewed as a statement of the form "link context has a relation +/// type resource at link target", where the optional target attributes may +/// further describe the resource. +class Link { + /// Target IRI of a link or submission target of a form. + late final Uri href; + + /// Target attribute providing a hint indicating what the media type (see RFC + /// 2046) of the result of dereferencing the link should be. + String? type; + + /// A link relation type identifies the semantics of a link. + String? rel; + + /// Overrides the link context (by default the Thing itself identified by its + /// id) with the given URI or IRI. + Uri? anchor; + + /// Target attribute that specifies one or more sizes for a referenced icon. + /// + /// Only applicable for relation type "icon". The value pattern follows + /// {Height}x{Width} (e.g., "16x16", "16x16 32x32"). + String? sizes; + + final List _parsedJsonFields = []; + + /// Additional fields collected during the parsing of a JSON object. + final Map additionalFields = {}; + + /// Constructor. + Link( + String href, { + this.type, + this.rel, + String? anchor, + this.sizes, + Map? additionalFields, + }) : href = Uri.parse(href), + anchor = anchor != null ? Uri.parse(anchor) : null { + if (additionalFields != null) { + this.additionalFields.addAll(additionalFields); + } + } + + /// Creates a new [Link] from a [json] object. + Link.fromJson(Map json) { + // TODO(JKRhb): Check if this can be refactored + if (json["href"] is String) { + _parsedJsonFields.add("href"); + final hrefString = json["href"] as String; + href = Uri.parse(hrefString); + } else { + // [href] *must* be initialized. + throw ArgumentError("'href' field must exist as a string.", "formJson"); + } + + if (json["type"] is String) { + _parsedJsonFields.add("type"); + type = json["type"] as String; + } + + if (json["rel"] is String) { + _parsedJsonFields.add("rel"); + rel = json["rel"] as String; + } + + if (json["anchor"] is String) { + _parsedJsonFields.add("anchor"); + anchor = Uri.parse(json["anchor"] as String); + } + + if (json["sizes"] is String) { + _parsedJsonFields.add("sizes"); + sizes = json["sizes"] as String; + } + + _addAdditionalFields(json); + } + + void _addAdditionalFields(Map formJson) { + for (final entry in formJson.entries) { + if (!_parsedJsonFields.contains(entry.key)) { + additionalFields[entry.key] = entry.value; + } + } + } +} diff --git a/lib/src/definitions/thing_description.dart b/lib/src/definitions/thing_description.dart index 39a60f64..5cbb959c 100644 --- a/lib/src/definitions/thing_description.dart +++ b/lib/src/definitions/thing_description.dart @@ -10,6 +10,7 @@ import 'context_entry.dart'; import 'interaction_affordances/action.dart'; import 'interaction_affordances/event.dart'; import 'interaction_affordances/property.dart'; +import 'link.dart'; import 'security_scheme.dart'; import 'thing_model.dart'; @@ -47,6 +48,9 @@ class ThingDescription { /// A [Map] of [Event] Affordances. Map events = {}; + /// A [List] of [Link]s. + final List links = []; + /// The [base] address of this [ThingDescription]. Might be `null`. String? base; @@ -116,6 +120,10 @@ class ThingDescription { if (securityDefinitions is Map) { _parseSecurityDefinitions(securityDefinitions); } + final dynamic links = json["links"]; + if (links is List) { + _parseLinks(links); + } } // TODO(JKRhb): Refactor @@ -167,6 +175,14 @@ class ThingDescription { ThingDescription.fromThingModel(ThingModel thingModel) : rawThingModel = thingModel; + void _parseLinks(List json) { + for (final link in json) { + if (link is Map) { + links.add(Link.fromJson(link)); + } + } + } + void _parseProperties(Map json) { for (final property in json.entries) { if (property.value is Map) { From 8ddadfb43c605f12da9c3b755524c4b0ffc5740e Mon Sep 17 00:00:00 2001 From: Jan Romann Date: Sat, 22 Jan 2022 00:44:16 +0100 Subject: [PATCH 2/2] test: add test for links field --- test/dart_wot_test.dart | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/test/dart_wot_test.dart b/test/dart_wot_test.dart index 41a81e93..ccf28865 100644 --- a/test/dart_wot_test.dart +++ b/test/dart_wot_test.dart @@ -5,6 +5,7 @@ // SPDX-License-Identifier: BSD-3-Clause import 'package:dart_wot/dart_wot.dart'; +import 'package:dart_wot/src/definitions/link.dart'; import 'package:test/test.dart'; // TODO(JKRhb): Add proper tests @@ -38,7 +39,17 @@ void main() { } ] } - } + }, + "links": [ + { + "href": "https://example.org", + "rel": "test", + "anchor": "https://example.org", + "type": "test", + "sizes": "42", + "test": "test" + } + ] } '''; final parsedTd = ThingDescription(thingDescriptionJson); @@ -52,6 +63,34 @@ void main() { expect(firstContextEntry.value, "http://www.w3.org/ns/td"); expect(secondContextEntry.key, "@language"); expect(secondContextEntry.value, "de"); + + final parsedLink = parsedTd.links[0]; + expect(parsedLink.href, Uri.parse("https://example.org")); + expect(parsedLink.rel, "test"); + expect(parsedLink.anchor, Uri.parse("https://example.org")); + expect(parsedLink.type, "test"); + expect(parsedLink.sizes, "42"); + expect(parsedLink.additionalFields["test"], "test"); + }); + + test('Link Tests', () { + final link = Link("https://example.org", + type: "test", + rel: "test", + anchor: "https://example.org", + sizes: "42", + additionalFields: {"test": "test"}); + expect(link.href, Uri.parse("https://example.org")); + expect(link.rel, "test"); + expect(link.anchor, Uri.parse("https://example.org")); + expect(link.type, "test"); + expect(link.sizes, "42"); + expect(link.additionalFields["test"], "test"); + + final link2 = Link("https://example.org"); + expect(link2.href, Uri.parse("https://example.org")); + expect(link2.anchor, null); + expect(link2.additionalFields, {}); }); }); }