| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| part of '../../db.dart'; | |
| /// Annotation used to mark dart classes which can be stored into datastore. | |
| /// | |
| /// The `Kind` annotation on a class as well as other `Property` annotations on | |
| /// fields or getters of the class itself (and any of it's superclasses) up to | |
| /// the [Model] class describe the *mapping* of *dart objects* to datastore | |
| /// *entities*. | |
| /// | |
| /// An "entity" is an object which can be stored into Google Cloud Datastore. | |
| /// It contains a number of named "properties", some of them might get indexed, | |
| /// others are not. A "property" value can be of a limited set of supported | |
| /// types (such as `int` and `String`). | |
| /// | |
| /// Here is an example of a dart model class which can be stored into datastore: | |
| /// @Kind() | |
| /// class Person extends db.Model { | |
| /// @StringProperty() | |
| /// String name; | |
| /// | |
| /// @IntProperty() | |
| /// int age; | |
| /// | |
| /// @DateTimeProperty() | |
| /// DateTime dateOfBirth; | |
| /// } | |
| class Kind { | |
| /// The kind name used when saving objects to datastore. | |
| /// | |
| /// If `null` the name will be the same as the class name at which the | |
| /// annotation is placed. | |
| final String? name; | |
| /// The type, either [ID_TYPE_INTEGER] or [ID_TYPE_STRING]. | |
| final IdType idType; | |
| /// Annotation specifying the name of this kind and whether to use integer or | |
| /// string `id`s. | |
| /// | |
| /// If `name` is omitted, it will default to the name of class to which this | |
| /// annotation is attached to. | |
| const Kind({this.name, this.idType = IdType.Integer}); | |
| } | |
| /// The type used for id's of an entity. | |
| class IdType { | |
| /// Use integer ids for identifying entities. | |
| // ignore: constant_identifier_names | |
| static const IdType Integer = IdType('Integer'); | |
| /// Use string ids for identifying entities. | |
| // ignore: constant_identifier_names | |
| static const IdType String = IdType('String'); | |
| final core.String _type; | |
| const IdType(this._type); | |
| @override | |
| core.String toString() => 'IdType: $_type'; | |
| } | |
| /// Describes a property of an Entity. | |
| /// | |
| /// Please see [Kind] for an example on how to use them. | |
| abstract class Property { | |
| /// The name of the property. | |
| /// | |
| /// If it is `null`, the name will be the same as used in the | |
| /// model class. | |
| final String? propertyName; | |
| /// Specifies whether this property is required or not. | |
| /// | |
| /// If required is `true`, it will be enforced when saving model objects to | |
| /// the datastore and when retrieving them. | |
| final bool required; | |
| /// Specifies whether this property should be indexed or not. | |
| /// | |
| /// When running queries no this property, it is necessary to set [indexed] to | |
| /// `true`. | |
| final bool indexed; | |
| const Property( | |
| {this.propertyName, this.required = false, this.indexed = true}); | |
| bool validate(ModelDB db, Object? value) { | |
| if (required && value == null) return false; | |
| return true; | |
| } | |
| Object? encodeValue(ModelDB db, Object? value, {bool forComparison = false}); | |
| Object? decodePrimitiveValue(ModelDB db, Object? value); | |
| } | |
| /// An abstract base class for primitive properties which can e.g. be used | |
| /// within a composed `ListProperty`. | |
| abstract class PrimitiveProperty extends Property { | |
| const PrimitiveProperty( | |
| {String? propertyName, bool required = false, bool indexed = true}) | |
| : super(propertyName: propertyName, required: required, indexed: indexed); | |
| @override | |
| Object? encodeValue(ModelDB db, Object? value, | |
| {bool forComparison = false}) => | |
| value; | |
| @override | |
| Object? decodePrimitiveValue(ModelDB db, Object? value) => value; | |
| } | |
| /// A boolean [Property]. | |
| /// | |
| /// It will validate that values are booleans before writing them to the | |
| /// datastore and when reading them back. | |
| class BoolProperty extends PrimitiveProperty { | |
| const BoolProperty( | |
| {String? propertyName, bool required = false, bool indexed = true}) | |
| : super(propertyName: propertyName, required: required, indexed: indexed); | |
| @override | |
| bool validate(ModelDB db, Object? value) => | |
| super.validate(db, value) && (value == null || value is bool); | |
| } | |
| /// A integer [Property]. | |
| /// | |
| /// It will validate that values are integers before writing them to the | |
| /// datastore and when reading them back. | |
| class IntProperty extends PrimitiveProperty { | |
| const IntProperty( | |
| {String? propertyName, bool required = false, bool indexed = true}) | |
| : super(propertyName: propertyName, required: required, indexed: indexed); | |
| @override | |
| bool validate(ModelDB db, Object? value) => | |
| super.validate(db, value) && (value == null || value is int); | |
| } | |
| /// A double [Property]. | |
| /// | |
| /// It will validate that values are doubles before writing them to the | |
| /// datastore and when reading them back. | |
| class DoubleProperty extends PrimitiveProperty { | |
| const DoubleProperty( | |
| {String? propertyName, bool required = false, bool indexed = true}) | |
| : super(propertyName: propertyName, required: required, indexed: indexed); | |
| @override | |
| bool validate(ModelDB db, Object? value) => | |
| super.validate(db, value) && (value == null || value is double); | |
| } | |
| /// A string [Property]. | |
| /// | |
| /// It will validate that values are strings before writing them to the | |
| /// datastore and when reading them back. | |
| class StringProperty extends PrimitiveProperty { | |
| const StringProperty( | |
| {String? propertyName, bool required = false, bool indexed = true}) | |
| : super(propertyName: propertyName, required: required, indexed: indexed); | |
| @override | |
| bool validate(ModelDB db, Object? value) => | |
| super.validate(db, value) && (value == null || value is String); | |
| } | |
| /// A key [Property]. | |
| /// | |
| /// It will validate that values are keys before writing them to the | |
| /// datastore and when reading them back. | |
| class ModelKeyProperty extends PrimitiveProperty { | |
| const ModelKeyProperty( | |
| {String? propertyName, bool required = false, bool indexed = true}) | |
| : super(propertyName: propertyName, required: required, indexed: indexed); | |
| @override | |
| bool validate(ModelDB db, Object? value) => | |
| super.validate(db, value) && (value == null || value is Key); | |
| @override | |
| Object? encodeValue(ModelDB db, Object? value, {bool forComparison = false}) { | |
| if (value == null) return null; | |
| return db.toDatastoreKey(value as Key); | |
| } | |
| @override | |
| Object? decodePrimitiveValue(ModelDB db, Object? value) { | |
| if (value == null) return null; | |
| return db.fromDatastoreKey(value as ds.Key); | |
| } | |
| } | |
| /// A binary blob [Property]. | |
| /// | |
| /// It will validate that values are blobs before writing them to the | |
| /// datastore and when reading them back. Blob values will be represented by | |
| /// List<int>. | |
| class BlobProperty extends PrimitiveProperty { | |
| const BlobProperty({String? propertyName, bool required = false}) | |
| : super(propertyName: propertyName, required: required, indexed: false); | |
| // NOTE: We don't validate that the entries of the list are really integers | |
| // of the range 0..255! | |
| // If an untyped list was created the type check will always succeed. i.e. | |
| // "[1, true, 'bar'] is List<int>" evaluates to `true` | |
| @override | |
| bool validate(ModelDB db, Object? value) => | |
| super.validate(db, value) && (value == null || value is List<int>); | |
| @override | |
| Object? encodeValue(ModelDB db, Object? value, {bool forComparison = false}) { | |
| if (value == null) return null; | |
| return ds.BlobValue(value as List<int>); | |
| } | |
| @override | |
| Object? decodePrimitiveValue(ModelDB db, Object? value) { | |
| if (value == null) return null; | |
| return (value as ds.BlobValue).bytes; | |
| } | |
| } | |
| /// A datetime [Property]. | |
| /// | |
| /// It will validate that values are DateTime objects before writing them to the | |
| /// datastore and when reading them back. | |
| class DateTimeProperty extends PrimitiveProperty { | |
| const DateTimeProperty( | |
| {String? propertyName, bool required = false, bool indexed = true}) | |
| : super(propertyName: propertyName, required: required, indexed: indexed); | |
| @override | |
| bool validate(ModelDB db, Object? value) => | |
| super.validate(db, value) && (value == null || value is DateTime); | |
| @override | |
| Object? decodePrimitiveValue(ModelDB db, Object? value) { | |
| if (value is int) { | |
| return DateTime.fromMillisecondsSinceEpoch(value ~/ 1000, isUtc: true); | |
| } | |
| return value; | |
| } | |
| } | |
| /// A composed list [Property], with a `subProperty` for the list elements. | |
| /// | |
| /// It will validate that values are List objects before writing them to the | |
| /// datastore and when reading them back. It will also validate the elements | |
| /// of the list itself. | |
| class ListProperty extends Property { | |
| final PrimitiveProperty subProperty; | |
| // TODO: We want to support optional list properties as well. | |
| // Get rid of "required: true" here. | |
| const ListProperty(this.subProperty, | |
| {String? propertyName, bool indexed = true}) | |
| : super(propertyName: propertyName, required: true, indexed: indexed); | |
| @override | |
| bool validate(ModelDB db, Object? value) { | |
| if (!super.validate(db, value) || value is! List) return false; | |
| for (var entry in value) { | |
| if (!subProperty.validate(db, entry)) return false; | |
| } | |
| return true; | |
| } | |
| @override | |
| Object? encodeValue(ModelDB db, Object? value, {bool forComparison = false}) { | |
| if (forComparison) { | |
| // If we have comparison of list properties (i.e. repeated property names) | |
| // the comparison object must not be a list, but the value itself. | |
| // i.e. | |
| // | |
| // class Article { | |
| // ... | |
| // @ListProperty(StringProperty()) | |
| // List<String> tags; | |
| // ... | |
| // } | |
| // | |
| // should be queried via | |
| // | |
| // await db.query(Article, 'tags=', "Dart").toList(); | |
| // | |
| // So the [value] for the comparison is of type `String` and not | |
| // `List<String>`! | |
| return subProperty.encodeValue(db, value, forComparison: true); | |
| } | |
| if (value == null) return null; | |
| var list = value as List; | |
| if (list.isEmpty) return null; | |
| if (list.length == 1) return subProperty.encodeValue(db, list[0]); | |
| return list.map((value) => subProperty.encodeValue(db, value)).toList(); | |
| } | |
| @override | |
| Object decodePrimitiveValue(ModelDB db, Object? value) { | |
| if (value == null) return []; | |
| if (value is! List) return [subProperty.decodePrimitiveValue(db, value)]; | |
| return value | |
| .map((entry) => subProperty.decodePrimitiveValue(db, entry)) | |
| .toList(); | |
| } | |
| } | |
| /// A convenience [Property] for list of strings. | |
| class StringListProperty extends ListProperty { | |
| const StringListProperty({String? propertyName, bool indexed = true}) | |
| : super(const StringProperty(), | |
| propertyName: propertyName, indexed: indexed); | |
| @override | |
| Object decodePrimitiveValue(ModelDB db, Object? value) { | |
| return (super.decodePrimitiveValue(db, value) as core.List).cast<String>(); | |
| } | |
| } |