Skip to content

Commit

Permalink
feat(core): add JSON schema validation of values
Browse files Browse the repository at this point in the history
  • Loading branch information
JKRhb committed Mar 21, 2022
1 parent 59e5a31 commit 529d6f4
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 1 deletion.
28 changes: 27 additions & 1 deletion lib/src/core/content_serdes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'dart:convert';
import 'dart:typed_data';

import 'package:http_parser/http_parser.dart';
import 'package:json_schema2/json_schema2.dart';

import '../definitions/data_schema.dart';
import 'codecs/cbor_codec.dart';
Expand All @@ -28,6 +29,16 @@ const jsonLdContentType = 'application/ld+json';
/// Defines `application/json` as the default content type.
const defaultContentType = jsonContentType;

/// Custom [Exception] that is thrown when Serialization or Deserialization
/// fails.
class ContentSerdesException implements Exception {
/// The error message of this [ContentSerdesException].
String? message;

/// Constructor.
ContentSerdesException(this.message);
}

/// Class providing serializing and deserializing capabilities.
///
// TODO(JKRhb): Decide if a class-based approach is the right way to go.
Expand Down Expand Up @@ -151,12 +162,25 @@ class ContentSerdes {
return _supportedCodecs[codecName];
}

void _validateValue(Object? value, DataSchema? dataSchema) {
final dataSchemaJson = dataSchema?.rawJson;
if (dataSchemaJson == null) {
return;
}
final schema = JsonSchema.createSchema(dataSchemaJson);
if (!schema.validate(value)) {
throw ContentSerdesException("JSON Schema validation failed.");
}
}

/// Converts an [Object] to a byte representation based on its [contentType].
///
/// A [dataSchema] can be passed for validating the input [value] before the
/// conversion.
Content valueToContent(
Object? value, DataSchema? dataSchema, String? contentType) {
_validateValue(value, dataSchema);

contentType ??= defaultContentType;

final parsedMediaType = MediaType.parse(contentType);
Expand Down Expand Up @@ -194,7 +218,9 @@ class ContentSerdes {

final codec = _getCodecFromMediaType(mimeType);
if (codec != null) {
return codec.bytesToValue(bytes, dataSchema, parameters);
final value = codec.bytesToValue(bytes, dataSchema, parameters);
_validateValue(value, dataSchema);
return value;
} else {
// TODO(JKRhb): Should unsupported data be returned as a String?
return utf8.decode(bytes.asUint8List());
Expand Down
4 changes: 4 additions & 0 deletions lib/src/definitions/data_schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,12 @@ class DataSchema {
/// or null.
String? type;

/// The original JSON object that was parsed when creating this [DataSchema].
Map<String, dynamic>? rawJson;

/// Creates a new [DataSchema] from a [json] object.
DataSchema.fromJson(Map<String, dynamic> json) {
parseDataSchemaJson(this, json);
rawJson = json;
}
}
4 changes: 4 additions & 0 deletions lib/src/definitions/interaction_affordances/property.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,16 @@ class Property extends InteractionAffordance implements DataSchema {
@override
bool? writeOnly;

@override
Map<String, dynamic>? rawJson;

/// Default constructor that creates a [Property] from a [List] of [forms].
Property(List<Form> forms) : super(forms);

/// Creates a new [Property] from a [json] object.
Property.fromJson(Map<String, dynamic> json) : super([]) {
parseAffordanceFields(json);
parseDataSchemaJson(this, json);
rawJson = json;
}
}

0 comments on commit 529d6f4

Please sign in to comment.