From 796c0c70b0faa1e342908ecfac77fdae284cd1f1 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Fri, 8 Sep 2023 21:35:57 +0200 Subject: [PATCH] feat(dynamite): support date and date-time formats Signed-off-by: Nikolas Rimikis --- .../dynamite/lib/src/builder/imports.dart | 1 + .../lib/src/builder/resolve_type.dart | 2 +- .../dynamite/lib/src/builder/serializer.dart | 2 +- .../lib/src/helpers/dart_helpers.dart | 10 +- .../lib/date_time.openapi.dart | 71 ++++++++ .../lib/date_time.openapi.g.dart | 154 ++++++++++++++++++ .../lib/date_time.openapi.json | 26 +++ .../lib/nested_ofs.openapi.dart | 2 + .../test/date_time_test.dart | 20 +++ 9 files changed, 284 insertions(+), 4 deletions(-) create mode 100644 packages/dynamite/dynamite_end_to_end_test/lib/date_time.openapi.dart create mode 100644 packages/dynamite/dynamite_end_to_end_test/lib/date_time.openapi.g.dart create mode 100644 packages/dynamite/dynamite_end_to_end_test/lib/date_time.openapi.json create mode 100644 packages/dynamite/dynamite_end_to_end_test/test/date_time_test.dart diff --git a/packages/dynamite/dynamite/lib/src/builder/imports.dart b/packages/dynamite/dynamite/lib/src/builder/imports.dart index c3a37d54..805a7c5b 100644 --- a/packages/dynamite/dynamite/lib/src/builder/imports.dart +++ b/packages/dynamite/dynamite/lib/src/builder/imports.dart @@ -12,6 +12,7 @@ List generateImports(final AssetId outputId) => [ const Code(''), Directive.import('package:built_collection/built_collection.dart'), Directive.import('package:built_value/built_value.dart'), + Directive.import('package:built_value/iso_8601_date_time_serializer.dart'), Directive.import('package:built_value/json_object.dart'), Directive.import('package:built_value/serializer.dart'), Directive.import('package:built_value/standard_json_plugin.dart'), diff --git a/packages/dynamite/dynamite/lib/src/builder/resolve_type.dart b/packages/dynamite/dynamite/lib/src/builder/resolve_type.dart index 607f5ad6..ecef9093 100644 --- a/packages/dynamite/dynamite/lib/src/builder/resolve_type.dart +++ b/packages/dynamite/dynamite/lib/src/builder/resolve_type.dart @@ -89,12 +89,12 @@ TypeResult resolveType( 'Uint8List', nullable: nullable, ), + 'date' || 'date-time' => TypeResultBase('DateTime'), _ => TypeResultBase( 'String', nullable: nullable, ), }; - case openapi.SchemaType.array: if (schema.items != null) { final subResult = resolveType( diff --git a/packages/dynamite/dynamite/lib/src/builder/serializer.dart b/packages/dynamite/dynamite/lib/src/builder/serializer.dart index 04674180..930e0cb8 100644 --- a/packages/dynamite/dynamite/lib/src/builder/serializer.dart +++ b/packages/dynamite/dynamite/lib/src/builder/serializer.dart @@ -14,7 +14,7 @@ List buildSerializer(final State state) { const Code(').build();'), const Code(''), const Code( - 'final Serializers _jsonSerializers = (_serializers.toBuilder()..add(DynamiteDoubleSerializer())..addPlugin(StandardJsonPlugin())..addPlugin(const ContentStringPlugin())).build();', + 'final Serializers _jsonSerializers = (_serializers.toBuilder()..add(DynamiteDoubleSerializer())..add(Iso8601DateTimeSerializer())..addPlugin(StandardJsonPlugin())..addPlugin(const ContentStringPlugin())).build();', ), const Code('// coverage:ignore-end'), ]; diff --git a/packages/dynamite/dynamite/lib/src/helpers/dart_helpers.dart b/packages/dynamite/dynamite/lib/src/helpers/dart_helpers.dart index 54e40aa3..49bab3b8 100644 --- a/packages/dynamite/dynamite/lib/src/helpers/dart_helpers.dart +++ b/packages/dynamite/dynamite/lib/src/helpers/dart_helpers.dart @@ -15,14 +15,16 @@ String toDartName( } } - if (_dartKeywords.contains(result) || RegExp(r'^[0-9]+$', multiLine: true).hasMatch(result)) { + if (_dartKeywords.contains(result) || + _dartTypes.contains(result) || + RegExp(r'^[0-9]+$', multiLine: true).hasMatch(result)) { return '\$$result'; } return result; } -final _dartKeywords = [ +const _dartKeywords = [ 'assert', 'break', 'case', @@ -84,6 +86,10 @@ final _dartKeywords = [ 'typedef', ]; +const _dartTypes = [ + 'DateTime', +]; + bool _isNonAlphaNumericString(final String input) => !RegExp(r'^[a-zA-Z0-9]$').hasMatch(input); String toFieldName(final String dartName, final String type) => dartName == type ? '\$$dartName' : dartName; diff --git a/packages/dynamite/dynamite_end_to_end_test/lib/date_time.openapi.dart b/packages/dynamite/dynamite_end_to_end_test/lib/date_time.openapi.dart new file mode 100644 index 00000000..3a217f54 --- /dev/null +++ b/packages/dynamite/dynamite_end_to_end_test/lib/date_time.openapi.dart @@ -0,0 +1,71 @@ +// ignore_for_file: camel_case_types +// ignore_for_file: discarded_futures +// ignore_for_file: public_member_api_docs +// ignore_for_file: unreachable_switch_case + +import 'package:built_value/built_value.dart'; +import 'package:built_value/iso_8601_date_time_serializer.dart'; +import 'package:built_value/serializer.dart'; +import 'package:built_value/standard_json_plugin.dart'; +import 'package:dynamite_runtime/built_value.dart'; +import 'package:dynamite_runtime/http_client.dart'; + +part 'date_time.openapi.g.dart'; + +class Client extends DynamiteClient { + Client( + super.baseURL, { + super.baseHeaders, + super.userAgent, + super.httpClient, + super.cookieJar, + }); + + Client.fromClient(final DynamiteClient client) + : super( + client.baseURL, + baseHeaders: client.baseHeaders, + httpClient: client.httpClient, + cookieJar: client.cookieJar, + authentications: client.authentications, + ); +} + +@BuiltValue(instantiable: false) +abstract interface class $DateTimeInterface { + DateTime get date; + @BuiltValueField(wireName: 'date-time') + DateTime get dateTime; +} + +abstract class $DateTime implements $DateTimeInterface, Built<$DateTime, $DateTimeBuilder> { + factory $DateTime([final void Function($DateTimeBuilder)? b]) = _$$DateTime; + + // coverage:ignore-start + const $DateTime._(); + // coverage:ignore-end + + // coverage:ignore-start + factory $DateTime.fromJson(final Map json) => _jsonSerializers.deserializeWith(serializer, json)!; + // coverage:ignore-end + + // coverage:ignore-start + Map toJson() => _jsonSerializers.serializeWith(serializer, this)! as Map; + // coverage:ignore-end + + static Serializer<$DateTime> get serializer => _$$dateTimeSerializer; +} + +// coverage:ignore-start +final Serializers _serializers = (Serializers().toBuilder() + ..addBuilderFactory(const FullType($DateTime), $DateTime.new) + ..add($DateTime.serializer)) + .build(); + +final Serializers _jsonSerializers = (_serializers.toBuilder() + ..add(DynamiteDoubleSerializer()) + ..add(Iso8601DateTimeSerializer()) + ..addPlugin(StandardJsonPlugin()) + ..addPlugin(const ContentStringPlugin())) + .build(); +// coverage:ignore-end diff --git a/packages/dynamite/dynamite_end_to_end_test/lib/date_time.openapi.g.dart b/packages/dynamite/dynamite_end_to_end_test/lib/date_time.openapi.g.dart new file mode 100644 index 00000000..0a26dbdc --- /dev/null +++ b/packages/dynamite/dynamite_end_to_end_test/lib/date_time.openapi.g.dart @@ -0,0 +1,154 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'date_time.openapi.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer<$DateTime> _$$dateTimeSerializer = _$$DateTimeSerializer(); + +class _$$DateTimeSerializer implements StructuredSerializer<$DateTime> { + @override + final Iterable types = const [$DateTime, _$$DateTime]; + @override + final String wireName = '\$DateTime'; + + @override + Iterable serialize(Serializers serializers, $DateTime object, + {FullType specifiedType = FullType.unspecified}) { + final result = [ + 'date', + serializers.serialize(object.date, specifiedType: const FullType(DateTime)), + 'date-time', + serializers.serialize(object.dateTime, specifiedType: const FullType(DateTime)), + ]; + + return result; + } + + @override + $DateTime deserialize(Serializers serializers, Iterable serialized, + {FullType specifiedType = FullType.unspecified}) { + final result = $DateTimeBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current! as String; + iterator.moveNext(); + final Object? value = iterator.current; + switch (key) { + case 'date': + result.date = serializers.deserialize(value, specifiedType: const FullType(DateTime))! as DateTime; + break; + case 'date-time': + result.dateTime = serializers.deserialize(value, specifiedType: const FullType(DateTime))! as DateTime; + break; + } + } + + return result.build(); + } +} + +abstract mixin class $DateTimeInterfaceBuilder { + void replace($DateTimeInterface other); + void update(void Function($DateTimeInterfaceBuilder) updates); + DateTime? get date; + set date(DateTime? date); + + DateTime? get dateTime; + set dateTime(DateTime? dateTime); +} + +class _$$DateTime extends $DateTime { + @override + final DateTime date; + @override + final DateTime dateTime; + + factory _$$DateTime([void Function($DateTimeBuilder)? updates]) => ($DateTimeBuilder()..update(updates))._build(); + + _$$DateTime._({required this.date, required this.dateTime}) : super._() { + BuiltValueNullFieldError.checkNotNull(date, r'$DateTime', 'date'); + BuiltValueNullFieldError.checkNotNull(dateTime, r'$DateTime', 'dateTime'); + } + + @override + $DateTime rebuild(void Function($DateTimeBuilder) updates) => (toBuilder()..update(updates)).build(); + + @override + $DateTimeBuilder toBuilder() => $DateTimeBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is $DateTime && date == other.date && dateTime == other.dateTime; + } + + @override + int get hashCode { + var _$hash = 0; + _$hash = $jc(_$hash, date.hashCode); + _$hash = $jc(_$hash, dateTime.hashCode); + _$hash = $jf(_$hash); + return _$hash; + } + + @override + String toString() { + return (newBuiltValueToStringHelper(r'$DateTime') + ..add('date', date) + ..add('dateTime', dateTime)) + .toString(); + } +} + +class $DateTimeBuilder implements Builder<$DateTime, $DateTimeBuilder>, $DateTimeInterfaceBuilder { + _$$DateTime? _$v; + + DateTime? _date; + DateTime? get date => _$this._date; + set date(covariant DateTime? date) => _$this._date = date; + + DateTime? _dateTime; + DateTime? get dateTime => _$this._dateTime; + set dateTime(covariant DateTime? dateTime) => _$this._dateTime = dateTime; + + $DateTimeBuilder(); + + $DateTimeBuilder get _$this { + final $v = _$v; + if ($v != null) { + _date = $v.date; + _dateTime = $v.dateTime; + _$v = null; + } + return this; + } + + @override + void replace(covariant $DateTime other) { + ArgumentError.checkNotNull(other, 'other'); + _$v = other as _$$DateTime; + } + + @override + void update(void Function($DateTimeBuilder)? updates) { + if (updates != null) updates(this); + } + + @override + $DateTime build() => _build(); + + _$$DateTime _build() { + final _$result = _$v ?? + _$$DateTime._( + date: BuiltValueNullFieldError.checkNotNull(date, r'$DateTime', 'date'), + dateTime: BuiltValueNullFieldError.checkNotNull(dateTime, r'$DateTime', 'dateTime')); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: deprecated_member_use_from_same_package,type=lint diff --git a/packages/dynamite/dynamite_end_to_end_test/lib/date_time.openapi.json b/packages/dynamite/dynamite_end_to_end_test/lib/date_time.openapi.json new file mode 100644 index 00000000..824d55b7 --- /dev/null +++ b/packages/dynamite/dynamite_end_to_end_test/lib/date_time.openapi.json @@ -0,0 +1,26 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "nested ofs test", + "version": "0.0.1" + }, + "components": { + "schemas": { + "DateTime": { + "type": "object", + "properties": { + "date": { + "type": "string", + "format": "date" + }, + "date-time": { + "type": "string", + "format": "date-time" + } + } + } + } + }, + "paths": {}, + "tags": [] +} diff --git a/packages/dynamite/dynamite_end_to_end_test/lib/nested_ofs.openapi.dart b/packages/dynamite/dynamite_end_to_end_test/lib/nested_ofs.openapi.dart index bdbfd579..37d64d32 100644 --- a/packages/dynamite/dynamite_end_to_end_test/lib/nested_ofs.openapi.dart +++ b/packages/dynamite/dynamite_end_to_end_test/lib/nested_ofs.openapi.dart @@ -4,6 +4,7 @@ // ignore_for_file: unreachable_switch_case import 'package:built_value/built_value.dart'; +import 'package:built_value/iso_8601_date_time_serializer.dart'; import 'package:built_value/json_object.dart'; import 'package:built_value/serializer.dart'; import 'package:built_value/standard_json_plugin.dart'; @@ -522,6 +523,7 @@ final Serializers _serializers = (Serializers().toBuilder() final Serializers _jsonSerializers = (_serializers.toBuilder() ..add(DynamiteDoubleSerializer()) + ..add(Iso8601DateTimeSerializer()) ..addPlugin(StandardJsonPlugin()) ..addPlugin(const ContentStringPlugin())) .build(); diff --git a/packages/dynamite/dynamite_end_to_end_test/test/date_time_test.dart b/packages/dynamite/dynamite_end_to_end_test/test/date_time_test.dart new file mode 100644 index 00000000..902adcbb --- /dev/null +++ b/packages/dynamite/dynamite_end_to_end_test/test/date_time_test.dart @@ -0,0 +1,20 @@ +import 'package:dynamite_end_to_end_test/date_time.openapi.dart'; +import 'package:test/test.dart'; + +void main() { + test(r'$DateTime', () { + final object = $DateTime( + (final b) => b + ..date = DateTime(2023, 10, 23).toUtc() + ..dateTime = DateTime(2023, 10, 23, 16, 45, 20).toUtc(), + ); + + final json = { + 'date': '2023-10-22T22:00:00.000Z', + 'date-time': '2023-10-23T14:45:20.000Z', + }; + + expect(object.toJson(), equals(json)); + expect($DateTime.fromJson(json), equals(object)); + }); +}