| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| |
| import 'package:meta/meta.dart'; |
| |
| /// When an Entity does not support a given type. |
| class EntityTypeException implements Exception { |
| /// The unsuported type. |
| final String type; |
| |
| /// Create a new [EntityTypeException]. |
| EntityTypeException(this.type); |
| |
| @override |
| String toString() => |
| 'EntityTypeError: type "$type" is not available for Entity'; |
| } |
| |
| /// A [Codec] used for handling the the automatic translation of to and from and |
| /// Entity's source data to the specified Dart type [T]. |
| class EntityCodec<T> extends Codec<T, String> { |
| /// The "type" this codec is associated with, similar to mime-type, see |
| /// [Entity#GetTypes](https://goo.gl/QJo3tW). |
| final String type; |
| |
| final _EntityEncoder<T> _encoder; |
| final _EntityDecoder<T> _decoder; |
| |
| /// Create a new [EntityCodec]. |
| EntityCodec({ |
| @required this.type, |
| @required _EncodeEntity<T> encode, |
| @required _DecodeEntity<T> decode, |
| }) : assert(type != null), |
| assert(type.isEmpty == false), |
| assert(encode != null), |
| assert(decode != null), |
| _encoder = _EntityEncoder<T>(encode), |
| _decoder = _EntityDecoder<T>(decode); |
| |
| @override |
| _EntityEncoder<T> get encoder => _encoder; |
| |
| @override |
| _EntityDecoder<T> get decoder => _decoder; |
| } |
| |
| typedef _EncodeEntity<T> = String Function(T value); |
| |
| class _EntityEncoder<T> extends Converter<T, String> { |
| final _EncodeEntity<T> encode; |
| |
| const _EntityEncoder(this.encode); |
| |
| @override |
| String convert(T source) { |
| return encode(source); |
| } |
| } |
| |
| typedef _DecodeEntity<T> = T Function(String data); |
| |
| class _EntityDecoder<T> extends Converter<String, T> { |
| final _DecodeEntity<T> decode; |
| |
| const _EntityDecoder(this.decode); |
| |
| @override |
| T convert(String data) => decode(data); |
| |
| @override |
| Stream<T> bind(Stream<String> source) { |
| EntityDecoderSink<T> map(EventSink<T> out) => |
| EntityDecoderSink<T>(out, this); |
| |
| return Stream<T>.eventTransformed(source, map); |
| } |
| } |
| |
| /// Entity data [String]s in, [T] out. |
| class EntityDecoderSink<T> extends EventSink<String> { |
| /// The [EventSink] that values are decoded into. Errors generated by the |
| /// decoder are also added to [out]. |
| final EventSink<T> out; |
| |
| /// The decoder to used to convert the source ([Stream<String>]) events. |
| final _EntityDecoder<T> decoder; |
| |
| /// Create an instance of [EntityDecoderSink], usually via the |
| /// [Stream#eventTransformed] constructor. |
| EntityDecoderSink(this.out, this.decoder); |
| |
| @override |
| void add(String data) { |
| try { |
| T value = decoder.decode(data); |
| out.add(value); |
| } on Object catch (err, stackTrace) { |
| addError(err, stackTrace); |
| } |
| } |
| |
| @override |
| void addError(Object e, [StackTrace s]) => out.addError(e, s); |
| |
| @override |
| void close() => out.close(); |
| } |