LCOV - code coverage report
Current view: top level - src - model.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 78 82 95.1 %
Date: 2023-05-22 12:02:26 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:collection/collection.dart';
       2             : import 'package:tuple/tuple.dart';
       3             : import 'package:uuid/uuid.dart';
       4             : 
       5             : /// Getter function to retrieve a field from a [GenericModel]
       6             : typedef Getter<T> = T Function();
       7             : 
       8             : /// Setter function to set a field in a [GenericModel]
       9             : typedef Setter<T> = void Function(T value);
      10           4 : final bool Function(dynamic, dynamic) _equality =
      11           2 :     const DeepCollectionEquality(DefaultEquality<dynamic>()).equals;
      12             : 
      13             : /// Base Class to be extended by
      14             : abstract class GenericModel {
      15             :   /// The key for [type] in the result of [toMap]
      16             :   static const TYPE = 'type';
      17             : 
      18             :   /// The key for [id] in the result of [toMap]
      19             :   static const ID = 'id';
      20             : 
      21             :   /// Unique identifier for this model
      22             :   String? id;
      23             : 
      24             :   /// Used by [toMap] to generate the map
      25             :   late final Map<String, Tuple2<Getter<dynamic>, Setter<dynamic>>>
      26          10 :       getterSetterMap = _getterSetterMap;
      27           5 :   Map<String, Tuple2<Getter<dynamic>, Setter<dynamic>>> get _getterSetterMap {
      28           5 :     final getterSetterMap = getGetterSetterMap();
      29             :     assert(
      30          10 :       !getterSetterMap.containsKey(TYPE),
      31           0 :       '"$TYPE" is already used by GenericModel. Do not use it for extensions',
      32             :     );
      33             :     assert(
      34          10 :       !getterSetterMap.containsKey(ID),
      35           0 :       '"$ID" is already used by GenericModel. Do not use it for extensions',
      36             :     );
      37          30 :     getterSetterMap[ID] = Tuple2(() => id, (val) => id = val as String?);
      38             :     return getterSetterMap;
      39             :   }
      40             : 
      41             :   /// Converts this [GenericModel] into a Serializable Map.
      42             :   ///
      43             :   /// Can be coverted back into the Class version by calling [loadFromMap]
      44           4 :   Map<String, dynamic> toMap() {
      45           4 :     final map = <String, dynamic>{};
      46           8 :     map[TYPE] = type;
      47           8 :     getterSetterMap.keys
      48          28 :         .forEach((element) => map[element] = getterSetterMap[element]!.item1());
      49             : 
      50             :     return map;
      51             :   }
      52             : 
      53             :   /// Loads a Serializable map into the values of this [GenericModel]
      54             :   ///
      55             :   /// You can generate a value that can be passed into this by using [toMap]
      56             :   ///
      57             :   /// [respectType] will make a check to ensure that the TYPE entry is the same
      58             :   /// if true. This will throw an [ArgumentError] if they're not the same
      59           4 :   void loadFromMap(Map<String, dynamic> map, {bool respectType = true}) {
      60           4 :     if (respectType && map.containsKey(TYPE)) {
      61          12 :       if (map[TYPE] != type) {
      62           0 :         throw ArgumentError('Type in $map does not match $type');
      63             :       }
      64             :     }
      65           8 :     getterSetterMap.keys
      66          28 :         .forEach((element) => getterSetterMap[element]!.item2(map[element]));
      67             :   }
      68             : 
      69             :   /// Copies values from the given [model] into this model.
      70             :   ///
      71             :   /// If [allowDifferentTypes] is true, the method will continue even if the
      72             :   /// types and fields in [model] and myself are different. Otherwise,
      73             :   /// differences will be met with an error.
      74             :   ///
      75             :   /// [onlyFields] and [exceptFields] can be used to limit the fields that are
      76             :   /// copied. The two are mutually exclusive and an error will be thrown if both
      77             :   /// are specified.
      78           4 :   void copy<T extends GenericModel>(
      79             :     T model, {
      80             :     bool allowDifferentTypes = false,
      81             :     bool copyId = true,
      82             :     Iterable<String>? onlyFields,
      83             :     Iterable<String>? exceptFields,
      84             :   }) {
      85             :     if (!allowDifferentTypes) {
      86             :       assert(
      87          12 :         type == model.type,
      88           0 :         'Types do not match! ("$type" and "${model.type}")',
      89             :       );
      90             :     }
      91           4 :     fieldsToEvaluate(onlyFields, exceptFields)
      92           8 :         .where((element) {
      93           4 :           if (element == ID) {
      94             :             return copyId;
      95             :           }
      96             :           return true;
      97             :         })
      98          20 :         .where((element) => model.getterSetterMap.keys.contains(element))
      99           8 :         .forEach((element) {
     100           8 :           getterSetterMap[element]!
     101          24 :               .item2(model.getterSetterMap[element]!.item1());
     102             :         });
     103             :   }
     104             : 
     105             :   /// Returns whether the given [model] has the same given fields as this model.
     106             :   ///
     107             :   /// [onlyFields] and [exceptFields] can be used to limit the fields that are
     108             :   /// compared. The two are mutually exclusive and an error will be thrown if
     109             :   /// both are specified.
     110           2 :   bool hasSameFields<T extends GenericModel>({
     111             :     required T model,
     112             :     Iterable<String>? onlyFields,
     113             :     Iterable<String>? exceptFields,
     114             :   }) {
     115           2 :     return fieldsToEvaluate(onlyFields, exceptFields)
     116           2 :         .map(
     117           6 :           (e) => _equality(
     118           8 :             getterSetterMap[e]?.item1(),
     119           8 :             model.getterSetterMap[e]?.item1(),
     120             :           ),
     121             :         )
     122           4 :         .reduce((value, element) => value && element);
     123             :   }
     124             : 
     125             :   /// Returns the fields that exist in this model.
     126             :   ///
     127             :   /// [onlyFields] and [exceptFields] can be used to limit the fields that are
     128             :   /// evaluated. The two are mutually exclusive and an error will be thrown if
     129             :   /// both are specified.
     130           4 :   Iterable<String> fieldsToEvaluate<T extends GenericModel>(
     131             :     Iterable<String>? onlyFields,
     132             :     Iterable<String>? exceptFields,
     133             :   ) {
     134             :     assert(
     135           4 :       onlyFields == null || exceptFields == null,
     136             :       'onlyFields and exceptFields cannot both be specified at the same time',
     137             :     );
     138          16 :     return getterSetterMap.keys.where((element) {
     139             :       if (onlyFields != null) {
     140           2 :         return onlyFields.contains(element);
     141             :       }
     142             :       if (exceptFields != null) {
     143           1 :         return !exceptFields.contains(element);
     144             :       }
     145             :       return true;
     146             :     });
     147             :   }
     148             : 
     149             :   /// Returns [id] if that value is not null. Otherwise will automatically
     150             :   /// generate a new value for [id] with the [idSuffix] setter.
     151          20 :   String get autoGenId => id = id ?? prefixTypeForId(const Uuid().v4());
     152             : 
     153             :   /// Prefixes this [GenericModel]'s [type] to [idSuffix]
     154          12 :   String prefixTypeForId(String idSuffix) => '$type::$idSuffix';
     155             : 
     156             :   /// Sets the [id] to have [idSuffix] as a suffix. This will override the
     157             :   /// existing [id].
     158           2 :   set idSuffix(String? idSuffix) =>
     159           4 :       id = idSuffix == null ? null : prefixTypeForId(idSuffix);
     160             : 
     161             :   /// Returns the last part of this [id], which is always a unique identifier.
     162             :   ///
     163             :   /// Note that if multiple types are prefixed, none of those will be returned.
     164           8 :   String? get idSuffix => id?.split('::').last;
     165             : 
     166             :   /// Implemented by subclasses to map the getters and setters of the object.
     167             :   ///
     168             :   /// Cannot have keys that have the values [TYPE] or [ID]
     169             :   Map<String, Tuple2<Getter<dynamic>, Setter<dynamic>>> getGetterSetterMap();
     170             : 
     171             :   /// Unique type to give to the model. Whether or not collision is expected is
     172             :   /// dependent on the parameters of your system.
     173             :   String get type;
     174             : 
     175             :   /// Converts the pair of [Getter] and [Setter] for an enum into the
     176             :   /// appropriate pair for storage (a String)
     177             :   ///
     178             :   /// [values] is the list of possible values that the enum has
     179             :   /// (ex. ExampleEnum.values)
     180           4 :   static Tuple2<Getter<dynamic>, Setter<dynamic>>
     181             :       convertEnumToString<T extends Enum>(
     182             :     Getter<T?> getter,
     183             :     Setter<T?> setter,
     184             :     Iterable<T> values,
     185             :   ) {
     186           4 :     return Tuple2(
     187           4 :       () {
     188           4 :         final value = getter();
     189           4 :         return value?.name;
     190             :       },
     191           8 :       (val) => setter(
     192             :         val == null
     193             :             ? null
     194          12 :             : values.map<T?>((e) => e).firstWhere(
     195          12 :                   (element) => val == element?.name,
     196           1 :                   orElse: () => null,
     197             :                 ),
     198             :       ),
     199             :     );
     200             :   }
     201             : 
     202             :   /// Converts the pair of [Getter] and [Setter] for a [GenericModel] into the
     203             :   /// appropriate serialized type.
     204             :   ///
     205             :   /// [supplier] should generate a new mutable version of this [GenericModel]
     206           3 :   static Tuple2<Getter<dynamic>, Setter<dynamic>> model<T extends GenericModel>(
     207             :     Getter<T?> getter,
     208             :     Setter<T?> setter,
     209             :     Getter<T> supplier,
     210             :   ) =>
     211           3 :       Tuple2(
     212           9 :         () => getter()?.toMap(),
     213           6 :         (val) => setter(
     214             :           val == null
     215             :               ? null
     216           6 :               : (supplier()..loadFromMap(val as Map<String, dynamic>)),
     217             :         ),
     218             :       );
     219             : 
     220             :   /// Converts the pair of [Getter] and [Setter] for a [List] of [GenericModel]
     221             :   /// into the appropriate serialized type.
     222             :   ///
     223             :   /// [supplier] should generate a new mutable version of this [GenericModel]
     224           3 :   static Tuple2<Getter<dynamic>, Setter<dynamic>>
     225             :       modelList<T extends GenericModel>(
     226             :     Getter<List<T>?> getter,
     227             :     Setter<List<T>?> setter,
     228             :     Getter<T> supplier,
     229             :   ) =>
     230           3 :           Tuple2(
     231          14 :             () => getter()?.map((e) => e.toMap()).toList(),
     232           6 :             (val) => setter(
     233             :               (val as List<Map<String, dynamic>>?)
     234           6 :                   ?.map<T>((e) => supplier()..loadFromMap(e))
     235           3 :                   .toList(),
     236             :             ),
     237             :           );
     238             : 
     239             :   /// Converts the pair of [Getter] and [Setter] for a [Map] of [GenericModel]
     240             :   /// into the appropriate serialized type.
     241             :   ///
     242             :   /// [supplier] should generate a new mutable version of this [GenericModel]
     243           3 :   static Tuple2<Getter<dynamic>, Setter<dynamic>>
     244             :       modelMap<T extends GenericModel>(
     245             :     Getter<Map<String, T>?> getter,
     246             :     Setter<Map<String, T>?> setter,
     247             :     Getter<T> supplier,
     248             :   ) =>
     249           3 :           Tuple2(
     250          12 :             () => getter()?.map((key, value) => MapEntry(key, value.toMap())),
     251           6 :             (val) => setter(
     252           3 :               (val as Map<String, dynamic>?)?.map<String, T>(
     253           2 :                 (key, value) => MapEntry(
     254             :                   key,
     255           2 :                   supplier()..loadFromMap(value as Map<String, dynamic>),
     256             :                 ),
     257             :               ),
     258             :             ),
     259             :           );
     260             : 
     261             :   /// Converts the pair of [Getter] and [Setter] for a [DateTime] into the
     262             :   /// appropriate serialized type. (microsecondsSinceEpoc)
     263           4 :   static Tuple2<Getter<dynamic>, Setter<dynamic>> dateTime(
     264             :     Getter<DateTime?> getter,
     265             :     Setter<DateTime?> setter,
     266             :   ) =>
     267           4 :       Tuple2(
     268          12 :         () => getter()?.microsecondsSinceEpoch,
     269           8 :         (val) => setter(
     270           4 :           val == null ? null : DateTime.fromMicrosecondsSinceEpoch(val as int),
     271             :         ),
     272             :       );
     273             : 
     274             :   /// Takes the pair of [Getter] and [Setter] for a primitive and puts them in
     275             :   /// a [Tuple2]. Convenience function if you don't want to rely on the tuple
     276             :   /// package directly.
     277           5 :   static Tuple2<Getter<dynamic>, Setter<dynamic>> primitive<T>(
     278             :     Getter<T?> getter,
     279             :     Setter<T?> setter,
     280             :   ) =>
     281          25 :       Tuple2(() => getter(), (val) => setter(val as T?));
     282             : }

Generated by: LCOV version 1.14