Skip to content

Commit

Permalink
feat: Implement AdditionalExpectedResponse class
Browse files Browse the repository at this point in the history
  • Loading branch information
JKRhb committed Jun 12, 2022
1 parent e86b823 commit 6a032f2
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 0 deletions.
90 changes: 90 additions & 0 deletions lib/src/definitions/additional_expected_response.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2021 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

import 'package:collection/collection.dart';

/// Communication metadata describing the expected response message for the
/// primary response.
class AdditionalExpectedResponse {
/// The [contentType] of this [AdditionalExpectedResponse] object.
final String contentType;

bool? _success;

/// Signals if an additional response should not be considered an error.
bool get success => _success ?? false;

String? _schema;

/// Used to define the output data schema for an additional response if it
/// differs from the default output data schema.
///
/// Rather than a `DataSchema` object, the name of a previous definition given
/// in a `schemaDefinitions` map must be used.
String? get schema => _schema;

/// Any other additional field will be included in this [Map].
final Map<String, dynamic> additionalFields = <String, dynamic>{};

/// Constructs a new [AdditionalExpectedResponse] object from a [contentType].
AdditionalExpectedResponse(
this.contentType, {
String? schema,
bool? success,
}) : _success = success,
_schema = schema;

static String? _parseContentType(dynamic contentType) {
if (contentType is! String) {
return null;
}
return contentType;
}

/// Creates an [AdditionalExpectedResponse] from a [json] object.
AdditionalExpectedResponse.fromJson(
Map<String, dynamic> json, String formContentType)
: contentType =
_parseContentType(json["contentType"]) ?? formContentType {
const parsedFields = ["contentType", "schema", "success"];

final dynamic success = json["success"];
if (success is bool) {
_success = success;
}

final dynamic schema = json["schema"];
if (schema is String) {
_schema = schema;
}

for (final entry in json.entries) {
final key = entry.key;
if (parsedFields.contains(key)) {
continue;
}

additionalFields[key] = entry.value;
}
}

@override
bool operator ==(Object? other) {
if (other is! AdditionalExpectedResponse) {
return false;
}

return other.success == success &&
other.schema == schema &&
other.contentType == contentType &&
MapEquality<String, dynamic>()
.equals(other.additionalFields, additionalFields);
}

@override
int get hashCode =>
Object.hash(success, schema, contentType, additionalFields);
}
30 changes: 30 additions & 0 deletions lib/src/definitions/form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:curie/curie.dart';
import 'package:json_schema3/json_schema3.dart';
import 'package:uri/uri.dart';

import 'additional_expected_response.dart';
import 'expected_response.dart';
import 'interaction_affordances/action.dart';
import 'interaction_affordances/event.dart';
Expand Down Expand Up @@ -52,6 +53,14 @@ class Form {
/// The [response] a consumer can expect from interacting with this [Form].
ExpectedResponse? response;

/// This optional term can be used if additional expected responses are
/// possible, e.g. for error reporting.
///
/// Each additional response needs to be distinguished from others in some way
/// (for example, by specifying a protocol-specific error code), and may also
/// have its own data schema.
List<AdditionalExpectedResponse>? additionalResponses;

/// Additional fields collected during the parsing of a JSON object.
final Map<String, dynamic> additionalFields = <String, dynamic>{};

Expand Down Expand Up @@ -85,6 +94,7 @@ class Form {
List<String>? op,
this.scopes,
this.response,
this.additionalResponses,
Map<String, dynamic>? additionalFields})
: resolvedHref = _expandHref(href, interactionAffordance),
securityDefinitions =
Expand Down Expand Up @@ -181,6 +191,25 @@ class Form {
}
}

List<AdditionalExpectedResponse>? additionalResponses;
if (json["additionalResponses"] != null) {
final dynamic jsonResponse =
_getJsonValue(json, "additionalResponses", parsedJsonFields);
if (jsonResponse is Map<String, dynamic>) {
additionalResponses = [
AdditionalExpectedResponse.fromJson(jsonResponse, contentType)
];
} else if (jsonResponse is List<dynamic>) {
additionalResponses = [];
for (final entry in jsonResponse) {
if (entry is Map<String, dynamic>) {
additionalResponses
.add(AdditionalExpectedResponse.fromJson(entry, contentType));
}
}
}
}

final additionalFields = _parseAdditionalFields(json, parsedJsonFields,
interactionAffordance.thingDescription.prefixMapping);

Expand All @@ -191,6 +220,7 @@ class Form {
scopes: scopes,
security: security,
response: response,
additionalResponses: additionalResponses,
additionalFields: additionalFields);
}

Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ dependencies:
uri: ^1.0.0
uuid: ^3.0.5
curie: ^0.1.0
collection: ^1.16.0
39 changes: 39 additions & 0 deletions test/core/definitions_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import 'dart:convert';

import 'package:dart_wot/dart_wot.dart';
import 'package:dart_wot/src/definitions/additional_expected_response.dart';
import 'package:dart_wot/src/definitions/context_entry.dart';
import 'package:dart_wot/src/definitions/expected_response.dart';
import 'package:dart_wot/src/definitions/interaction_affordances/property.dart';
Expand Down Expand Up @@ -78,6 +79,10 @@ void main() {
"response": {
"contentType": "application/json"
},
"additionalResponses": {
"success": false,
"schema": "hallo"
},
"op": ["writeproperty", "readproperty"],
"test": "test"
}""");
Expand All @@ -92,6 +97,10 @@ void main() {
form3.op, [OperationType.writeproperty, OperationType.readproperty]);
expect(form3.scopes, ["test1", "test2"]);
expect(form3.response?.contentType, "application/json");
expect(form3.additionalResponses, [
AdditionalExpectedResponse("application/json",
success: false, schema: "hallo")
]);
expect(form3.additionalFields, {"test": "test"});

final dynamic form4Json = jsonDecode("""
Expand All @@ -115,6 +124,36 @@ void main() {
() => Form.fromJson(
form5Json as Map<String, dynamic>, interactionAffordance),
throwsException);

final dynamic form6Json = jsonDecode("""
{
"href": "https://example.org",
"contentType": "application/cbor",
"additionalResponses": [
{
"schema": "hallo"
}, {
"contentType": "text/plain"
}
],
"op": ["writeproperty", "readproperty"],
"test": "test"
}""");

final form6 = Form.fromJson(
form6Json as Map<String, dynamic>, interactionAffordance);

final additionalResponses = form6.additionalResponses;

final additionalResponse1 = additionalResponses![0];
final additionalResponse2 = additionalResponses[1];

expect(additionalResponse1.contentType, "application/cbor");
expect(additionalResponse1.schema, "hallo");
expect(additionalResponse1.success, false);

expect(additionalResponse2.contentType, "text/plain");
expect(additionalResponse2.schema, null);
});

test("should correctly parse actions", () {
Expand Down

0 comments on commit 6a032f2

Please sign in to comment.