Skip to content

Commit

Permalink
fixup! feat!: improve deserialization logic
Browse files Browse the repository at this point in the history
  • Loading branch information
JKRhb committed Jun 2, 2024
1 parent 8e73131 commit e921685
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 67 deletions.
9 changes: 7 additions & 2 deletions lib/src/core/definitions/data_schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,20 @@ class DataSchema {
Set<String>? parsedFields,
]) {
parsedFields = parsedFields ?? {};
final atType = json.parseArrayField<String>("@type", parsedFields);
final atType =
json.parseArrayField<String>("@type", parsedFields: parsedFields);
final title = json.parseField<String>("title", parsedFields);
final titles = json.parseMapField<String>("titles", parsedFields);
final description = json.parseField<String>("description", parsedFields);
final descriptions =
json.parseMapField<String>("descriptions", parsedFields);
final constant = json.parseField<Object>("constant", parsedFields);
final defaultValue = json.parseField<Object>("default", parsedFields);
final enumeration = json.parseField<List<Object?>>("enum", parsedFields);
final enumeration = json.parseArrayField<Object?>(
"enum",
parsedFields: parsedFields,
minimalSize: 1,
);
final readOnly = json.parseField<bool>("readOnly", parsedFields);
final writeOnly = json.parseField<bool>("writeOnly", parsedFields);
final format = json.parseField<String>("format", parsedFields);
Expand Down
98 changes: 68 additions & 30 deletions lib/src/core/definitions/extensions/json_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,16 @@ extension ParseField on Map<String, dynamic> {
///
/// If a [Set] of [parsedFields] is passed to this function, the field [name]
/// will added. This can be used for filtering when parsing additional fields.
List<Uri>? parseUriArrayField(String name, [Set<String>? parsedFields]) {
final fieldValue = parseArrayField<String>(name, parsedFields);
List<Uri>? parseUriArrayField(
String name, {
Set<String>? parsedFields,
int minimalSize = 0,
}) {
final fieldValue = parseArrayField<String>(
name,
parsedFields: parsedFields,
minimalSize: minimalSize,
);

if (fieldValue == null) {
return null;
Expand Down Expand Up @@ -182,30 +190,46 @@ extension ParseField on Map<String, dynamic> {
/// will be added. This can be used for filtering when parsing additional
/// fields.
List<T>? parseArrayField<T>(
String name, [
String name, {
Set<String>? parsedFields,
]) {
int minimalSize = 0,
}) {
final fieldValue = parseField(name, parsedFields);

if (fieldValue == null) {
return null;
}

if (fieldValue is T) {
return [fieldValue];
} else if (fieldValue is List<T>) {
return fieldValue;
final List<T> result;

if (fieldValue is List<T>) {
result = fieldValue;
} else if (fieldValue is T) {
result = [fieldValue];
} else if (fieldValue is List<dynamic>) {
final filteredArray = fieldValue.whereType<T>().toList(growable: false);

if (filteredArray.length == fieldValue.length) {
return filteredArray;
result = filteredArray;
} else {
throw FormatException(
"Expected $T or a List of $T, but found a List member with invalid "
"type",
);
}
} else {
throw FormatException(
"Expected $T or a List of $T, got ${fieldValue.runtimeType}",
);
}

throw FormatException(
"Expected $T or a List of $T, got ${fieldValue.runtimeType}",
);
if (result.length < minimalSize) {
throw const FormatException(
"Expected a non-empty array, but encountered an empty one.",
);
}

return result;
}

/// Parses a field with a given [name] that can contain either a single value
Expand All @@ -218,10 +242,15 @@ extension ParseField on Map<String, dynamic> {
/// will be added. This can be used for filtering when parsing additional
/// fields.
List<T> parseRequiredArrayField<T>(
String name, [
String name, {
Set<String>? parsedFields,
]) {
final result = parseArrayField<T>(name, parsedFields);
int minimalSize = 0,
}) {
final result = parseArrayField<T>(
name,
parsedFields: parsedFields,
minimalSize: minimalSize,
);

if (result == null) {
throw FormatException("Missing required field $name");
Expand Down Expand Up @@ -313,12 +342,14 @@ extension ParseField on Map<String, dynamic> {
PrefixMapping prefixMapping,
Set<String>? parsedFields,
) {
final fieldValue =
parseField<List<Map<String, dynamic>>>("forms", parsedFields);
final fieldValue = parseArrayField<Map<String, dynamic>>(
"forms",
parsedFields: parsedFields,
minimalSize: 1,
);

return fieldValue
?.whereType<Map<String, dynamic>>()
.map(
?.map(
(e) => Form.fromJson(
e,
prefixMapping,
Expand All @@ -340,13 +371,13 @@ extension ParseField on Map<String, dynamic> {
parsedFields,
);

if (forms != null) {
return forms;
if (forms == null) {
throw const FormatException(
'Missing "forms" member in InteractionAffordance',
);
}

throw const FormatException(
'Missing "forms" member in InteractionAffordance',
);
return forms;
}

/// Parses [Link]s contained in this JSON object.
Expand Down Expand Up @@ -502,9 +533,14 @@ extension ParseField on Map<String, dynamic> {
/// Processes this JSON value and tries to generate a [List] of
/// [OperationType]s from it.
List<OperationType>? parseOperationTypes(
Set<String>? parsedFields,
) {
final opArray = parseArrayField<String>("op", parsedFields);
Set<String>? parsedFields, {
int minimalSize = 0,
}) {
final opArray = parseArrayField<String>(
"op",
parsedFields: parsedFields,
minimalSize: minimalSize,
);

return opArray?.map(OperationType.fromString).toList();
}
Expand Down Expand Up @@ -532,11 +568,13 @@ extension ParseField on Map<String, dynamic> {
List<AdditionalExpectedResponse>? parseAdditionalExpectedResponse(
PrefixMapping prefixMapping,
String formContentType,
Set<String>? parsedFields,
) {
Set<String>? parsedFields, {
int minimalSize = 0,
}) {
final fieldValue = parseArrayField<Map<String, dynamic>>(
"additionalResponses",
parsedFields,
parsedFields: parsedFields,
minimalSize: minimalSize,
);

if (fieldValue == null) {
Expand Down
9 changes: 7 additions & 2 deletions lib/src/core/definitions/form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@ class Form {
final contentCoding =
json.parseField<String>("contentCoding", parsedFields);

final security = json.parseArrayField<String>("security", parsedFields);
final scopes = json.parseArrayField<String>("scopes", parsedFields);
final security = json.parseArrayField<String>(
"security",
parsedFields: parsedFields,
minimalSize: 1,
);
final scopes =
json.parseArrayField<String>("scopes", parsedFields: parsedFields);
final response = json.parseExpectedResponse(prefixMapping, parsedFields);

final additionalResponses = json.parseAdditionalExpectedResponse(
Expand Down
3 changes: 2 additions & 1 deletion lib/src/core/definitions/link.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class Link {
final rel = json.parseField<String>("rel", parsedFields);
final anchor = json.parseUriField("anchor", parsedFields);
final sizes = json.parseField<String>("sizes", parsedFields);
final hreflang = json.parseArrayField<String>("hreflang", parsedFields);
final hreflang =
json.parseArrayField<String>("hreflang", parsedFields: parsedFields);
final additionalFields =
json.parseAdditionalFields(prefixMapping, parsedFields);

Expand Down
3 changes: 2 additions & 1 deletion lib/src/core/definitions/security/ace_security_scheme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ final class AceSecurityScheme extends SecurityScheme {
final as = json.parseField<String>("ace:as", parsedFields);
final cnonce = json.parseField<bool>("ace:cnonce", parsedFields);
final audience = json.parseField<String>("ace:audience", parsedFields);
final scopes = json.parseArrayField<String>("ace:scopes", parsedFields);
final scopes =
json.parseArrayField<String>("ace:scopes", parsedFields: parsedFields);

final additionalFields =
json.parseAdditionalFields(prefixMapping, parsedFields);
Expand Down
23 changes: 21 additions & 2 deletions lib/src/core/definitions/security/combo_security_scheme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//
// SPDX-License-Identifier: BSD-3-Clause

import "package:collection/collection.dart";
import "package:curie/curie.dart";

import "../extensions/json_parser.dart";
Expand Down Expand Up @@ -38,8 +39,26 @@ final class ComboSecurityScheme extends SecurityScheme {
final jsonLdType = json.parseArrayField<String>("@type");
final proxy = json.parseUriField("proxy", parsedFields);

final oneOf = json.parseArrayField<String>("oneOf", parsedFields);
final allOf = json.parseArrayField<String>("allOf", parsedFields);
final oneOf = json.parseArrayField<String>(
"oneOf",
parsedFields: parsedFields,
minimalSize: 2,
);
final allOf = json.parseArrayField<String>(
"allOf",
parsedFields: parsedFields,
minimalSize: 2,
);

final count =
[oneOf, allOf].whereNotNull().fold(0, (previous, _) => previous + 1);

if (count != 1) {
throw FormatException(
"Expected exactly one of allOf or oneOf to be "
"defined, but $count were given.",
);
}

final additionalFields =
json.parseAdditionalFields(prefixMapping, parsedFields);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ final class OAuth2SecurityScheme extends SecurityScheme {
json.parseField<String>("authorization", parsedFields);
final token = json.parseField<String>("token", parsedFields);
final refresh = json.parseField<String>("refresh", parsedFields);
final scopes = json.parseArrayField<String>("scopes", parsedFields);
final scopes =
json.parseArrayField<String>("scopes", parsedFields: parsedFields);
final flow = json.parseRequiredField<String>("flow", parsedFields);

final additionalFields =
Expand Down
22 changes: 18 additions & 4 deletions lib/src/core/definitions/thing_description.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ class ThingDescription {
final context = json.parseContext(parsedFields);
final prefixMapping = context.prefixMapping;

final atType = json.parseArrayField<String>("@type", parsedFields);
final atType =
json.parseArrayField<String>("@type", parsedFields: parsedFields);
final title = json.parseRequiredField<String>("title", parsedFields);
final titles = json.parseMapField<String>("titles", parsedFields);
final description = json.parseField<String>("description", parsedFields);
Expand All @@ -71,21 +72,34 @@ class ThingDescription {
final base = json.parseUriField("base", parsedFields);
final id = json.parseField<String>("id", parsedFields);

final security =
json.parseRequiredArrayField<String>("security", parsedFields);
final security = json.parseRequiredArrayField<String>(
"security",
parsedFields: parsedFields,
);

final securityDefinitions =
json.parseSecurityDefinitions(prefixMapping, parsedFields) ?? {};

final forms = json.parseForms(prefixMapping, parsedFields);
// TODO: Move somewhere else
// TODO: Validate correct use of op-values
forms?.forEach((form) {
if (form.op == null) {
throw const FormatException('Missing "op" field in thing-level form.');
}
});

final properties = json.parseProperties(prefixMapping, parsedFields);
final actions = json.parseActions(prefixMapping, parsedFields);
final events = json.parseEvents(prefixMapping, parsedFields);

final links = json.parseLinks(prefixMapping, parsedFields);

final profile = json.parseUriArrayField("profile", parsedFields);
final profile = json.parseUriArrayField(
"profile",
parsedFields: parsedFields,
minimalSize: 1,
);
final schemaDefinitions = json.parseDataSchemaMapField(
"schemaDefinitions",
prefixMapping,
Expand Down
2 changes: 1 addition & 1 deletion lib/src/core/definitions/thing_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import "extensions/json_parser.dart";

/// Class representing a WoT Thing Model.
///
/// See W3C WoT Thing Description Specificition, [section 10][spec link].
/// See W3C WoT Thing Description Specification, [section 10][spec link].
///
/// [spec link]: https://w3c.github.io/wot-thing-description/#thing-model
class ThingModel {
Expand Down
42 changes: 19 additions & 23 deletions test/core/definitions_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -638,30 +638,26 @@ void main() {
);
});

test(
"Should throw FormatExceptions for empty affordance forms in TDs",
() {
final invalidThingDescription = {
"@context": ["https://www.w3.org/2022/wot/td/v1.1"],
"title": "Thingweb WoT Thing",
"security": ["nosec_sc"],
"securityDefinitions": {
"nosec_sc": {
"scheme": "nosec",
},
test("Should throw FormatExceptions for empty affordance forms in TDs", () {
final invalidThingDescription = {
"@context": ["https://www.w3.org/2022/wot/td/v1.1"],
"title": "Thingweb WoT Thing",
"security": ["nosec_sc"],
"securityDefinitions": {
"nosec_sc": {
"scheme": "nosec",
},
"actions": {
"testAction": {
"forms": [],
},
},
"actions": {
"testAction": {
"forms": [],
},
};
},
};

expect(
() => ThingDescription.fromJson(invalidThingDescription),
throwsA(isA<FormatException>()),
);
},
skip: true,
);
expect(
() => ThingDescription.fromJson(invalidThingDescription),
throwsA(isA<FormatException>()),
);
});
}

0 comments on commit e921685

Please sign in to comment.