From 095b390db9991914f87ecb1f58871e6ec7794360 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller [
Code("final map = json.decodeMap(context: '$className');"),
];
diff --git a/packages/tonik_generate/lib/src/pubspec_generator.dart b/packages/tonik_generate/lib/src/pubspec_generator.dart
index ef578fb..257a253 100644
--- a/packages/tonik_generate/lib/src/pubspec_generator.dart
+++ b/packages/tonik_generate/lib/src/pubspec_generator.dart
@@ -25,6 +25,7 @@ dependencies:
big_decimal: ^0.5.0
collection: ^1.17.0
dio: ^5.8.0+1
+ lints: ^6.0.0
meta: ^1.16.0
tonik_util: ^0.0.5
''';
diff --git a/packages/tonik_generate/lib/src/util/format_with_header.dart b/packages/tonik_generate/lib/src/util/format_with_header.dart
index 6a51ab5..d6f305d 100644
--- a/packages/tonik_generate/lib/src/util/format_with_header.dart
+++ b/packages/tonik_generate/lib/src/util/format_with_header.dart
@@ -1,19 +1,10 @@
import 'package:dart_style/dart_style.dart';
extension FormatWithHeader on DartFormatter {
- static const _ignores = [
- 'lines_longer_than_80_chars',
- 'unnecessary_raw_strings',
- 'unnecessary_brace_in_string_interps',
- 'no_leading_underscores_for_local_identifiers',
- 'cascade_invocations',
- 'prefer_is_empty',
- ];
-
String formatWithHeader(String code) {
return format('''
// Generated code - do not modify by hand
-${_ignores.map((i) => '// ignore_for_file: $i').join('\n')}
+
$code''');
}
}
diff --git a/packages/tonik_generate/test/src/model/class_json_generator_test.dart b/packages/tonik_generate/test/src/model/class_json_generator_test.dart
index 2b391bb..7fb5505 100644
--- a/packages/tonik_generate/test/src/model/class_json_generator_test.dart
+++ b/packages/tonik_generate/test/src/model/class_json_generator_test.dart
@@ -597,5 +597,43 @@ void main() {
contains(collapseWhitespace(expectedMethod)),
);
});
+
+ test('generates fromJson method for class without properties', () {
+ final model = ClassModel(
+ context: context,
+ name: 'EmptyClass',
+ properties: const [],
+ );
+
+ const expectedMethod = '''
+ factory EmptyClass.fromJson(Object? json) {
+ return EmptyClass();
+ }''';
+
+ final generatedClass = generator.generateClass(model);
+ expect(
+ collapseWhitespace(format(generatedClass.accept(emitter).toString())),
+ contains(collapseWhitespace(expectedMethod)),
+ );
+ });
+
+ test('generates fromSimple method for class without properties', () {
+ final model = ClassModel(
+ context: context,
+ name: 'EmptyClass',
+ properties: const [],
+ );
+
+ const expectedMethod = '''
+ factory EmptyClass.fromSimple(String? value) {
+ return EmptyClass();
+ }''';
+
+ final generatedClass = generator.generateClass(model);
+ expect(
+ collapseWhitespace(format(generatedClass.accept(emitter).toString())),
+ contains(collapseWhitespace(expectedMethod)),
+ );
+ });
});
}
From 11e0fbd74bb86ba1a01ce4fc76372adc7f6f2d01 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
-
+
+
[];
// Check if we have a default response with null content type
@@ -49,7 +50,7 @@ class ParseGenerator {
}
}
- // Only add a default case if we don't have a default response with
+ // Only add a default case if we don't have a default response with
// null content type
final switchCases = [
const Code(
@@ -60,17 +61,19 @@ class ParseGenerator {
];
if (!hasDefaultWithNullContentType) {
- switchCases.add(Block.of([
- const Code('default:'),
- const Code(
- "final content = response.headers.value('content-type') "
- "?? 'not specified';",
- ),
- const Code('final status = response.statusCode;'),
- generateDecodingExceptionExpression(
- r'Unexpected content type: $content for status code: $status',
- ).statement,
- ]),);
+ switchCases.add(
+ Block.of([
+ const Code('default:'),
+ const Code(
+ "final content = response.headers.value('content-type') "
+ "?? 'not specified';",
+ ),
+ const Code('final status = response.statusCode;'),
+ generateDecodingExceptionExpression(
+ r'Unexpected content type: $content for status code: $status',
+ ).statement,
+ ]),
+ );
}
switchCases.add(const Code('}'));
diff --git a/packages/tonik_generate/lib/src/response_wrapper/response_wrapper_generator.dart b/packages/tonik_generate/lib/src/response_wrapper/response_wrapper_generator.dart
index 7cfc1b9..f5601d7 100644
--- a/packages/tonik_generate/lib/src/response_wrapper/response_wrapper_generator.dart
+++ b/packages/tonik_generate/lib/src/response_wrapper/response_wrapper_generator.dart
@@ -84,7 +84,7 @@ class ResponseWrapperGenerator {
(response.bodyCount > 1 || response.hasHeaders)) {
final responseClassName =
nameManager.responseNames(response.resolved).baseName;
-
+
bodyField = Field(
(b) =>
b
diff --git a/packages/tonik_generate/lib/src/server/server_file_generator.dart b/packages/tonik_generate/lib/src/server/server_file_generator.dart
index fcaefd8..488c832 100644
--- a/packages/tonik_generate/lib/src/server/server_file_generator.dart
+++ b/packages/tonik_generate/lib/src/server/server_file_generator.dart
@@ -40,4 +40,4 @@ class ServerFileGenerator {
final filePath = path.join(serverDirPath, result.filename);
File(filePath).writeAsStringSync(result.code);
}
-}
+}
diff --git a/packages/tonik_generate/lib/src/util/doc_comment_formatter.dart b/packages/tonik_generate/lib/src/util/doc_comment_formatter.dart
index 55e774d..c3860aa 100644
--- a/packages/tonik_generate/lib/src/util/doc_comment_formatter.dart
+++ b/packages/tonik_generate/lib/src/util/doc_comment_formatter.dart
@@ -1,18 +1,18 @@
/// Formats a single string as a doc comment.
-///
+///
/// If the string is multiline, each line will be prefixed with '/// '.
/// Returns an empty list if the input is null or empty.
List formatDocComment(String? text) {
if (text == null || text.isEmpty) {
return [];
}
-
+
// Split by newlines and prefix each line with '/// '
return text.split('\n').map((line) => '/// $line').toList();
}
/// Formats a list of strings as doc comments.
-///
+///
/// Each string in the list is processed with [formatDocComment],
/// and the results are flattened into a single list.
/// Null and empty strings are filtered out.
@@ -23,12 +23,12 @@ List formatDocComments(List? texts) {
}
final result = [];
-
+
for (final text in texts) {
if (text != null && text.isNotEmpty) {
result.addAll(formatDocComment(text));
}
}
-
+
return result;
-}
+}
diff --git a/packages/tonik_generate/lib/src/util/exception_code_generator.dart b/packages/tonik_generate/lib/src/util/exception_code_generator.dart
index 5580b9d..ec738fb 100644
--- a/packages/tonik_generate/lib/src/util/exception_code_generator.dart
+++ b/packages/tonik_generate/lib/src/util/exception_code_generator.dart
@@ -1,7 +1,5 @@
import 'package:code_builder/code_builder.dart';
-
-
/// Generates a throw expression for ArgumentError.
Expression generateArgumentErrorExpression(String message) {
return _generateExceptionExpression('ArgumentError', message);
diff --git a/packages/tonik_generate/lib/src/util/response_property_normalizer.dart b/packages/tonik_generate/lib/src/util/response_property_normalizer.dart
index 1b9c137..ea7ef4c 100644
--- a/packages/tonik_generate/lib/src/util/response_property_normalizer.dart
+++ b/packages/tonik_generate/lib/src/util/response_property_normalizer.dart
@@ -7,7 +7,6 @@ List<({String normalizedName, Property property, ResponseHeader? header})>
normalizeResponseProperties(ResponseObject response) {
final headerMap = {};
-
final headerProperties = response.headers.entries.map((header) {
final property = Property(
name:
diff --git a/packages/tonik_generate/lib/src/util/response_type_generator.dart b/packages/tonik_generate/lib/src/util/response_type_generator.dart
index 4666e0f..281898e 100644
--- a/packages/tonik_generate/lib/src/util/response_type_generator.dart
+++ b/packages/tonik_generate/lib/src/util/response_type_generator.dart
@@ -4,7 +4,7 @@ import 'package:tonik_core/tonik_core.dart';
import 'package:tonik_generate/src/naming/name_manager.dart';
import 'package:tonik_generate/src/util/type_reference_generator.dart';
-/// Generates the appropriate return type for an operation
+/// Generates the appropriate return type for an operation
/// based on its responses.
TypeReference resultTypeForOperation(
Operation operation,
@@ -63,4 +63,4 @@ TypeReference resultTypeForOperation(
),
),
};
-}
+}
diff --git a/packages/tonik_generate/test/src/api_client/api_client_file_generator_test.dart b/packages/tonik_generate/test/src/api_client/api_client_file_generator_test.dart
index 7d69fed..379f41d 100644
--- a/packages/tonik_generate/test/src/api_client/api_client_file_generator_test.dart
+++ b/packages/tonik_generate/test/src/api_client/api_client_file_generator_test.dart
@@ -215,7 +215,7 @@ void main() {
// Read the generated file to verify it contains the operation
final fileContent =
File(clientDir.listSync().first.path).readAsStringSync();
-
+
expect(fileContent, contains('untaggedOperation'));
expect(fileContent, contains('class DefaultApi'));
});
diff --git a/packages/tonik_generate/test/src/model/class_simple_generator_test.dart b/packages/tonik_generate/test/src/model/class_simple_generator_test.dart
index 420155c..8189a6a 100644
--- a/packages/tonik_generate/test/src/model/class_simple_generator_test.dart
+++ b/packages/tonik_generate/test/src/model/class_simple_generator_test.dart
@@ -232,7 +232,7 @@ void main() {
);
}
''';
-
+
expect(
collapseWhitespace(classCode),
contains(collapseWhitespace(expectedMethod)),
@@ -355,7 +355,7 @@ void main() {
);
}
''';
-
+
expect(
collapseWhitespace(classCode),
contains(collapseWhitespace(expectedMethod)),
diff --git a/packages/tonik_generate/test/src/naming/name_generator_test.dart b/packages/tonik_generate/test/src/naming/name_generator_test.dart
index b2c3858..8e2e473 100644
--- a/packages/tonik_generate/test/src/naming/name_generator_test.dart
+++ b/packages/tonik_generate/test/src/naming/name_generator_test.dart
@@ -1086,7 +1086,10 @@ void main() {
final generator = NameGenerator();
final servers = [
const Server(url: 'https://api.dev.example.com', description: null),
- const Server(url: 'https://api.staging.example.com', description: null),
+ const Server(
+ url: 'https://api.staging.example.com',
+ description: null,
+ ),
const Server(url: 'https://api.prod.example.com', description: null),
];
@@ -1178,7 +1181,8 @@ void main() {
expect(result.serverMap[servers[0]], 'CustomServer');
expect(result.customName, r'CustomServer$');
expect(result.baseName, 'ApiServer');
- });
+ },
+ );
test('uses default names on invalid URLs', () {
final generator = NameGenerator();
diff --git a/packages/tonik_generate/test/src/operation/operation_generator_response_test.dart b/packages/tonik_generate/test/src/operation/operation_generator_response_test.dart
index e9eba7b..b3b8c0b 100644
--- a/packages/tonik_generate/test/src/operation/operation_generator_response_test.dart
+++ b/packages/tonik_generate/test/src/operation/operation_generator_response_test.dart
@@ -169,49 +169,47 @@ void main() {
);
});
- test(
- 'returns result with model for single status code with body only',
- () {
- final operation = Operation(
- operationId: 'bodyStatus',
- context: context,
- summary: '',
- description: '',
- tags: const {},
- isDeprecated: false,
- path: '/body',
- method: HttpMethod.get,
- headers: const {},
- queryParameters: const {},
- pathParameters: const {},
- requestBody: null,
- responses: {
- const ExplicitResponseStatus(statusCode: 200): ResponseObject(
- name: 'BodyResponse',
- context: context,
- headers: const {},
- description: '',
- bodies: {
- ResponseBody(
- model: StringModel(context: context),
- rawContentType: 'application/json',
- contentType: ContentType.json,
- ),
- },
- ),
- },
- );
- const normalizedParams = NormalizedRequestParameters(
- pathParameters: [],
- queryParameters: [],
- headers: [],
- );
- final method = generator.generateCallMethod(
- operation,
- normalizedParams,
- );
- expect(
- method.returns?.accept(emitter).toString(),
+ test('returns result with model for single status code with body only', () {
+ final operation = Operation(
+ operationId: 'bodyStatus',
+ context: context,
+ summary: '',
+ description: '',
+ tags: const {},
+ isDeprecated: false,
+ path: '/body',
+ method: HttpMethod.get,
+ headers: const {},
+ queryParameters: const {},
+ pathParameters: const {},
+ requestBody: null,
+ responses: {
+ const ExplicitResponseStatus(statusCode: 200): ResponseObject(
+ name: 'BodyResponse',
+ context: context,
+ headers: const {},
+ description: '',
+ bodies: {
+ ResponseBody(
+ model: StringModel(context: context),
+ rawContentType: 'application/json',
+ contentType: ContentType.json,
+ ),
+ },
+ ),
+ },
+ );
+ const normalizedParams = NormalizedRequestParameters(
+ pathParameters: [],
+ queryParameters: [],
+ headers: [],
+ );
+ final method = generator.generateCallMethod(
+ operation,
+ normalizedParams,
+ );
+ expect(
+ method.returns?.accept(emitter).toString(),
'Future>',
);
});
diff --git a/packages/tonik_generate/test/src/server/server_file_generator_test.dart b/packages/tonik_generate/test/src/server/server_file_generator_test.dart
index 8b1b600..580bd63 100644
--- a/packages/tonik_generate/test/src/server/server_file_generator_test.dart
+++ b/packages/tonik_generate/test/src/server/server_file_generator_test.dart
@@ -100,17 +100,17 @@ void main() {
final serverDir = Directory(
path.join(tempDir.path, 'test_package', 'lib', 'src', 'server'),
);
-
+
expect(serverDir.listSync(), hasLength(1));
-
+
// Get file name and content
final generatedFile = serverDir.listSync().first;
final actualFileName = path.basename(generatedFile.path);
final fileContent = File(generatedFile.path).readAsStringSync();
-
+
// Check file name
expect(actualFileName, equals('api_server.dart'));
-
+
// Check file content
expect(fileContent, contains('sealed class ApiServer'));
expect(fileContent, contains('class ProductionServer'));
@@ -128,7 +128,7 @@ void main() {
models: {},
responseHeaders: {},
requestHeaders: {},
- servers: {}, // Empty servers collection
+ servers: {}, // Empty servers collection
operations: {},
responses: {},
queryParameters: {},
@@ -148,17 +148,17 @@ void main() {
);
expect(serverDir.existsSync(), isTrue);
expect(serverDir.listSync(), hasLength(1));
-
+
// Get file content
final generatedFile = serverDir.listSync().first;
final fileContent = File(generatedFile.path).readAsStringSync();
-
+
// Expect base class and custom class to be generated
expect(fileContent, contains('sealed class ApiServer'));
expect(fileContent, contains('class CustomServer'));
-
+
// No server-specific classes should be present
expect(fileContent.split('class').length, 3);
});
});
-}
+}
diff --git a/packages/tonik_generate/test/src/server/server_generator_test.dart b/packages/tonik_generate/test/src/server/server_generator_test.dart
index 77c768c..8f32742 100644
--- a/packages/tonik_generate/test/src/server/server_generator_test.dart
+++ b/packages/tonik_generate/test/src/server/server_generator_test.dart
@@ -65,7 +65,7 @@ void main() {
test('generates constructor with named parameters', () {
final constructor = baseClass.constructors.first;
- // Constructor should not be const since _dio is not final
+ // Constructor should not be const since _dio is not final
// and initialized later
expect(constructor.constant, isFalse);
expect(constructor.optionalParameters.length, 2);
@@ -121,7 +121,7 @@ void main() {
final productionClass = generatedClasses[1];
final productionConstructor = productionClass.constructors.first;
- // Constructor should not be const since base class
+ // Constructor should not be const since base class
// constructor isn't const
expect(productionConstructor.constant, isFalse);
expect(productionConstructor.optionalParameters.length, 1);
@@ -155,7 +155,7 @@ void main() {
final stagingClass = generatedClasses[2];
final stagingConstructor = stagingClass.constructors.first;
- // Constructor should not be const since base class constructor
+ // Constructor should not be const since base class constructor
// isn't const
expect(stagingConstructor.constant, isFalse);
expect(stagingConstructor.optionalParameters.length, 1);
@@ -191,7 +191,7 @@ void main() {
final customClass = generatedClasses.last;
final customConstructor = customClass.constructors.first;
- // Constructor should not be const since base class constructor
+ // Constructor should not be const since base class constructor
// isn't const
expect(customConstructor.constant, isFalse);
expect(customConstructor.optionalParameters.length, 2);
diff --git a/packages/tonik_generate/test/src/util/doc_comment_formatter_test.dart b/packages/tonik_generate/test/src/util/doc_comment_formatter_test.dart
index bc9d990..8f7452b 100644
--- a/packages/tonik_generate/test/src/util/doc_comment_formatter_test.dart
+++ b/packages/tonik_generate/test/src/util/doc_comment_formatter_test.dart
@@ -5,7 +5,7 @@ void main() {
group('doc comment formatter', () {
test('formats a single line string', () {
final result = formatDocComment('This is a doc comment');
-
+
expect(result, isNotEmpty);
expect(result.length, 1);
expect(result.first, '/// This is a doc comment');
@@ -15,7 +15,7 @@ void main() {
final result = formatDocComment(
'This is a multiline\ndoc comment\nwith three lines',
);
-
+
expect(result, isNotEmpty);
expect(result.length, 3);
expect(result[0], '/// This is a multiline');
@@ -25,13 +25,13 @@ void main() {
test('returns empty list for null string', () {
final result = formatDocComment(null);
-
+
expect(result, isEmpty);
});
test('returns empty list for empty string', () {
final result = formatDocComment('');
-
+
expect(result, isEmpty);
});
@@ -43,7 +43,7 @@ void main() {
'',
'Last line',
]);
-
+
expect(result, isNotEmpty);
expect(result.length, 3);
expect(result[0], '/// First line');
@@ -53,19 +53,19 @@ void main() {
test('returns empty list for null list', () {
final result = formatDocComments(null);
-
+
expect(result, isEmpty);
});
test('returns empty list for empty list', () {
final result = formatDocComments([]);
-
+
expect(result, isEmpty);
});
test('returns empty list for list of nulls and empty strings', () {
final result = formatDocComments([null, '', null]);
-
+
expect(result, isEmpty);
});
@@ -74,7 +74,7 @@ void main() {
'First\nMultiline',
'Second',
]);
-
+
expect(result, isNotEmpty);
expect(result.length, 3);
expect(result[0], '/// First');
@@ -82,4 +82,4 @@ void main() {
expect(result[2], '/// Second');
});
});
-}
+}
diff --git a/packages/tonik_generate/test/src/util/from_json_value_expression_generator_test.dart b/packages/tonik_generate/test/src/util/from_json_value_expression_generator_test.dart
index 4fab605..ee76425 100644
--- a/packages/tonik_generate/test/src/util/from_json_value_expression_generator_test.dart
+++ b/packages/tonik_generate/test/src/util/from_json_value_expression_generator_test.dart
@@ -514,14 +514,15 @@ void main() {
context: context,
);
- final expresion = buildFromJsonValueExpression(
- 'value',
- model: nestedListModel,
- nameManager: nameManager,
- package: 'package:my_package/my_package.dart',
- contextClass: 'Order',
- contextProperty: 'items',
- ).accept(emitter).toString();
+ final expresion =
+ buildFromJsonValueExpression(
+ 'value',
+ model: nestedListModel,
+ nameManager: nameManager,
+ package: 'package:my_package/my_package.dart',
+ contextClass: 'Order',
+ contextProperty: 'items',
+ ).accept(emitter).toString();
expect(
expresion,
diff --git a/packages/tonik_generate/test/src/util/from_simple_value_expression_generator_test.dart b/packages/tonik_generate/test/src/util/from_simple_value_expression_generator_test.dart
index 9aea25b..244a69a 100644
--- a/packages/tonik_generate/test/src/util/from_simple_value_expression_generator_test.dart
+++ b/packages/tonik_generate/test/src/util/from_simple_value_expression_generator_test.dart
@@ -296,7 +296,7 @@ void main() {
test('passes context parameter to decode methods when provided', () {
final value = refer('value');
-
+
expect(
buildSimpleValueExpression(
value,
diff --git a/packages/tonik_util/lib/src/dio/server_config.dart b/packages/tonik_util/lib/src/dio/server_config.dart
index f5fa6f4..8f4c48e 100644
--- a/packages/tonik_util/lib/src/dio/server_config.dart
+++ b/packages/tonik_util/lib/src/dio/server_config.dart
@@ -50,15 +50,15 @@ class ServerConfig {
// Set the server URL
dio.options.baseUrl = serverUrl;
-
+
// Add all interceptors
for (final interceptor in interceptors) {
dio.interceptors.add(interceptor);
}
-
+
// Set httpClientAdapter if provided
if (httpClientAdapter != null) {
dio.httpClientAdapter = httpClientAdapter!;
}
}
-}
+}
From a060662a50942cb71f9faed22e0becefc708defc Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Sun, 15 Jun 2025 17:06:34 +0200
Subject: [PATCH 11/31] fix: proper handle dates
---
.../to_json_value_expression_generator.dart | 3 +-
.../src/util/type_reference_generator.dart | 7 +-
.../test/src/model/all_of_generator_test.dart | 10 +-
.../src/model/typedef_generator_test.dart | 2 +-
.../src/operation/data_generator_test.dart | 4 +-
..._json_value_expression_generator_test.dart | 2 +-
packages/tonik_util/lib/src/date.dart | 16 +-
.../lib/src/decoding/json_decoder.dart | 43 ++++++
.../lib/src/decoding/simple_decoder.dart | 46 ++++++
packages/tonik_util/test/src/date_test.dart | 54 +++++--
.../test/src/decoding/json_decoder_test.dart | 67 ++++++++
.../src/decoding/simple_decoder_test.dart | 44 ++++++
.../src/encoder/deep_object_encoder_test.dart | 144 ++++++++++++------
13 files changed, 360 insertions(+), 82 deletions(-)
diff --git a/packages/tonik_generate/lib/src/util/to_json_value_expression_generator.dart b/packages/tonik_generate/lib/src/util/to_json_value_expression_generator.dart
index eaf0927..7d0a55d 100644
--- a/packages/tonik_generate/lib/src/util/to_json_value_expression_generator.dart
+++ b/packages/tonik_generate/lib/src/util/to_json_value_expression_generator.dart
@@ -47,8 +47,9 @@ String? _getSerializationSuffix(Model model, bool isNullable) {
isNullable || (model is EnumModel && model.isNullable) ? '?' : '';
return switch (model) {
- DateTimeModel() || DateModel() => '$nullablePart.toIso8601String()',
+ DateTimeModel() => '$nullablePart.toIso8601String()',
DecimalModel() => '$nullablePart.toString()',
+ DateModel() ||
EnumModel() ||
ClassModel() ||
AllOfModel() ||
diff --git a/packages/tonik_generate/lib/src/util/type_reference_generator.dart b/packages/tonik_generate/lib/src/util/type_reference_generator.dart
index 9be2bb4..63d6e85 100644
--- a/packages/tonik_generate/lib/src/util/type_reference_generator.dart
+++ b/packages/tonik_generate/lib/src/util/type_reference_generator.dart
@@ -71,8 +71,8 @@ TypeReference typeReference(
DateModel _ => TypeReference(
(b) =>
b
- ..symbol = 'DateTime'
- ..url = 'dart:core'
+ ..symbol = 'Date'
+ ..url = 'package:tonik_util/tonik_util.dart'
..isNullable = isNullableOverride,
),
DecimalModel _ => TypeReference(
@@ -87,8 +87,7 @@ TypeReference typeReference(
b
..symbol = nameManager.modelName(m)
..url = package
- ..isNullable =
- isNullableOverride,
+ ..isNullable = isNullableOverride,
),
};
}
diff --git a/packages/tonik_generate/test/src/model/all_of_generator_test.dart b/packages/tonik_generate/test/src/model/all_of_generator_test.dart
index f1875bd..1a67894 100644
--- a/packages/tonik_generate/test/src/model/all_of_generator_test.dart
+++ b/packages/tonik_generate/test/src/model/all_of_generator_test.dart
@@ -501,14 +501,14 @@ void main() {
expect(combinedClass.fields, hasLength(2));
expect(
combinedClass.fields.map((f) => f.name),
- containsAll(['dateTime', 'string']),
+ containsAll(['date', 'string']),
);
// Check field types
final dateField = combinedClass.fields.firstWhere(
- (f) => f.name == 'dateTime',
+ (f) => f.name == 'date',
);
- expect(dateField.type?.accept(emitter).toString(), 'DateTime');
+ expect(dateField.type?.accept(emitter).toString(), 'Date');
final stringField = combinedClass.fields.firstWhere(
(f) => f.name == 'string',
@@ -517,7 +517,7 @@ void main() {
// Check toJson - should return the ISO string
const expectedToJson = '''
- Object? toJson() => dateTime.toIso8601String();
+ Object? toJson() => date.toJson();
''';
expect(
@@ -529,7 +529,7 @@ void main() {
const expectedFromJson = '''
factory DateStringModel.fromJson(Object? json) {
return DateStringModel(
- dateTime: json.decodeJsonDate(context: r'DateStringModel'),
+ date: json.decodeJsonDate(context: r'DateStringModel'),
string: json.decodeJsonString(context: r'DateStringModel'),
);
}
diff --git a/packages/tonik_generate/test/src/model/typedef_generator_test.dart b/packages/tonik_generate/test/src/model/typedef_generator_test.dart
index fe4f88c..4d9d270 100644
--- a/packages/tonik_generate/test/src/model/typedef_generator_test.dart
+++ b/packages/tonik_generate/test/src/model/typedef_generator_test.dart
@@ -113,7 +113,7 @@ void main() {
(model: NumberModel(context: context), expectedType: 'num'),
(model: BooleanModel(context: context), expectedType: 'bool'),
(model: DateTimeModel(context: context), expectedType: 'DateTime'),
- (model: DateModel(context: context), expectedType: 'DateTime'),
+ (model: DateModel(context: context), expectedType: 'Date'),
(model: DecimalModel(context: context), expectedType: 'BigDecimal'),
];
diff --git a/packages/tonik_generate/test/src/operation/data_generator_test.dart b/packages/tonik_generate/test/src/operation/data_generator_test.dart
index 284bfd5..66c8e16 100644
--- a/packages/tonik_generate/test/src/operation/data_generator_test.dart
+++ b/packages/tonik_generate/test/src/operation/data_generator_test.dart
@@ -239,8 +239,8 @@ void main() {
);
const expectedMethod = '''
- Object? _data({required DateTime? body}) {
- return body?.toIso8601String();
+ Object? _data({required Date? body}) {
+ return body?.toJson();
}
''';
diff --git a/packages/tonik_generate/test/src/util/to_json_value_expression_generator_test.dart b/packages/tonik_generate/test/src/util/to_json_value_expression_generator_test.dart
index 4f48260..f54b21a 100644
--- a/packages/tonik_generate/test/src/util/to_json_value_expression_generator_test.dart
+++ b/packages/tonik_generate/test/src/util/to_json_value_expression_generator_test.dart
@@ -67,7 +67,7 @@ void main() {
);
expect(
buildToJsonPropertyExpression('dueDate', property),
- 'dueDate?.toIso8601String()',
+ 'dueDate?.toJson()',
);
});
diff --git a/packages/tonik_util/lib/src/date.dart b/packages/tonik_util/lib/src/date.dart
index 0f94661..b422e3f 100644
--- a/packages/tonik_util/lib/src/date.dart
+++ b/packages/tonik_util/lib/src/date.dart
@@ -10,7 +10,7 @@ import 'package:tonik_util/src/decoding/simple_decoder.dart';
class Date {
/// Creates a new [Date] instance.
///
- /// Throws [RangeError] if any of the date components are invalid.
+ /// Throws [FormatException] if any of the date components are invalid.
Date(this.year, this.month, this.day) {
_validate();
}
@@ -24,7 +24,8 @@ class Date {
/// Creates a [Date] from an ISO 8601 formatted string (YYYY-MM-DD).
///
- /// Throws [FormatException] if the string is not in the correct format.
+ /// Throws [FormatException] if the string is not in the correct format
+ /// or if any of the date components are invalid.
factory Date.fromString(String dateString) {
final parts = dateString.split('-');
if (parts.length != 3) {
@@ -37,7 +38,7 @@ class Date {
final day = int.parse(parts[2]);
final date = Date(year, month, day).._validate();
return date;
- } on FormatException {
+ } on Object {
throw const FormatException('Invalid date format. Expected YYYY-MM-DD');
}
}
@@ -114,12 +115,17 @@ class Date {
void _validate() {
if (month < 1 || month > 12) {
- throw RangeError.range(month, 1, 12, 'month');
+ throw FormatException(
+ 'Invalid month: $month. Month must be between 1 and 12.',
+ );
}
final daysInMonth = DateTime(year, month + 1, 0).day;
if (day < 1 || day > daysInMonth) {
- throw RangeError.range(day, 1, daysInMonth, 'day');
+ throw FormatException(
+ 'Invalid day: $day. Day must be between 1 and $daysInMonth for '
+ 'month $month.',
+ );
}
}
}
diff --git a/packages/tonik_util/lib/src/decoding/json_decoder.dart b/packages/tonik_util/lib/src/decoding/json_decoder.dart
index 4192acf..957f61d 100644
--- a/packages/tonik_util/lib/src/decoding/json_decoder.dart
+++ b/packages/tonik_util/lib/src/decoding/json_decoder.dart
@@ -1,4 +1,5 @@
import 'package:big_decimal/big_decimal.dart';
+import 'package:tonik_util/src/date.dart';
import 'package:tonik_util/src/decoding/decoding_exception.dart';
/// Extensions for decoding JSON values.
@@ -348,4 +349,46 @@ extension JsonDecoder on Object? {
}
return this! as bool;
}
+
+ /// Decodes a JSON value to a Date.
+ ///
+ /// Expects ISO 8601 format string (YYYY-MM-DD).
+ /// Throws [InvalidTypeException] if the value is not a valid date string
+ /// or if the value is null.
+ Date decodeJsonDate({String? context}) {
+ if (this == null) {
+ throw InvalidTypeException(
+ value: 'null',
+ targetType: Date,
+ context: context,
+ );
+ }
+ if (this is! String) {
+ throw InvalidTypeException(
+ value: toString(),
+ targetType: Date,
+ context: context,
+ );
+ }
+ try {
+ return Date.fromString(this! as String);
+ } on FormatException catch (e) {
+ throw InvalidTypeException(
+ value: this! as String,
+ targetType: Date,
+ context: e.message,
+ );
+ }
+ }
+
+ /// Decodes a JSON value to a nullable Date.
+ ///
+ /// Returns null if the value is null or an empty string.
+ /// Throws [InvalidTypeException] if the value is not a valid date string.
+ Date? decodeJsonNullableDate({String? context}) {
+ if (this == null || (this is String && (this! as String).isEmpty)) {
+ return null;
+ }
+ return decodeJsonDate(context: context);
+ }
}
diff --git a/packages/tonik_util/lib/src/decoding/simple_decoder.dart b/packages/tonik_util/lib/src/decoding/simple_decoder.dart
index c5f7957..9f7d9a8 100644
--- a/packages/tonik_util/lib/src/decoding/simple_decoder.dart
+++ b/packages/tonik_util/lib/src/decoding/simple_decoder.dart
@@ -1,4 +1,5 @@
import 'package:big_decimal/big_decimal.dart';
+import 'package:tonik_util/src/date.dart';
import 'package:tonik_util/src/decoding/decoding_exception.dart';
/// Extensions for decoding simple form values from strings.
@@ -253,4 +254,49 @@ extension SimpleDecoder on String? {
if (this?.isEmpty ?? true) return null;
return decodeSimpleStringNullableList(context: context);
}
+
+ /// Decodes a string to a Date.
+ ///
+ /// The string must be in ISO 8601 format (YYYY-MM-DD).
+ /// Throws [FormatException] if the string is not in the correct format or if
+ /// any of the date components are invalid.
+ /// Throws [InvalidTypeException] if the value is null or empty.
+ Date decodeSimpleDate({String? context}) {
+ if (this == null) {
+ throw InvalidTypeException(
+ value: 'null',
+ targetType: Date,
+ context: context,
+ );
+ }
+ if (this!.isEmpty) {
+ throw InvalidTypeException(
+ value: 'empty string',
+ targetType: Date,
+ context: context,
+ );
+ }
+ try {
+ return Date.fromString(this!);
+ } on FormatException {
+ rethrow;
+ } on Object {
+ throw InvalidTypeException(
+ value: this!,
+ targetType: Date,
+ context: context,
+ );
+ }
+ }
+
+ /// Decodes a string to a nullable Date.
+ ///
+ /// Returns null if the string is empty or null.
+ /// The string must be in ISO 8601 format (YYYY-MM-DD).
+ /// Throws [FormatException] if the string is not in the correct format or if
+ /// any of the date components are invalid.
+ Date? decodeSimpleNullableDate({String? context}) {
+ if (this?.isEmpty ?? true) return null;
+ return decodeSimpleDate(context: context);
+ }
}
diff --git a/packages/tonik_util/test/src/date_test.dart b/packages/tonik_util/test/src/date_test.dart
index 98c3be7..25ce1b3 100644
--- a/packages/tonik_util/test/src/date_test.dart
+++ b/packages/tonik_util/test/src/date_test.dart
@@ -98,13 +98,25 @@ void main() {
});
test('fromJson throws on invalid date values', () {
- expect(() => Date.fromJson('2024-00-15'), throwsA(isA()));
- expect(() => Date.fromJson('2024-13-15'), throwsA(isA()));
- expect(() => Date.fromJson('2024-03-00'), throwsA(isA()));
- expect(() => Date.fromJson('2024-03-32'), throwsA(isA()));
+ expect(
+ () => Date.fromJson('2024-00-15'),
+ throwsA(isA()),
+ );
+ expect(
+ () => Date.fromJson('2024-13-15'),
+ throwsA(isA()),
+ );
+ expect(
+ () => Date.fromJson('2024-03-00'),
+ throwsA(isA()),
+ );
+ expect(
+ () => Date.fromJson('2024-03-32'),
+ throwsA(isA()),
+ );
expect(
() => Date.fromJson('2024-02-30'),
- throwsA(isA()),
+ throwsA(isA()),
); // February 30th
});
@@ -128,24 +140,36 @@ void main() {
});
test('fromSimple throws on invalid date values', () {
- expect(() => Date.fromSimple('2024-00-15'), throwsA(isA()));
- expect(() => Date.fromSimple('2024-13-15'), throwsA(isA()));
- expect(() => Date.fromSimple('2024-03-00'), throwsA(isA()));
- expect(() => Date.fromSimple('2024-03-32'), throwsA(isA()));
+ expect(
+ () => Date.fromSimple('2024-00-15'),
+ throwsA(isA()),
+ );
+ expect(
+ () => Date.fromSimple('2024-13-15'),
+ throwsA(isA()),
+ );
+ expect(
+ () => Date.fromSimple('2024-03-00'),
+ throwsA(isA()),
+ );
+ expect(
+ () => Date.fromSimple('2024-03-32'),
+ throwsA(isA()),
+ );
expect(
() => Date.fromSimple('2024-02-30'),
- throwsA(isA()),
+ throwsA(isA()),
); // February 30th
});
test('validates date components', () {
- expect(() => Date(2024, 0, 15), throwsA(isA()));
- expect(() => Date(2024, 13, 15), throwsA(isA()));
- expect(() => Date(2024, 3, 0), throwsA(isA()));
- expect(() => Date(2024, 3, 32), throwsA(isA()));
+ expect(() => Date(2024, 0, 15), throwsA(isA()));
+ expect(() => Date(2024, 13, 15), throwsA(isA()));
+ expect(() => Date(2024, 3, 0), throwsA(isA()));
+ expect(() => Date(2024, 3, 32), throwsA(isA()));
expect(
() => Date(2024, 2, 30),
- throwsA(isA()),
+ throwsA(isA()),
); // February 30th
});
});
diff --git a/packages/tonik_util/test/src/decoding/json_decoder_test.dart b/packages/tonik_util/test/src/decoding/json_decoder_test.dart
index f2cc630..1b21dc2 100644
--- a/packages/tonik_util/test/src/decoding/json_decoder_test.dart
+++ b/packages/tonik_util/test/src/decoding/json_decoder_test.dart
@@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:big_decimal/big_decimal.dart';
import 'package:test/test.dart';
+import 'package:tonik_util/src/date.dart';
import 'package:tonik_util/src/decoding/decoding_exception.dart';
import 'package:tonik_util/src/decoding/json_decoder.dart';
@@ -196,6 +197,72 @@ void main() {
);
});
});
+
+ group('Date', () {
+ test('decodes Date values', () {
+ final date = Date(2024, 3, 15);
+ expect('2024-03-15'.decodeJsonDate(), date);
+ expect(
+ () => 123.decodeJsonDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => null.decodeJsonDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-00-15'.decodeJsonDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-13-15'.decodeJsonDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-03-00'.decodeJsonDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-03-32'.decodeJsonDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-02-30'.decodeJsonDate(),
+ throwsA(isA()),
+ );
+ });
+
+ test('decodes nullable Date values', () {
+ final date = Date(2024, 3, 15);
+ expect('2024-03-15'.decodeJsonNullableDate(), date);
+ expect(null.decodeJsonNullableDate(), isNull);
+ expect(''.decodeJsonNullableDate(), isNull);
+ expect(
+ () => 123.decodeJsonNullableDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-00-15'.decodeJsonNullableDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-13-15'.decodeJsonNullableDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-03-00'.decodeJsonNullableDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-03-32'.decodeJsonNullableDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-02-30'.decodeJsonNullableDate(),
+ throwsA(isA()),
+ );
+ });
+ });
});
group('List', () {
diff --git a/packages/tonik_util/test/src/decoding/simple_decoder_test.dart b/packages/tonik_util/test/src/decoding/simple_decoder_test.dart
index 85818cc..366b6a1 100644
--- a/packages/tonik_util/test/src/decoding/simple_decoder_test.dart
+++ b/packages/tonik_util/test/src/decoding/simple_decoder_test.dart
@@ -1,5 +1,6 @@
import 'package:big_decimal/big_decimal.dart';
import 'package:test/test.dart';
+import 'package:tonik_util/src/date.dart';
import 'package:tonik_util/src/decoding/decoding_exception.dart';
import 'package:tonik_util/src/decoding/simple_decoder.dart';
@@ -70,6 +71,39 @@ void main() {
throwsA(isA()),
);
});
+
+ test('decodes Date values', () {
+ final date = Date(2024, 3, 15);
+ expect('2024-03-15'.decodeSimpleDate(), date);
+ expect(
+ () => 'not-a-date'.decodeSimpleDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => null.decodeSimpleDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-00-15'.decodeSimpleDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-13-15'.decodeSimpleDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-03-00'.decodeSimpleDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-03-32'.decodeSimpleDate(),
+ throwsA(isA()),
+ );
+ expect(
+ () => '2024-02-30'.decodeSimpleDate(),
+ throwsA(isA()),
+ );
+ });
});
group('Nullable Values', () {
@@ -79,12 +113,14 @@ void main() {
expect(''.decodeSimpleNullableBool(), isNull);
expect(''.decodeSimpleNullableDateTime(), isNull);
expect(''.decodeSimpleNullableBigDecimal(), isNull);
+ expect(''.decodeSimpleNullableDate(), isNull);
expect(null.decodeSimpleNullableInt(), isNull);
expect(null.decodeSimpleNullableDouble(), isNull);
expect(null.decodeSimpleNullableBool(), isNull);
expect(null.decodeSimpleNullableDateTime(), isNull);
expect(null.decodeSimpleNullableBigDecimal(), isNull);
+ expect(null.decodeSimpleNullableDate(), isNull);
});
test('decodes non-empty strings for nullable types', () {
@@ -99,6 +135,10 @@ void main() {
'3.14'.decodeSimpleNullableBigDecimal(),
BigDecimal.parse('3.14'),
);
+ expect(
+ '2024-03-15'.decodeSimpleNullableDate(),
+ Date(2024, 3, 15),
+ );
});
});
@@ -124,6 +164,10 @@ void main() {
() => ''.decodeSimpleBigDecimal(),
throwsA(isA()),
);
+ expect(
+ () => ''.decodeSimpleDate(),
+ throwsA(isA()),
+ );
});
});
diff --git a/packages/tonik_util/test/src/encoder/deep_object_encoder_test.dart b/packages/tonik_util/test/src/encoder/deep_object_encoder_test.dart
index 1182669..9bfd98b 100644
--- a/packages/tonik_util/test/src/encoder/deep_object_encoder_test.dart
+++ b/packages/tonik_util/test/src/encoder/deep_object_encoder_test.dart
@@ -7,10 +7,14 @@ void main() {
group('DeepObjectEncoder', () {
test('encodes a simple object', () {
- final result = encoder.encode('filter', {
- 'color': 'red',
- 'size': 'large',
- }, allowEmpty: true,);
+ final result = encoder.encode(
+ 'filter',
+ {
+ 'color': 'red',
+ 'size': 'large',
+ },
+ allowEmpty: true,
+ );
expect(result, [
(name: 'filter[color]', value: 'red'),
@@ -19,10 +23,14 @@ void main() {
});
test('encodes boolean properties', () {
- final result = encoder.encode('filter', {
- 'active': true,
- 'premium': false,
- }, allowEmpty: true,);
+ final result = encoder.encode(
+ 'filter',
+ {
+ 'active': true,
+ 'premium': false,
+ },
+ allowEmpty: true,
+ );
expect(result, [
(name: 'filter[active]', value: 'true'),
@@ -31,10 +39,14 @@ void main() {
});
test('encodes an object with a null value', () {
- final result = encoder.encode('filter', {
- 'color': null,
- 'size': 'large',
- }, allowEmpty: true,);
+ final result = encoder.encode(
+ 'filter',
+ {
+ 'color': null,
+ 'size': 'large',
+ },
+ allowEmpty: true,
+ );
expect(result, [
(name: 'filter[color]', value: ''),
@@ -53,9 +65,13 @@ void main() {
});
test('encodes nested objects', () {
- final result = encoder.encode('filter', {
- 'product': {'color': 'blue', 'size': 'medium'},
- }, allowEmpty: true,);
+ final result = encoder.encode(
+ 'filter',
+ {
+ 'product': {'color': 'blue', 'size': 'medium'},
+ },
+ allowEmpty: true,
+ );
expect(result, [
(name: 'filter[product][color]', value: 'blue'),
@@ -64,11 +80,15 @@ void main() {
});
test('encodes deeply nested objects', () {
- final result = encoder.encode('filter', {
- 'product': {
- 'attributes': {'color': 'blue', 'size': 'medium'},
+ final result = encoder.encode(
+ 'filter',
+ {
+ 'product': {
+ 'attributes': {'color': 'blue', 'size': 'medium'},
+ },
},
- }, allowEmpty: true,);
+ allowEmpty: true,
+ );
expect(result, [
(name: 'filter[product][attributes][color]', value: 'blue'),
@@ -78,9 +98,13 @@ void main() {
test('throws for objects containing arrays', () {
expect(
- () => encoder.encode('filter', {
- 'colors': ['red', 'blue', 'green'],
- }, allowEmpty: true,),
+ () => encoder.encode(
+ 'filter',
+ {
+ 'colors': ['red', 'blue', 'green'],
+ },
+ allowEmpty: true,
+ ),
throwsA(isA()),
);
});
@@ -95,20 +119,28 @@ void main() {
test('throws for objects containing sets', () {
expect(
- () => encoder.encode('filter', {
- 'sizes': {'small', 'medium', 'large'},
- }, allowEmpty: true,),
+ () => encoder.encode(
+ 'filter',
+ {
+ 'sizes': {'small', 'medium', 'large'},
+ },
+ allowEmpty: true,
+ ),
throwsA(isA()),
);
});
test('encodes a complex object with various types', () {
- final result = encoder.encode('params', {
- 'name': 'John',
- 'age': 30,
- 'active': true,
- 'address': {'street': '123 Main St', 'city': 'New York'},
- }, allowEmpty: true,);
+ final result = encoder.encode(
+ 'params',
+ {
+ 'name': 'John',
+ 'age': 30,
+ 'active': true,
+ 'address': {'street': '123 Main St', 'city': 'New York'},
+ },
+ allowEmpty: true,
+ );
expect(result, [
(name: 'params[name]', value: 'John'),
@@ -180,11 +212,15 @@ void main() {
group('allowEmpty parameter', () {
test('allows empty values when allowEmpty is true', () {
- final result = encoder.encode('filter', {
- 'emptyString': '',
- 'emptyMap': {},
- 'normalValue': 'test',
- }, allowEmpty: true,);
+ final result = encoder.encode(
+ 'filter',
+ {
+ 'emptyString': '',
+ 'emptyMap': {},
+ 'normalValue': 'test',
+ },
+ allowEmpty: true,
+ );
expect(result, [
(name: 'filter[emptyString]', value: ''),
@@ -195,10 +231,14 @@ void main() {
test('throws when allowEmpty is false and value is empty string', () {
expect(
- () => encoder.encode('filter', {
- 'emptyString': '',
- 'normalValue': 'test',
- }, allowEmpty: false,),
+ () => encoder.encode(
+ 'filter',
+ {
+ 'emptyString': '',
+ 'normalValue': 'test',
+ },
+ allowEmpty: false,
+ ),
throwsA(isA()),
);
});
@@ -213,10 +253,14 @@ void main() {
test('throws when allowEmpty is false and nested map is empty', () {
expect(
- () => encoder.encode('filter', {
- 'nested': {},
- 'normalValue': 'test',
- }, allowEmpty: false,),
+ () => encoder.encode(
+ 'filter',
+ {
+ 'nested': {},
+ 'normalValue': 'test',
+ },
+ allowEmpty: false,
+ ),
throwsA(isA()),
);
});
@@ -238,10 +282,14 @@ void main() {
});
test('allows non-empty values when allowEmpty is false', () {
- final result = encoder.encode('filter', {
- 'string': 'value',
- 'nested': {'inner': 'value'},
- }, allowEmpty: false,);
+ final result = encoder.encode(
+ 'filter',
+ {
+ 'string': 'value',
+ 'nested': {'inner': 'value'},
+ },
+ allowEmpty: false,
+ );
expect(result, [
(name: 'filter[string]', value: 'value'),
From 38e3bd5a9f16873d5c75b073c37d0aa41f886217 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Sun, 15 Jun 2025 17:07:46 +0200
Subject: [PATCH 12/31] chore: add gov integration tests
---
.gitignore | 5 +-
integration_test/gov/gov_test/.gitignore | 7 +
.../gov/gov_test/analysis_options.yaml | 30 ++
.../gov_test/imposter/imposter-config.json | 7 +
.../gov/gov_test/imposter/response.groovy | 7 +
integration_test/gov/gov_test/pubspec.yaml | 19 +
.../gov/gov_test/test/default_test.dart | 120 +++++
.../gov/gov_test/test/test_helper.dart | 87 ++++
integration_test/gov/openapi.yaml | 431 ++++++++++++++++++
integration_test/setup.sh | 3 +
10 files changed, 715 insertions(+), 1 deletion(-)
create mode 100644 integration_test/gov/gov_test/.gitignore
create mode 100644 integration_test/gov/gov_test/analysis_options.yaml
create mode 100644 integration_test/gov/gov_test/imposter/imposter-config.json
create mode 100644 integration_test/gov/gov_test/imposter/response.groovy
create mode 100644 integration_test/gov/gov_test/pubspec.yaml
create mode 100644 integration_test/gov/gov_test/test/default_test.dart
create mode 100644 integration_test/gov/gov_test/test/test_helper.dart
create mode 100644 integration_test/gov/openapi.yaml
diff --git a/.gitignore b/.gitignore
index e9fabb6..2d4b255 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,7 +5,10 @@
.fvm/
# Integration Test
-/integration_test/petstore/petstore_api
/integration_test/imposter.jar
+/integration_test/petstore/petstore_api
/integration_test/music_streaming/music_streaming_api
+/integration_test/gov/gov_api
+
+# Dependencies
/pubspec.lock
diff --git a/integration_test/gov/gov_test/.gitignore b/integration_test/gov/gov_test/.gitignore
new file mode 100644
index 0000000..3cceda5
--- /dev/null
+++ b/integration_test/gov/gov_test/.gitignore
@@ -0,0 +1,7 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
+
+# Avoid committing pubspec.lock for library packages; see
+# https://dart.dev/guides/libraries/private-files#pubspeclock.
+pubspec.lock
diff --git a/integration_test/gov/gov_test/analysis_options.yaml b/integration_test/gov/gov_test/analysis_options.yaml
new file mode 100644
index 0000000..dee8927
--- /dev/null
+++ b/integration_test/gov/gov_test/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+# rules:
+# - camel_case_types
+
+# analyzer:
+# exclude:
+# - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/integration_test/gov/gov_test/imposter/imposter-config.json b/integration_test/gov/gov_test/imposter/imposter-config.json
new file mode 100644
index 0000000..a2480b3
--- /dev/null
+++ b/integration_test/gov/gov_test/imposter/imposter-config.json
@@ -0,0 +1,7 @@
+{
+ "plugin": "openapi",
+ "specFile": "../../openapi.yaml",
+ "response": {
+ "scriptFile": "response.groovy"
+ }
+}
\ No newline at end of file
diff --git a/integration_test/gov/gov_test/imposter/response.groovy b/integration_test/gov/gov_test/imposter/response.groovy
new file mode 100644
index 0000000..65422ea
--- /dev/null
+++ b/integration_test/gov/gov_test/imposter/response.groovy
@@ -0,0 +1,7 @@
+// Get the response status from the request header
+def responseStatus = context.request.headers['X-Response-Status'] ?: '200'
+
+// Set the response status code and use the OpenAPI specification
+respond()
+ .withStatusCode(Integer.parseInt(responseStatus))
+ .usingDefaultBehaviour()
diff --git a/integration_test/gov/gov_test/pubspec.yaml b/integration_test/gov/gov_test/pubspec.yaml
new file mode 100644
index 0000000..da73204
--- /dev/null
+++ b/integration_test/gov/gov_test/pubspec.yaml
@@ -0,0 +1,19 @@
+name: gov_test
+description: A starting point for Dart libraries or applications.
+version: 1.0.0
+publish_to: none
+
+environment:
+ sdk: ^3.8.0
+
+dependencies:
+ dio: ^5.8.0
+ gov_api:
+ path: ../gov_api
+ path: ^1.8.3
+ tonik_util: ^0.0.7
+
+dev_dependencies:
+ test: ^1.24.0
+ very_good_analysis: ^9.0.0
+
diff --git a/integration_test/gov/gov_test/test/default_test.dart b/integration_test/gov/gov_test/test/default_test.dart
new file mode 100644
index 0000000..59efdeb
--- /dev/null
+++ b/integration_test/gov/gov_test/test/default_test.dart
@@ -0,0 +1,120 @@
+import 'package:dio/dio.dart';
+import 'package:gov_api/gov_api.dart';
+import 'package:test/test.dart';
+import 'package:tonik_util/tonik_util.dart';
+
+import 'test_helper.dart';
+
+void main() {
+ const port = 8080;
+ const baseUrl = 'http://localhost:$port';
+
+ late ImposterServer imposterServer;
+
+ setUpAll(() async {
+ imposterServer = ImposterServer(port: port);
+ await setupImposterServer(imposterServer);
+ });
+
+ DefaultApi buildAlbumsApi({required String responseStatus}) {
+ return DefaultApi(
+ CustomServer(
+ baseUrl: baseUrl,
+ serverConfig: ServerConfig(
+ baseOptions: BaseOptions(
+ headers: {'X-Response-Status': responseStatus},
+ ),
+ ),
+ ),
+ );
+ }
+
+ group('findForms', () {
+ test('200', () async {
+ final defaultApi = buildAlbumsApi(responseStatus: '200');
+
+ final response = await defaultApi.findForms(query: '10-10EZ');
+
+ expect(response, isA>());
+ final success = response as TonikSuccess;
+ expect(success.response.statusCode, 200);
+ expect(success.value, isA());
+
+ final value = success.value as FindFormsResponse200;
+ expect(value.body, isA());
+
+ final body = value.body;
+ expect(body.data, isA>());
+
+ final formIndex = body.data.first;
+ expect(formIndex.attributes?.benefitCategories, isA>());
+ expect(formIndex.id, isA());
+ expect(formIndex.$type, isA());
+
+ final attributes = formIndex.attributes;
+ final benefitCategory = attributes?.benefitCategories?.first;
+ expect(benefitCategory?.name, isA());
+ expect(benefitCategory?.description, isA());
+
+ expect(attributes?.deletedAt, isA());
+ expect(attributes?.firstIssuedOn, isA());
+ expect(attributes?.formDetailsUrl, isA());
+ expect(attributes?.formName, isA());
+ expect(attributes?.formToolIntro, isA());
+ expect(attributes?.formToolUrl, isA());
+ expect(attributes?.formType, isA());
+ expect(attributes?.formUsage, isA());
+ expect(attributes?.language, isA());
+ expect(attributes?.lastRevisionOn, isA());
+ expect(attributes?.lastSha256Change, isA());
+ expect(attributes?.pages, isA());
+ expect(attributes?.relatedForms, isA?>());
+ expect(attributes?.sha256, isA());
+ expect(attributes?.title, isA());
+ expect(attributes?.url, isA());
+ expect(attributes?.vaFormAdministration, isA());
+ expect(attributes?.validPdf, isA());
+ });
+
+ test('401', () async {
+ final defaultApi = buildAlbumsApi(responseStatus: '401');
+
+ final response = await defaultApi.findForms();
+
+ expect(response, isA>());
+ final success = response as TonikSuccess;
+
+ expect(success.response.statusCode, 401);
+ expect(success.value, isA());
+
+ final value = success.value as FindFormsResponse401;
+ expect(value.body, isA());
+
+ final body = value.body;
+ expect(body.message, isA());
+ });
+
+ test('429', () async {
+ final defaultApi = buildAlbumsApi(responseStatus: '429');
+
+ final response = await defaultApi.findForms();
+
+ expect(response, isA>());
+ final success = response as TonikSuccess;
+
+ expect(success.response.statusCode, 429);
+ expect(success.value, isA());
+
+ final value = success.value as FindFormsResponse429;
+ expect(value.body, isA());
+ });
+
+ test('unexpected status code', () async {
+ final defaultApi = buildAlbumsApi(responseStatus: '500');
+
+ final response = await defaultApi.findForms();
+
+ expect(response, isA>());
+ });
+ });
+}
diff --git a/integration_test/gov/gov_test/test/test_helper.dart b/integration_test/gov/gov_test/test/test_helper.dart
new file mode 100644
index 0000000..6347edb
--- /dev/null
+++ b/integration_test/gov/gov_test/test/test_helper.dart
@@ -0,0 +1,87 @@
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+import 'package:test/test.dart';
+
+class ImposterServer {
+ ImposterServer({required this.port});
+
+ Process? _process;
+ final int port;
+
+ Future start() async {
+ final imposterJar = path.join(
+ Directory.current.parent.parent.path,
+ 'imposter.jar',
+ );
+
+ if (!File(imposterJar).existsSync()) {
+ throw Exception(
+ 'Imposter JAR not found at $imposterJar. Please download it first.',
+ );
+ }
+
+ _process = await Process.start(
+ 'java',
+ [
+ '-jar',
+ imposterJar,
+ '--listenPort',
+ port.toString(),
+ '--configDir',
+ path.join(Directory.current.path, 'imposter'),
+ '--plugin',
+ 'openapi',
+ '--plugin',
+ 'rest',
+ ],
+ environment: {
+ ...Platform.environment,
+ 'IMPOSTER_LOG_LEVEL': 'DEBUG',
+ },
+ );
+
+ _process!.stdout.transform(const Utf8Decoder()).listen((data) {
+ print('Imposter stdout: $data');
+ });
+ _process!.stderr.transform(const Utf8Decoder()).listen((data) {
+ print('Imposter stderr: $data');
+ });
+
+ await _waitForImposterReady();
+ }
+
+ Future _waitForImposterReady({int timeoutSec = 10}) async {
+ final deadline = DateTime.now().add(Duration(seconds: timeoutSec));
+ final client = HttpClient();
+
+ while (DateTime.now().isBefore(deadline)) {
+ try {
+ final request = await client.getUrl(
+ Uri.parse('http://localhost:$port'),
+ );
+ await request.close();
+
+ return true; // No exception means the server is ready.
+ } on SocketException catch (_) {
+ // ignore
+ }
+ await Future.delayed(const Duration(milliseconds: 300));
+ }
+ return false;
+ }
+
+ Future stop() async {
+ if (_process != null) {
+ _process!.kill();
+ await _process!.exitCode;
+ _process = null;
+ }
+ }
+}
+
+Future setupImposterServer(ImposterServer server) async {
+ await server.start();
+ addTearDown(() => server.stop());
+}
diff --git a/integration_test/gov/openapi.yaml b/integration_test/gov/openapi.yaml
new file mode 100644
index 0000000..5d1c219
--- /dev/null
+++ b/integration_test/gov/openapi.yaml
@@ -0,0 +1,431 @@
+openapi: 3.0.0
+servers:
+ - description: VA.gov API sandbox environment
+ url: https://sandbox-api.va.gov/services/va_forms/{version}
+ variables:
+ version:
+ default: v0
+ - description: VA.gov API production environment
+ url: https://api.va.gov/services/va_forms/{version}
+ variables:
+ version:
+ default: v0
+info:
+ contact:
+ name: va.gov
+ description: |
+ Use the VA Forms API to search for VA forms, get the form's PDF link and metadata, and check for new versions.
+
+ Visit our VA Lighthouse [Contact Us page](https://developer.va.gov/support) for further assistance.
+
+ ## Background
+ This API offers an efficient way to stay up-to-date with the latest VA forms and information. The forms information listed on VA.gov matches the information returned by this API.
+ - Search by form number, keyword, or title
+ - Get a link to the form in PDF format
+ - Get detailed form metadata including the number of pages, related forms, benefit categories, language, and more
+ - Retrieve the latest date of PDF changes and the SHA256 checksum
+ - Identify when a form is deleted by the VA
+
+ ## Technical summary
+ The VA Forms API collects form data from the official VA Form Repository on a nightly basis. The Index endpoint can return all available forms or, if an optional query parameter is passed, will return only forms that may relate to the query value. When a valid form name is passed to the Show endpoint, it will return a single form with additional metadata and full revision history. A JSON response is given with the PDF link (if published) and the corresponding form metadata.
+
+ ### Authentication and authorization
+ The form information shared by this API is publicly available. API requests are authorized through a symmetric API token, provided in an HTTP header with name apikey. [Get a sandbox API Key](https://developer.va.gov/apply).
+
+ ### Testing in sandbox environment
+ Form data in the sandbox environment is for testing your API only, and is not guaranteed to be up-to-date. This API also has a reduced API rate limit. When you're ready to move to production, be sure to [request a production API key.](https://developer.va.gov/go-live)
+
+ ### SHA256 revision history
+ Each form is checked nightly for recent file changes. A corresponding SHA256 checksum is calculated, which provides a record of when the PDF changed and the SHA256 hash that was calculated. This allows end users to know that they have the most recent version and can verify the integrity of a previously downloaded PDF.
+
+ ### Valid PDF link
+ Additionally, during the nightly refresh process, the link to the form PDF is verified and the `valid_pdf` metadata is updated accordingly. If marked `true`, the link is valid and is a current form. If marked `false`, the link is either broken or the form has been removed.
+
+ ### Deleted forms
+ If the `deleted_at` metadata is set, that means the VA has removed this form from the repository and it is no longer to be used.
+ title: VA Forms
+ version: 0.0.0
+ x-apisguru-categories:
+ - forms
+ x-logo:
+ url: https://prod-va-gov-assets.s3-us-gov-west-1.amazonaws.com/img/design/icons/apple-touch-icon.png
+ x-origin:
+ - format: openapi
+ url: https://api.va.gov/services/va_forms/docs/v0/api
+ version: "3.0"
+ x-providerName: va.gov
+ x-serviceName: forms
+paths:
+ /forms:
+ get:
+ description: Returns an index of all available VA forms. Optionally, pass a query parameter to filter forms by form number or title.
+ operationId: findForms
+ parameters:
+ - description: Returns form data based on entered form name.
+ in: query
+ name: query
+ required: false
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ properties:
+ data:
+ items:
+ $ref: "#/components/schemas/FormsIndex"
+ type: array
+ required:
+ - data
+ type: object
+ description: VA Forms index response
+ "401":
+ content:
+ application/json:
+ schema:
+ properties:
+ message:
+ example: Invalid authentication credentials
+ type: string
+ description: Unauthorized
+ "429":
+ content:
+ application/json:
+ schema:
+ properties:
+ message:
+ example: API rate limit exceeded
+ type: string
+ description: Too many requests
+ security:
+ - apikey: []
+ summary: Returns all VA Forms and their last revision date
+ tags:
+ - Forms
+ "/forms/{form_name}":
+ get:
+ description: Returns a single form and the full revision history
+ operationId: findFormByFormName
+ parameters:
+ - description: The VA form_name of the form being requested. The exact form name must be passed, including proper placement of prefixes and/or hyphens.
+ example: 10-10EZ
+ in: path
+ name: form_name
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ content:
+ application/json:
+ schema:
+ properties:
+ data:
+ $ref: "#/components/schemas/FormShow"
+ required:
+ - data
+ type: object
+ description: VA Form Show response
+ "401":
+ content:
+ application/json:
+ schema:
+ properties:
+ message:
+ example: Invalid authentication credentials
+ type: string
+ description: Unauthorized
+ "404":
+ content:
+ application/json:
+ schema:
+ properties:
+ errors:
+ items:
+ properties:
+ message:
+ example: Form not found
+ type: string
+ type: array
+ required:
+ - errors
+ type: object
+ description: Not Found
+ "429":
+ content:
+ application/json:
+ schema:
+ properties:
+ message:
+ example: API rate limit exceeded
+ type: string
+ description: Too many requests
+ security:
+ - apikey: []
+ summary: Find form by form name
+ tags:
+ - Forms
+components:
+ examples: {}
+ links: {}
+ parameters: {}
+ requestBodies: {}
+ responses: {}
+ schemas:
+ FormShow:
+ description: Data for a particular VA form, including form version history.
+ properties:
+ attributes:
+ properties:
+ benefit_categories:
+ description: Listing of benefit categories and match
+ items:
+ properties:
+ description:
+ description: Description of the benefit category of the form
+ example: VA health care
+ type: string
+ name:
+ description: Name of the benefit category of the form
+ example: Health care
+ type: string
+ nullable: true
+ type: array
+ created_at:
+ description: Internal field for VA.gov use
+ example: 2021-03-30T16:28:30.338Z
+ format: date-time
+ nullable: true
+ type: string
+ deleted_at:
+ description: The timestamp at which the form was deleted
+ example: null
+ format: date-time
+ nullable: true
+ type: string
+ first_issued_on:
+ description: The date the form first became available
+ example: 2016-07-10
+ format: date
+ nullable: true
+ type: string
+ form_details_url:
+ description: Location on www.va.gov of the info page for this form
+ example: https://www.va.gov/find-forms/about-form-10-10ez
+ nullable: true
+ type: string
+ form_name:
+ description: Name of the VA Form
+ example: 10-10EZ
+ type: string
+ form_tool_intro:
+ description: Introductory text describing the VA online tool for this form
+ example: You can apply online instead of filling out and sending us the paper form.
+ nullable: true
+ type: string
+ form_tool_url:
+ description: Location of the online tool for this form
+ example: https://www.va.gov/health-care/apply/application/introduction
+ nullable: true
+ type: string
+ form_type:
+ description: VA Type of the form
+ example: benefit
+ nullable: true
+ type: string
+ form_usage:
+ description: A description of how the form is to be used
+ example: Use VA Form 10-10EZ if you’re a Veteran and want to apply for VA health care. You must be enrolled in...
+ nullable: true
+ type: string
+ language:
+ description: Language code of the form
+ example: en
+ nullable: true
+ type: string
+ last_revision_on:
+ description: The date the form was last updated
+ example: 2020-01-17
+ format: date
+ nullable: true
+ type: string
+ pages:
+ description: Number of pages contained in the form
+ example: 5
+ type: integer
+ related_forms:
+ description: A listing of other forms that relate to current form
+ items:
+ example: 10-10EZR
+ type: string
+ nullable: true
+ type: array
+ sha256:
+ description: A sha256 hash of the form contents
+ example: 5fe171299ece147e8b456961a38e17f1391026f26e9e170229317bc95d9827b7
+ nullable: true
+ type: string
+ title:
+ description: Title of the form as given by VA
+ example: Instructions and Enrollment Application for Health Benefits
+ type: string
+ url:
+ description: Web location of the form
+ example: https://www.va.gov/vaforms/medical/pdf/10-10EZ-fillable.pdf
+ type: string
+ va_form_administration:
+ description: The VA organization that administers the form
+ example: Veterans Health Administration
+ nullable: true
+ type: string
+ valid_pdf:
+ description: A flag indicating whether the form url was confirmed as a valid download
+ example: "true"
+ type: boolean
+ versions:
+ description: The version history of revisions to the form
+ items:
+ properties:
+ revision_on:
+ description: The date the sha256 hash was calculated
+ example: 2012-01-01
+ format: date
+ type: string
+ sha256:
+ description: A sha256 hash of the form contents for that version
+ example: 5fe171299ece147e8b456961a38e17f1391026f26e9e170229317bc95d9827b7
+ type: string
+ nullable: true
+ type: array
+ id:
+ description: JSON API identifier
+ example: 10-10-EZ
+ type: string
+ type:
+ description: JSON API type specification
+ example: va_form
+ type: string
+ FormsIndex:
+ description: A listing of available VA forms and their location.
+ properties:
+ attributes:
+ properties:
+ benefit_categories:
+ description: Listing of benefit categories and match
+ items:
+ properties:
+ description:
+ description: Description of the benefit category of the form
+ example: VA health care
+ type: string
+ name:
+ description: Name of the benefit category of the form
+ example: Health care
+ type: string
+ nullable: true
+ type: array
+ deleted_at:
+ description: The timestamp at which the form was deleted
+ example: "null"
+ format: date-time
+ nullable: true
+ type: string
+ first_issued_on:
+ description: The date the form first became available
+ example: 2016-07-10
+ format: date
+ nullable: true
+ type: string
+ form_details_url:
+ description: Location on www.va.gov of the info page for this form
+ example: https://www.va.gov/find-forms/about-form-10-10ez
+ nullable: true
+ type: string
+ form_name:
+ description: Name of the VA Form
+ example: 10-10EZ
+ type: string
+ form_tool_intro:
+ description: Introductory text describing the VA online tool for this form
+ example: You can apply online instead of filling out and sending us the paper form.
+ nullable: true
+ type: string
+ form_tool_url:
+ description: Location of the online tool for this form
+ example: https://www.va.gov/health-care/apply/application/introduction
+ nullable: true
+ type: string
+ form_type:
+ description: VA Type of the form
+ example: benefit
+ nullable: true
+ type: string
+ form_usage:
+ description: A description of how the form is to be used
+ example: Use VA Form 10-10EZ if you’re a Veteran and want to apply for VA health care. You must be enrolled in...
+ nullable: true
+ type: string
+ language:
+ description: Language code of the form
+ example: en
+ type: string
+ last_revision_on:
+ description: The date the form was last updated
+ example: 2020-01-17
+ format: date
+ nullable: true
+ type: string
+ last_sha256_change:
+ description: The date of the last sha256 hash change
+ example: 2019-05-30
+ format: date
+ nullable: true
+ type: string
+ pages:
+ description: Number of pages contained in the form
+ example: 5
+ type: integer
+ related_forms:
+ description: A listing of other forms that relate to current form
+ items:
+ example: 10-10EZR
+ type: string
+ nullable: true
+ type: array
+ sha256:
+ description: A sha256 hash of the form contents
+ example: 6e6465e2e1c89225871daa9b6d86b92d1c263c7b02f98541212af7b35272372b
+ nullable: true
+ type: string
+ title:
+ description: Title of the form as given by VA
+ example: Instructions and Enrollment Application for Health Benefits
+ type: string
+ url:
+ description: Web location of the form
+ example: https://www.va.gov/vaforms/medical/pdf/10-10EZ-fillable.pdf
+ type: string
+ va_form_administration:
+ description: The VA organization that administers the form
+ example: Veterans Health Administration
+ nullable: true
+ type: string
+ valid_pdf:
+ description: A flag indicating whether the form url was confirmed as a valid download
+ example: "true"
+ type: boolean
+ id:
+ description: JSON API identifier
+ example: "5403"
+ type: string
+ type:
+ description: JSON API type specification
+ example: va_form
+ type: string
+ securitySchemes:
+ apikey:
+ in: header
+ name: apikey
+ type: apiKey
\ No newline at end of file
diff --git a/integration_test/setup.sh b/integration_test/setup.sh
index a87b847..472b9c6 100755
--- a/integration_test/setup.sh
+++ b/integration_test/setup.sh
@@ -21,6 +21,9 @@ cd petstore/petstore_api && dart pub get && cd ../..
dart run ../packages/tonik/bin/tonik.dart -p music_streaming_api -s music_streaming/openapi.yaml -o music_streaming --log-level verbose
cd music_streaming/music_streaming_api && dart pub get && cd ../..
+dart run ../packages/tonik/bin/tonik.dart -p gov_api -s gov/openapi.yaml -o gov --log-level verbose
+cd gov/gov_api && dart pub get && cd ../..
+
# Download Imposter JAR only if it doesn't exist
if [ ! -f imposter.jar ]; then
echo "Downloading Imposter JAR..."
From 2929544c80ee1882d07420343b4cb2b4b1495069 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Sun, 15 Jun 2025 17:13:30 +0200
Subject: [PATCH 13/31] chore: prep release 0.0.6
---
integration_test/gov/gov_test/pubspec.yaml | 2 +-
packages/tonik_generate/lib/src/pubspec_generator.dart | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/integration_test/gov/gov_test/pubspec.yaml b/integration_test/gov/gov_test/pubspec.yaml
index da73204..b4c67f7 100644
--- a/integration_test/gov/gov_test/pubspec.yaml
+++ b/integration_test/gov/gov_test/pubspec.yaml
@@ -11,7 +11,7 @@ dependencies:
gov_api:
path: ../gov_api
path: ^1.8.3
- tonik_util: ^0.0.7
+ tonik_util: ^0.0.6
dev_dependencies:
test: ^1.24.0
diff --git a/packages/tonik_generate/lib/src/pubspec_generator.dart b/packages/tonik_generate/lib/src/pubspec_generator.dart
index 257a253..7bb991e 100644
--- a/packages/tonik_generate/lib/src/pubspec_generator.dart
+++ b/packages/tonik_generate/lib/src/pubspec_generator.dart
@@ -27,7 +27,7 @@ dependencies:
dio: ^5.8.0+1
lints: ^6.0.0
meta: ^1.16.0
- tonik_util: ^0.0.5
+ tonik_util: ^0.0.6
''';
pubspecFile.writeAsStringSync(content);
From 8dec4bee938d6c1ccda9c4d0a332cb0609e67219 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Sun, 15 Jun 2025 17:14:51 +0200
Subject: [PATCH 14/31] chore(release): publish packages
- tonik@0.0.6
- tonik_core@0.0.6
- tonik_generate@0.0.6
- tonik_parse@0.0.6
- tonik_util@0.0.6
---
CHANGELOG.md | 38 ++++++++++++++++++++++++++++
packages/tonik/CHANGELOG.md | 2 ++
packages/tonik/pubspec.yaml | 8 +++---
packages/tonik_core/CHANGELOG.md | 2 ++
packages/tonik_core/pubspec.yaml | 2 +-
packages/tonik_generate/CHANGELOG.md | 7 +++++
packages/tonik_generate/pubspec.yaml | 4 +--
packages/tonik_parse/CHANGELOG.md | 2 ++
packages/tonik_parse/pubspec.yaml | 4 +--
packages/tonik_util/CHANGELOG.md | 4 +++
packages/tonik_util/pubspec.yaml | 2 +-
11 files changed, 65 insertions(+), 10 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d181de6..2bb3ab4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,44 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## 2025-06-15
+
+### Changes
+
+---
+
+Packages with breaking changes:
+
+ - There are no breaking changes in this release.
+
+Packages with other changes:
+
+ - [`tonik` - `v0.0.6`](#tonik---v006)
+ - [`tonik_core` - `v0.0.6`](#tonik_core---v006)
+ - [`tonik_generate` - `v0.0.6`](#tonik_generate---v006)
+ - [`tonik_parse` - `v0.0.6`](#tonik_parse---v006)
+ - [`tonik_util` - `v0.0.6`](#tonik_util---v006)
+
+---
+
+#### `tonik` - `v0.0.6`
+
+#### `tonik_core` - `v0.0.6`
+
+#### `tonik_generate` - `v0.0.6`
+
+ - **FIX**: proper handle dates.
+ - **FIX**: priority for exlict defined names of schemas.
+ - **FIX**: prio for explicitly defined names.
+ - **FIX**: proper hash code for classes with >20 properties.
+
+#### `tonik_parse` - `v0.0.6`
+
+#### `tonik_util` - `v0.0.6`
+
+ - **FIX**: proper handle dates.
+
+
## 2025-06-02
### Changes
diff --git a/packages/tonik/CHANGELOG.md b/packages/tonik/CHANGELOG.md
index c83e019..a9ba185 100644
--- a/packages/tonik/CHANGELOG.md
+++ b/packages/tonik/CHANGELOG.md
@@ -1,3 +1,5 @@
+## 0.0.6
+
## 0.0.5
## 0.0.4
diff --git a/packages/tonik/pubspec.yaml b/packages/tonik/pubspec.yaml
index 858d73c..df60006 100644
--- a/packages/tonik/pubspec.yaml
+++ b/packages/tonik/pubspec.yaml
@@ -1,6 +1,6 @@
name: tonik
description: A Dart code generator for OpenAPI 3.0 and 3.1 specifications.
-version: 0.0.5
+version: 0.0.6
repository: https://github.com/t-unit/tonik
resolution: workspace
@@ -20,9 +20,9 @@ environment:
dependencies:
args: ^2.5.0
logging: ^1.3.0
- tonik_core: ^0.0.5
- tonik_generate: ^0.0.5
- tonik_parse: ^0.0.5
+ tonik_core: ^0.0.6
+ tonik_generate: ^0.0.6
+ tonik_parse: ^0.0.6
yaml: ^3.1.3
dev_dependencies:
diff --git a/packages/tonik_core/CHANGELOG.md b/packages/tonik_core/CHANGELOG.md
index 47f0e8a..9396082 100644
--- a/packages/tonik_core/CHANGELOG.md
+++ b/packages/tonik_core/CHANGELOG.md
@@ -1,3 +1,5 @@
+## 0.0.6
+
## 0.0.5
## 0.0.4
diff --git a/packages/tonik_core/pubspec.yaml b/packages/tonik_core/pubspec.yaml
index d7939b9..53e6254 100644
--- a/packages/tonik_core/pubspec.yaml
+++ b/packages/tonik_core/pubspec.yaml
@@ -1,6 +1,6 @@
name: tonik_core
description: Core data structures and utilities for Tonik.
-version: 0.0.5
+version: 0.0.6
repository: https://github.com/t-unit/tonik
resolution: workspace
diff --git a/packages/tonik_generate/CHANGELOG.md b/packages/tonik_generate/CHANGELOG.md
index 7149c21..0eb61b4 100644
--- a/packages/tonik_generate/CHANGELOG.md
+++ b/packages/tonik_generate/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 0.0.6
+
+ - **FIX**: proper handle dates.
+ - **FIX**: priority for exlict defined names of schemas.
+ - **FIX**: prio for explicitly defined names.
+ - **FIX**: proper hash code for classes with >20 properties.
+
## 0.0.5
## 0.0.4
diff --git a/packages/tonik_generate/pubspec.yaml b/packages/tonik_generate/pubspec.yaml
index aadd59d..ef81ba1 100644
--- a/packages/tonik_generate/pubspec.yaml
+++ b/packages/tonik_generate/pubspec.yaml
@@ -1,6 +1,6 @@
name: tonik_generate
description: A code generation package for Tonik.
-version: 0.0.5
+version: 0.0.6
repository: https://github.com/t-unit/tonik
resolution: workspace
@@ -16,7 +16,7 @@ dependencies:
meta: ^1.16.0
path: ^1.9.1
spell_out_numbers: ^1.0.0
- tonik_core: ^0.0.5
+ tonik_core: ^0.0.6
dev_dependencies:
test: ^1.24.0
diff --git a/packages/tonik_parse/CHANGELOG.md b/packages/tonik_parse/CHANGELOG.md
index ca6a9fc..85629aa 100644
--- a/packages/tonik_parse/CHANGELOG.md
+++ b/packages/tonik_parse/CHANGELOG.md
@@ -1,3 +1,5 @@
+## 0.0.6
+
## 0.0.5
## 0.0.4
diff --git a/packages/tonik_parse/pubspec.yaml b/packages/tonik_parse/pubspec.yaml
index dec966c..0dfe1a0 100644
--- a/packages/tonik_parse/pubspec.yaml
+++ b/packages/tonik_parse/pubspec.yaml
@@ -1,6 +1,6 @@
name: tonik_parse
description: The parsing module for Tonik.
-version: 0.0.5
+version: 0.0.6
repository: https://github.com/t-unit/tonik
resolution: workspace
@@ -11,7 +11,7 @@ dependencies:
collection: ^1.19.1
json_annotation: ^4.9.0
logging: ^1.3.0
- tonik_core: ^0.0.5
+ tonik_core: ^0.0.6
dev_dependencies:
build_runner: ^2.3.3
diff --git a/packages/tonik_util/CHANGELOG.md b/packages/tonik_util/CHANGELOG.md
index b0c37af..8a18a94 100644
--- a/packages/tonik_util/CHANGELOG.md
+++ b/packages/tonik_util/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.0.6
+
+ - **FIX**: proper handle dates.
+
## 0.0.5
## 0.0.4
diff --git a/packages/tonik_util/pubspec.yaml b/packages/tonik_util/pubspec.yaml
index babc13d..2bccc0a 100644
--- a/packages/tonik_util/pubspec.yaml
+++ b/packages/tonik_util/pubspec.yaml
@@ -1,6 +1,6 @@
name: tonik_util
description: Runtime tools for packages generated by Tonik.
-version: 0.0.5
+version: 0.0.6
repository: https://github.com/t-unit/tonik
resolution: workspace
From 310d96533147a201cdb2278dc946d54684f5fac1 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Sun, 22 Jun 2025 17:56:58 +0200
Subject: [PATCH 15/31] chore: declare platform support
---
packages/tonik/pubspec.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/packages/tonik/pubspec.yaml b/packages/tonik/pubspec.yaml
index df60006..442cb3f 100644
--- a/packages/tonik/pubspec.yaml
+++ b/packages/tonik/pubspec.yaml
@@ -14,6 +14,14 @@ topics:
- rest
- swagger
+platforms:
+ android:
+ ios:
+ linux:
+ macos:
+ web:
+ windows:
+
environment:
sdk: ">=3.7.0 <4.0.0"
From ec5a8bb4ac8955ad888278031ac96dfa163eae4b Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Sun, 22 Jun 2025 18:52:41 +0200
Subject: [PATCH 16/31] feat: drop api prefix from generated server class.
Many APIs use an `api` subdomain. This results in `ApiServer` and `ApiServer2` getting generated. With this change we this will becom `Server` and `ApiServer` and much better user experience.
---
.../music_streaming_test/pubspec.lock | 4 +--
.../lib/src/naming/name_generator.dart | 4 +--
.../test/src/naming/name_generator_test.dart | 30 +++++++++----------
.../server/server_file_generator_test.dart | 6 ++--
.../src/server/server_generator_test.dart | 6 ++--
5 files changed, 25 insertions(+), 25 deletions(-)
diff --git a/integration_test/music_streaming/music_streaming_test/pubspec.lock b/integration_test/music_streaming/music_streaming_test/pubspec.lock
index 0c8693c..8e8b3d4 100644
--- a/integration_test/music_streaming/music_streaming_test/pubspec.lock
+++ b/integration_test/music_streaming/music_streaming_test/pubspec.lock
@@ -388,10 +388,10 @@ packages:
dependency: "direct main"
description:
name: tonik_util
- sha256: f16c86d5349fac40893d1d8ae87f6507192d8886f16ba6acefeabef4a61ae3ef
+ sha256: d1fc175e99d440d654d4be8b78f008d2d0614820fb8d9f40507cd3e5ee34fcca
url: "https://pub.dev"
source: hosted
- version: "0.0.5"
+ version: "0.0.6"
typed_data:
dependency: transitive
description:
diff --git a/packages/tonik_generate/lib/src/naming/name_generator.dart b/packages/tonik_generate/lib/src/naming/name_generator.dart
index 937f658..2547e1c 100644
--- a/packages/tonik_generate/lib/src/naming/name_generator.dart
+++ b/packages/tonik_generate/lib/src/naming/name_generator.dart
@@ -376,7 +376,7 @@ class NameGenerator {
({String baseName, Map serverMap, String customName})
_generateServerNames(List servers, List uniqueNames) {
- final baseName = _makeUnique('ApiServer', '');
+ final baseName = _makeUnique('Server', '');
final resultMap = {};
for (var index = 0; index < servers.length; index++) {
@@ -398,7 +398,7 @@ class NameGenerator {
({String baseName, Map serverMap, String customName})
_generateFallbackServerNames(List servers) {
- final baseName = _makeUnique('ApiServer', '');
+ final baseName = _makeUnique('Server', '');
final resultMap = {};
for (final server in servers) {
diff --git a/packages/tonik_generate/test/src/naming/name_generator_test.dart b/packages/tonik_generate/test/src/naming/name_generator_test.dart
index 8e2e473..8c35cff 100644
--- a/packages/tonik_generate/test/src/naming/name_generator_test.dart
+++ b/packages/tonik_generate/test/src/naming/name_generator_test.dart
@@ -1075,11 +1075,11 @@ void main() {
final result = generator.generateServerNames(servers);
expect(result.serverMap.length, 3);
- expect(result.serverMap[servers[0]], 'ApiServer2');
+ expect(result.serverMap[servers[0]], 'ApiServer');
expect(result.serverMap[servers[1]], 'StagingServer');
expect(result.serverMap[servers[2]], 'DevServer');
expect(result.customName, 'CustomServer');
- expect(result.baseName, 'ApiServer');
+ expect(result.baseName, 'Server');
});
test('generates names based on multi-level subdomain differences', () {
@@ -1100,7 +1100,7 @@ void main() {
expect(result.serverMap[servers[1]], 'ApiStagingServer');
expect(result.serverMap[servers[2]], 'ApiProdServer');
expect(result.customName, 'CustomServer');
- expect(result.baseName, 'ApiServer');
+ expect(result.baseName, 'Server');
});
test(
@@ -1120,7 +1120,7 @@ void main() {
expect(result.serverMap[servers[1]], 'AcmeServer');
expect(result.serverMap[servers[2]], 'TestServer');
expect(result.customName, 'CustomServer');
- expect(result.baseName, 'ApiServer');
+ expect(result.baseName, 'Server');
},
);
@@ -1140,7 +1140,7 @@ void main() {
expect(result.serverMap[servers[1]], 'V2Server');
expect(result.serverMap[servers[2]], 'BetaServer');
expect(result.customName, 'CustomServer');
- expect(result.baseName, 'ApiServer');
+ expect(result.baseName, 'Server');
});
test(
@@ -1156,11 +1156,11 @@ void main() {
final result = generator.generateServerNames(servers);
expect(result.serverMap.length, 3);
- expect(result.serverMap[servers[0]], 'Server');
- expect(result.serverMap[servers[1]], 'Server2');
- expect(result.serverMap[servers[2]], 'Server3');
+ expect(result.serverMap[servers[0]], 'Server2');
+ expect(result.serverMap[servers[1]], 'Server3');
+ expect(result.serverMap[servers[2]], 'Server4');
expect(result.customName, 'CustomServer');
- expect(result.baseName, 'ApiServer');
+ expect(result.baseName, 'Server');
},
);
@@ -1180,7 +1180,7 @@ void main() {
expect(result.serverMap.length, 1);
expect(result.serverMap[servers[0]], 'CustomServer');
expect(result.customName, r'CustomServer$');
- expect(result.baseName, 'ApiServer');
+ expect(result.baseName, 'Server');
},
);
@@ -1199,12 +1199,12 @@ void main() {
final result = generator.generateServerNames(servers);
expect(result.serverMap.length, 4);
- expect(result.serverMap[servers[0]], 'Server');
- expect(result.serverMap[servers[1]], 'Server2');
- expect(result.serverMap[servers[2]], 'Server3');
- expect(result.serverMap[servers[3]], 'Server4');
+ expect(result.serverMap[servers[0]], 'Server2');
+ expect(result.serverMap[servers[1]], 'Server3');
+ expect(result.serverMap[servers[2]], 'Server4');
+ expect(result.serverMap[servers[3]], 'Server5');
expect(result.customName, 'CustomServer');
- expect(result.baseName, 'ApiServer');
+ expect(result.baseName, 'Server');
});
});
});
diff --git a/packages/tonik_generate/test/src/server/server_file_generator_test.dart b/packages/tonik_generate/test/src/server/server_file_generator_test.dart
index 580bd63..32de2f7 100644
--- a/packages/tonik_generate/test/src/server/server_file_generator_test.dart
+++ b/packages/tonik_generate/test/src/server/server_file_generator_test.dart
@@ -109,10 +109,10 @@ void main() {
final fileContent = File(generatedFile.path).readAsStringSync();
// Check file name
- expect(actualFileName, equals('api_server.dart'));
+ expect(actualFileName, equals('server.dart'));
// Check file content
- expect(fileContent, contains('sealed class ApiServer'));
+ expect(fileContent, contains('sealed class Server'));
expect(fileContent, contains('class ProductionServer'));
expect(fileContent, contains('class StagingServer'));
expect(fileContent, contains('class CustomServer'));
@@ -154,7 +154,7 @@ void main() {
final fileContent = File(generatedFile.path).readAsStringSync();
// Expect base class and custom class to be generated
- expect(fileContent, contains('sealed class ApiServer'));
+ expect(fileContent, contains('sealed class Server'));
expect(fileContent, contains('class CustomServer'));
// No server-specific classes should be present
diff --git a/packages/tonik_generate/test/src/server/server_generator_test.dart b/packages/tonik_generate/test/src/server/server_generator_test.dart
index 8f32742..97e912a 100644
--- a/packages/tonik_generate/test/src/server/server_generator_test.dart
+++ b/packages/tonik_generate/test/src/server/server_generator_test.dart
@@ -110,7 +110,7 @@ void main() {
final productionClass = generatedClasses[1];
expect(productionClass.name, 'ProductionServer');
- expect(productionClass.extend?.accept(emitter).toString(), 'ApiServer');
+ expect(productionClass.extend?.accept(emitter).toString(), 'Server');
expect(
productionClass.docs.first,
'/// Production server - https://production.example.com',
@@ -144,7 +144,7 @@ void main() {
final stagingClass = generatedClasses[2];
expect(stagingClass.name, 'StagingServer');
- expect(stagingClass.extend?.accept(emitter).toString(), 'ApiServer');
+ expect(stagingClass.extend?.accept(emitter).toString(), 'Server');
expect(
stagingClass.docs.first,
'/// Staging server - https://staging.example.com',
@@ -180,7 +180,7 @@ void main() {
final customClass = generatedClasses.last;
expect(customClass.name, 'CustomServer');
- expect(customClass.extend?.accept(emitter).toString(), 'ApiServer');
+ expect(customClass.extend?.accept(emitter).toString(), 'Server');
expect(
customClass.docs.first,
'/// Custom server with user-defined base URL',
From 1d6d61a71f15a29884508b49a18091db25a09570 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Sun, 22 Jun 2025 18:54:01 +0200
Subject: [PATCH 17/31] chore: cleanup readme
---
packages/tonik/README.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/packages/tonik/README.md b/packages/tonik/README.md
index 5f80f6f..c6b1c09 100644
--- a/packages/tonik/README.md
+++ b/packages/tonik/README.md
@@ -97,7 +97,6 @@ For a full list of changes of each release, refer to [release notes](https://git
### Short term goals
- `allowReserved` support for query parameters
- `format: uri` mapping to Dart `Uri`
-- Add custom `Date` model in util package to handle `format: date` properly
- More E2E tests
- Full decoding and encoding support for any of and one of
- Support for `x-dart-name`, `x-dart-type` and `x-dart-enums`
From 51c3b93c5d1d80c0fa18ad4165b72b04c6092407 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Sun, 20 Jul 2025 11:12:27 +0200
Subject: [PATCH 18/31] feat: more verbose decimal parsing
---
packages/tonik_parse/lib/src/model_importer.dart | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/packages/tonik_parse/lib/src/model_importer.dart b/packages/tonik_parse/lib/src/model_importer.dart
index 12bb191..5ec0bd0 100644
--- a/packages/tonik_parse/lib/src/model_importer.dart
+++ b/packages/tonik_parse/lib/src/model_importer.dart
@@ -119,7 +119,13 @@ class ModelImporter {
context: context,
),
'string' when schema.format == 'date' => DateModel(context: context),
- 'string' when schema.format == 'decimal' || schema.format == 'currency' =>
+ 'string'
+ when [
+ 'decimal',
+ 'currency',
+ 'money',
+ 'number',
+ ].contains(schema.format) =>
DecimalModel(context: context),
'string' when schema.enumerated != null => _parseEnum(
name,
From 76a6143535a7abcbf6bdc8589a675ab95e5e5eb5 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Sun, 20 Jul 2025 12:48:53 +0200
Subject: [PATCH 19/31] feat: time zone aware encoding of date time objects
---
.../to_json_value_expression_generator.dart | 2 +-
.../src/model/class_json_generator_test.dart | 6 +-
.../src/operation/options_generator_test.dart | 2 +-
..._json_value_expression_generator_test.dart | 18 +-
.../lib/src/encoding/base_encoder.dart | 4 +-
.../lib/src/encoding/datetime_extension.dart | 60 +++++++
packages/tonik_util/lib/tonik_util.dart | 1 +
packages/tonik_util/pubspec.yaml | 1 +
.../test/src/decoding/json_decoder_test.dart | 8 +-
.../src/decoding/simple_decoder_test.dart | 9 +-
.../src/encoder/datetime_extension_test.dart | 162 ++++++++++++++++++
11 files changed, 254 insertions(+), 19 deletions(-)
create mode 100644 packages/tonik_util/lib/src/encoding/datetime_extension.dart
create mode 100644 packages/tonik_util/test/src/encoder/datetime_extension_test.dart
diff --git a/packages/tonik_generate/lib/src/util/to_json_value_expression_generator.dart b/packages/tonik_generate/lib/src/util/to_json_value_expression_generator.dart
index 7d0a55d..80011cc 100644
--- a/packages/tonik_generate/lib/src/util/to_json_value_expression_generator.dart
+++ b/packages/tonik_generate/lib/src/util/to_json_value_expression_generator.dart
@@ -47,7 +47,7 @@ String? _getSerializationSuffix(Model model, bool isNullable) {
isNullable || (model is EnumModel && model.isNullable) ? '?' : '';
return switch (model) {
- DateTimeModel() => '$nullablePart.toIso8601String()',
+ DateTimeModel() => '$nullablePart.toTimeZonedIso8601String()',
DecimalModel() => '$nullablePart.toString()',
DateModel() ||
EnumModel() ||
diff --git a/packages/tonik_generate/test/src/model/class_json_generator_test.dart b/packages/tonik_generate/test/src/model/class_json_generator_test.dart
index 7fb5505..8aba69c 100644
--- a/packages/tonik_generate/test/src/model/class_json_generator_test.dart
+++ b/packages/tonik_generate/test/src/model/class_json_generator_test.dart
@@ -258,7 +258,7 @@ void main() {
const expectedMethod = '''
Object? toJson() => {
r'name': name,
- r'createdAt': createdAt.toIso8601String(),
+ r'createdAt': createdAt.toTimeZonedIso8601String(),
r'status': status.toJson(),
r'homeAddress': homeAddress?.toJson(),
};
@@ -323,7 +323,9 @@ void main() {
const expectedMethod = '''
Object? toJson() => {
r'tags': tags,
- r'meetingTimes': meetingTimes.map((e) => e.toIso8601String()).toList(),
+ r'meetingTimes': meetingTimes
+ .map((e) => e.toTimeZonedIso8601String())
+ .toList(),
r'addresses': addresses?.map((e) => e.toJson()).toList(),
};
''';
diff --git a/packages/tonik_generate/test/src/operation/options_generator_test.dart b/packages/tonik_generate/test/src/operation/options_generator_test.dart
index dd7b4cf..255633c 100644
--- a/packages/tonik_generate/test/src/operation/options_generator_test.dart
+++ b/packages/tonik_generate/test/src/operation/options_generator_test.dart
@@ -367,7 +367,7 @@ void main() {
allowEmpty: false,
);
headers[r'X-Required-Date'] = headerEncoder.encode(
- xRequiredDate.toIso8601String(),
+ xRequiredDate.toTimeZonedIso8601String(),
explode: false,
allowEmpty: true,
);
diff --git a/packages/tonik_generate/test/src/util/to_json_value_expression_generator_test.dart b/packages/tonik_generate/test/src/util/to_json_value_expression_generator_test.dart
index f54b21a..2c7e9e9 100644
--- a/packages/tonik_generate/test/src/util/to_json_value_expression_generator_test.dart
+++ b/packages/tonik_generate/test/src/util/to_json_value_expression_generator_test.dart
@@ -53,7 +53,7 @@ void main() {
);
expect(
buildToJsonPropertyExpression('startTime', property),
- 'startTime.toIso8601String()',
+ 'startTime.toTimeZonedIso8601String()',
);
});
@@ -188,7 +188,7 @@ void main() {
);
expect(
buildToJsonPropertyExpression('meetingTimes', property),
- 'meetingTimes.map((e) => e.toIso8601String()).toList()',
+ 'meetingTimes.map((e) => e.toTimeZonedIso8601String()).toList()',
);
});
@@ -259,7 +259,7 @@ void main() {
);
expect(
buildToJsonPropertyExpression('createdAt', property),
- 'createdAt.toIso8601String()',
+ 'createdAt.toTimeZonedIso8601String()',
);
});
@@ -278,7 +278,7 @@ void main() {
);
expect(
buildToJsonPropertyExpression('updatedAt', property),
- 'updatedAt?.toIso8601String()',
+ 'updatedAt?.toTimeZonedIso8601String()',
);
});
@@ -497,7 +497,7 @@ void main() {
);
expect(
buildToJsonPropertyExpression('meetingTimes', property),
- 'meetingTimes.map((e) => e.toIso8601String()).toList()',
+ 'meetingTimes.map((e) => e.toTimeZonedIso8601String()).toList()',
);
});
@@ -519,7 +519,7 @@ void main() {
);
expect(
buildToJsonPropertyExpression('meetingTimes', property),
- 'meetingTimes?.map((e) => e.toIso8601String()).toList()',
+ 'meetingTimes?.map((e) => e.toTimeZonedIso8601String()).toList()',
);
});
@@ -626,7 +626,7 @@ void main() {
);
expect(
buildToJsonPathParameterExpression('startTime', parameter),
- 'startTime.toIso8601String()',
+ 'startTime.toTimeZonedIso8601String()',
);
});
@@ -713,7 +713,7 @@ void main() {
);
expect(
buildToJsonQueryParameterExpression('startTime', parameter),
- 'startTime.toIso8601String()',
+ 'startTime.toTimeZonedIso8601String()',
);
});
@@ -779,7 +779,7 @@ void main() {
);
expect(
buildToJsonHeaderParameterExpression('timestamp', parameter),
- 'timestamp.toIso8601String()',
+ 'timestamp.toTimeZonedIso8601String()',
);
});
diff --git a/packages/tonik_util/lib/src/encoding/base_encoder.dart b/packages/tonik_util/lib/src/encoding/base_encoder.dart
index 145cefa..eb21e97 100644
--- a/packages/tonik_util/lib/src/encoding/base_encoder.dart
+++ b/packages/tonik_util/lib/src/encoding/base_encoder.dart
@@ -1,5 +1,5 @@
import 'package:meta/meta.dart';
-import 'package:tonik_util/src/encoding/encoding_exception.dart';
+import 'package:tonik_util/tonik_util.dart';
/// Base class for OpenAPI parameter style encoders.
///
@@ -84,7 +84,7 @@ abstract class BaseEncoder {
}
if (value is DateTime) {
- return value.toIso8601String();
+ return value.toTimeZonedIso8601String();
}
return value.toString();
diff --git a/packages/tonik_util/lib/src/encoding/datetime_extension.dart b/packages/tonik_util/lib/src/encoding/datetime_extension.dart
new file mode 100644
index 0000000..0561254
--- /dev/null
+++ b/packages/tonik_util/lib/src/encoding/datetime_extension.dart
@@ -0,0 +1,60 @@
+/// Extension on DateTime to provide timezone-aware encoding.
+///
+/// This extension ensures that DateTime objects are encoded with their full
+/// timezone information, unlike [DateTime.toIso8601String()] which only
+/// works correctly for UTC dates.
+extension DateTimeEncodingExtension on DateTime {
+ /// Encodes this DateTime to a string representation that preserves
+ /// timezone information.
+ ///
+ /// For UTC dates, this returns the same as [DateTime.toIso8601String()].
+ /// For local dates, this ensures the timezone offset is properly included.
+ String toTimeZonedIso8601String() {
+ if (isUtc) {
+ // For UTC dates, use toIso8601String which works correctly
+ return toIso8601String();
+ }
+
+ // For local dates, we need to include timezone offset
+ // Format the base date and time manually to match toIso8601String format
+ final year = this.year;
+ final month = _twoDigits(this.month);
+ final day = _twoDigits(this.day);
+ final hour = _twoDigits(this.hour);
+ final minute = _twoDigits(this.minute);
+ final second = _twoDigits(this.second);
+
+ // Add milliseconds if present
+ final millisecondString = millisecond > 0
+ ? '.${_threeDigits(millisecond)}'
+ : '';
+
+ // Add microseconds if present
+ final microsecondString = microsecond > 0
+ ? _threeDigits(microsecond)
+ : '';
+
+ // Get the timezone offset in hours and minutes
+ final offset = timeZoneOffset;
+ final offsetHours = offset.inHours.abs();
+ final offsetMinutes = offset.inMinutes.abs() % 60;
+
+ // Format timezone offset
+ final offsetSign = offset.isNegative ? '-' : '+';
+ final offsetString = '$offsetSign'
+ '${_twoDigits(offsetHours)}:${_twoDigits(offsetMinutes)}';
+
+ return '$year-$month-${day}T$hour:$minute:$second'
+ '$millisecondString$microsecondString$offsetString';
+ }
+
+ /// Formats a number as two digits with leading zero if needed.
+ static String _twoDigits(int n) {
+ return n.toString().padLeft(2, '0');
+ }
+
+ /// Formats a number as three digits with leading zeros if needed.
+ static String _threeDigits(int n) {
+ return n.toString().padLeft(3, '0');
+ }
+}
diff --git a/packages/tonik_util/lib/tonik_util.dart b/packages/tonik_util/lib/tonik_util.dart
index 936c273..98bbe84 100644
--- a/packages/tonik_util/lib/tonik_util.dart
+++ b/packages/tonik_util/lib/tonik_util.dart
@@ -6,6 +6,7 @@ export 'src/decoding/decoding_exception.dart';
export 'src/decoding/json_decoder.dart';
export 'src/decoding/simple_decoder.dart';
export 'src/dio/server_config.dart';
+export 'src/encoding/datetime_extension.dart';
export 'src/encoding/deep_object_encoder.dart';
export 'src/encoding/delimited_encoder.dart';
export 'src/encoding/encoding_exception.dart';
diff --git a/packages/tonik_util/pubspec.yaml b/packages/tonik_util/pubspec.yaml
index 2bccc0a..ac77faa 100644
--- a/packages/tonik_util/pubspec.yaml
+++ b/packages/tonik_util/pubspec.yaml
@@ -12,6 +12,7 @@ dependencies:
collection: ^1.19.1
dio: ^5.0.0
meta: ^1.16.0
+ timezone: ^0.10.1
dev_dependencies:
test: ^1.24.0
diff --git a/packages/tonik_util/test/src/decoding/json_decoder_test.dart b/packages/tonik_util/test/src/decoding/json_decoder_test.dart
index 1b21dc2..98038eb 100644
--- a/packages/tonik_util/test/src/decoding/json_decoder_test.dart
+++ b/packages/tonik_util/test/src/decoding/json_decoder_test.dart
@@ -5,13 +5,14 @@ import 'package:test/test.dart';
import 'package:tonik_util/src/date.dart';
import 'package:tonik_util/src/decoding/decoding_exception.dart';
import 'package:tonik_util/src/decoding/json_decoder.dart';
+import 'package:tonik_util/src/encoding/datetime_extension.dart';
void main() {
group('JsonDecoder', () {
group('DateTime', () {
test('decodes DateTime values', () {
final date = DateTime.utc(2024, 3, 14);
- expect(date.toIso8601String().decodeJsonDateTime(), date);
+ expect(date.toTimeZonedIso8601String().decodeJsonDateTime(), date);
expect(
() => 123.decodeJsonDateTime(),
throwsA(isA()),
@@ -24,7 +25,10 @@ void main() {
test('decodes nullable DateTime values', () {
final date = DateTime.utc(2024, 3, 14);
- expect(date.toIso8601String().decodeJsonNullableDateTime(), date);
+ expect(
+ date.toTimeZonedIso8601String().decodeJsonNullableDateTime(),
+ date,
+ );
expect(null.decodeJsonNullableDateTime(), isNull);
expect(''.decodeJsonNullableDateTime(), isNull);
expect(
diff --git a/packages/tonik_util/test/src/decoding/simple_decoder_test.dart b/packages/tonik_util/test/src/decoding/simple_decoder_test.dart
index 366b6a1..5fb8ac9 100644
--- a/packages/tonik_util/test/src/decoding/simple_decoder_test.dart
+++ b/packages/tonik_util/test/src/decoding/simple_decoder_test.dart
@@ -3,6 +3,7 @@ import 'package:test/test.dart';
import 'package:tonik_util/src/date.dart';
import 'package:tonik_util/src/decoding/decoding_exception.dart';
import 'package:tonik_util/src/decoding/simple_decoder.dart';
+import 'package:tonik_util/src/encoding/datetime_extension.dart';
void main() {
group('SimpleDecoder', () {
@@ -48,7 +49,7 @@ void main() {
test('decodes DateTime values', () {
final date = DateTime.utc(2024, 3, 14);
- expect(date.toIso8601String().decodeSimpleDateTime(), date);
+ expect(date.toTimeZonedIso8601String().decodeSimpleDateTime(), date);
expect(
() => 'not-a-date'.decodeSimpleDateTime(),
throwsA(isA()),
@@ -128,7 +129,11 @@ void main() {
expect('3.14'.decodeSimpleNullableDouble(), 3.14);
expect('true'.decodeSimpleNullableBool(), isTrue);
expect(
- '2024-03-14T00:00:00.000Z'.decodeSimpleNullableDateTime(),
+ DateTime.utc(
+ 2024,
+ 3,
+ 14,
+ ).toTimeZonedIso8601String().decodeSimpleNullableDateTime(),
DateTime.utc(2024, 3, 14),
);
expect(
diff --git a/packages/tonik_util/test/src/encoder/datetime_extension_test.dart b/packages/tonik_util/test/src/encoder/datetime_extension_test.dart
new file mode 100644
index 0000000..117a4db
--- /dev/null
+++ b/packages/tonik_util/test/src/encoder/datetime_extension_test.dart
@@ -0,0 +1,162 @@
+import 'package:test/test.dart';
+import 'package:timezone/data/latest.dart' as tz;
+import 'package:timezone/timezone.dart' as tz;
+import 'package:tonik_util/src/encoding/datetime_extension.dart';
+
+void main() {
+ setUpAll(tz.initializeTimeZones);
+
+ group('DateTimeEncodingExtension', () {
+ group('toTimeZonedIso8601String', () {
+ test('encodes UTC DateTime correctly', () {
+ final utcDateTime = DateTime.utc(2023, 12, 25, 15, 30, 45, 123);
+ final result = utcDateTime.toTimeZonedIso8601String();
+
+ // Should match toIso8601String for UTC dates
+ expect(result, equals(utcDateTime.toIso8601String()));
+ expect(result, equals('2023-12-25T15:30:45.123Z'));
+ });
+ });
+
+ group('timezone handling', () {
+ test('encodes in EST (UTC-5:00)', () {
+ final estLocation = tz.getLocation('America/New_York');
+ final estDateTime = tz.TZDateTime(
+ estLocation,
+ 2023,
+ 12,
+ 25,
+ 15,
+ 30,
+ 45,
+ );
+
+ final result = estDateTime.toTimeZonedIso8601String();
+
+ // Should include EST timezone offset (-05:00)
+ expect(result, equals('2023-12-25T15:30:45-05:00'));
+ });
+
+ test('encodes in PST (UTC-8:00)', () {
+ final pstLocation = tz.getLocation('America/Los_Angeles');
+ final pstDateTime = tz.TZDateTime(
+ pstLocation,
+ 2023,
+ 12,
+ 25,
+ 18,
+ 30,
+ 45,
+ );
+
+ final result = pstDateTime.toTimeZonedIso8601String();
+
+ // Should include PST timezone offset (-08:00)
+ expect(result, equals('2023-12-25T18:30:45-08:00'));
+ });
+
+ test('encodes in IST (UTC+5:30)', () {
+ final istLocation = tz.getLocation('Asia/Kolkata');
+ final istDateTime = tz.TZDateTime(
+ istLocation,
+ 2023,
+ 12,
+ 25,
+ 20,
+ 0,
+ 45,
+ );
+
+ final result = istDateTime.toTimeZonedIso8601String();
+
+ // Should include IST timezone offset (+05:30)
+ expect(result, equals('2023-12-25T20:00:45+05:30'));
+ });
+
+ test('encodes in CET (UTC+1:00)', () {
+ final cetLocation = tz.getLocation('Europe/Paris');
+ final cetDateTime = tz.TZDateTime(
+ cetLocation,
+ 2023,
+ 12,
+ 25,
+ 16,
+ 30,
+ 45,
+ );
+
+ final result = cetDateTime.toTimeZonedIso8601String();
+
+ // Should include CET timezone offset (+01:00)
+ expect(result, equals('2023-12-25T16:30:45+01:00'));
+ });
+
+ test('encodes in GMT (UTC+0:00)', () {
+ final gmtLocation = tz.getLocation('Europe/London');
+ final gmtDateTime = tz.TZDateTime(
+ gmtLocation,
+ 2023,
+ 12,
+ 25,
+ 15,
+ 30,
+ 45,
+ );
+
+ final result = gmtDateTime.toTimeZonedIso8601String();
+
+ // Should include GMT timezone offset (+00:00)
+ expect(result, equals('2023-12-25T15:30:45+00:00'));
+ });
+
+ test('encodes with milliseconds in timezone', () {
+ final estLocation = tz.getLocation('America/New_York');
+ final estDateTime = tz.TZDateTime(
+ estLocation,
+ 2023,
+ 12,
+ 25,
+ 15,
+ 30,
+ 45,
+ 123,
+ );
+
+ final result = estDateTime.toTimeZonedIso8601String();
+
+ // Should include EST timezone offset (-05:00) and milliseconds
+ expect(result, equals('2023-12-25T15:30:45.123-05:00'));
+ });
+
+ test('encodes with microseconds in timezone', () {
+ final pstLocation = tz.getLocation('America/Los_Angeles');
+ final pstDateTime = tz.TZDateTime(
+ pstLocation,
+ 2023,
+ 12,
+ 25,
+ 18,
+ 30,
+ 45,
+ 123,
+ 456,
+ );
+
+ final result = pstDateTime.toTimeZonedIso8601String();
+
+ // Should include PST timezone offset (-08:00) and microseconds
+ expect(result, equals('2023-12-25T18:30:45.123456-08:00'));
+ });
+
+ test('encodes in JST (UTC+9:00)', () {
+ final jstLocation = tz.getLocation('Asia/Tokyo');
+ final jstDateTime = tz.TZDateTime(jstLocation, 2009, 6, 30, 18, 30);
+
+ final result = jstDateTime.toTimeZonedIso8601String();
+
+ // Should include JST timezone offset (+09:00)
+ expect(result, equals('2009-06-30T18:30:00+09:00'));
+ });
+ });
+ });
+}
From 1c2c1647504f82a96d8f424f99af38bd0b298569 Mon Sep 17 00:00:00 2001
From: Tobias Ottenweller
Date: Sun, 20 Jul 2025 13:16:00 +0200
Subject: [PATCH 20/31] chore: matcher cleanup
---
.../test/model/query_parameter_test.dart | 24 +++++++++----------
.../test/model/request_header_test.dart | 24 +++++++++----------
.../test/model/response_header_test.dart | 22 ++++++++---------
.../response_class_generator_test.dart | 10 ++++----
.../src/response/response_generator_test.dart | 4 ++--
.../server/server_file_generator_test.dart | 2 +-
.../test/model/model_alias_test.dart | 6 ++---
.../test/model/model_multiple_types_test.dart | 8 +++----
.../test/src/decoding/json_decoder_test.dart | 2 +-
.../src/encoder/datetime_extension_test.dart | 20 ++++++++--------
10 files changed, 61 insertions(+), 61 deletions(-)
diff --git a/packages/tonik_core/test/model/query_parameter_test.dart b/packages/tonik_core/test/model/query_parameter_test.dart
index 732ede2..76446c1 100644
--- a/packages/tonik_core/test/model/query_parameter_test.dart
+++ b/packages/tonik_core/test/model/query_parameter_test.dart
@@ -23,17 +23,17 @@ void main() {
final resolved = param.resolve(name: 'newName');
- expect(resolved.name, equals('newName'));
- expect(resolved.rawName, equals('originalRawName'));
- expect(resolved.description, equals('description'));
+ expect(resolved.name, 'newName');
+ expect(resolved.rawName, 'originalRawName');
+ expect(resolved.description, 'description');
expect(resolved.isRequired, isTrue);
expect(resolved.isDeprecated, isFalse);
expect(resolved.allowEmptyValue, isFalse);
expect(resolved.allowReserved, isFalse);
expect(resolved.explode, isFalse);
- expect(resolved.model, equals(model));
- expect(resolved.encoding, equals(QueryParameterEncoding.form));
- expect(resolved.context, equals(context));
+ expect(resolved.model, model);
+ expect(resolved.encoding, QueryParameterEncoding.form);
+ expect(resolved.context, context);
});
test('resolve preserves original name when no new name provided', () {
@@ -56,7 +56,7 @@ void main() {
final resolved = param.resolve();
- expect(resolved.name, equals('originalName'));
+ expect(resolved.name, 'originalName');
});
test('QueryParameterAlias.resolve resolves with alias name', () {
@@ -85,8 +85,8 @@ void main() {
final resolved = alias.resolve();
- expect(resolved.name, equals('aliasName'));
- expect(resolved.rawName, equals('originalRawName'));
+ expect(resolved.name, 'aliasName');
+ expect(resolved.rawName, 'originalRawName');
});
test(
@@ -117,7 +117,7 @@ void main() {
final resolved = alias.resolve(name: 'overrideName');
- expect(resolved.name, equals('overrideName'));
+ expect(resolved.name, 'overrideName');
},
);
@@ -153,8 +153,8 @@ void main() {
final resolved = secondAlias.resolve();
- expect(resolved.name, equals('secondAliasName'));
- expect(resolved.rawName, equals('originalRawName'));
+ expect(resolved.name, 'secondAliasName');
+ expect(resolved.rawName, 'originalRawName');
});
});
}
diff --git a/packages/tonik_core/test/model/request_header_test.dart b/packages/tonik_core/test/model/request_header_test.dart
index b6edca2..38b5147 100644
--- a/packages/tonik_core/test/model/request_header_test.dart
+++ b/packages/tonik_core/test/model/request_header_test.dart
@@ -22,16 +22,16 @@ void main() {
final resolved = header.resolve(name: 'newName');
- expect(resolved.name, equals('newName'));
- expect(resolved.rawName, equals('originalRawName'));
- expect(resolved.description, equals('description'));
+ expect(resolved.name, 'newName');
+ expect(resolved.rawName, 'originalRawName');
+ expect(resolved.description, 'description');
expect(resolved.isRequired, isTrue);
expect(resolved.isDeprecated, isFalse);
expect(resolved.allowEmptyValue, isFalse);
expect(resolved.explode, isFalse);
- expect(resolved.model, equals(model));
- expect(resolved.encoding, equals(HeaderParameterEncoding.simple));
- expect(resolved.context, equals(context));
+ expect(resolved.model, model);
+ expect(resolved.encoding, HeaderParameterEncoding.simple);
+ expect(resolved.context, context);
});
test('resolve preserves original name when no new name provided', () {
@@ -53,7 +53,7 @@ void main() {
final resolved = header.resolve();
- expect(resolved.name, equals('originalName'));
+ expect(resolved.name, 'originalName');
});
test('RequestHeaderAlias.resolve resolves with alias name', () {
@@ -81,8 +81,8 @@ void main() {
final resolved = alias.resolve();
- expect(resolved.name, equals('aliasName'));
- expect(resolved.rawName, equals('originalRawName'));
+ expect(resolved.name, 'aliasName');
+ expect(resolved.rawName, 'originalRawName');
});
test(
@@ -112,7 +112,7 @@ void main() {
final resolved = alias.resolve(name: 'overrideName');
- expect(resolved.name, equals('overrideName'));
+ expect(resolved.name, 'overrideName');
},
);
@@ -147,8 +147,8 @@ void main() {
final resolved = secondAlias.resolve();
- expect(resolved.name, equals('secondAliasName'));
- expect(resolved.rawName, equals('originalRawName'));
+ expect(resolved.name, 'secondAliasName');
+ expect(resolved.rawName, 'originalRawName');
});
});
}
diff --git a/packages/tonik_core/test/model/response_header_test.dart b/packages/tonik_core/test/model/response_header_test.dart
index 628766b..7bef64a 100644
--- a/packages/tonik_core/test/model/response_header_test.dart
+++ b/packages/tonik_core/test/model/response_header_test.dart
@@ -20,14 +20,14 @@ void main() {
final resolved = header.resolve(name: 'newName');
- expect(resolved.name, equals('newName'));
- expect(resolved.description, equals('description'));
+ expect(resolved.name, 'newName');
+ expect(resolved.description, 'description');
expect(resolved.isRequired, isTrue);
expect(resolved.isDeprecated, isFalse);
expect(resolved.explode, isFalse);
- expect(resolved.model, equals(model));
- expect(resolved.encoding, equals(ResponseHeaderEncoding.simple));
- expect(resolved.context, equals(context));
+ expect(resolved.model, model);
+ expect(resolved.encoding, ResponseHeaderEncoding.simple);
+ expect(resolved.context, context);
});
test('resolve preserves original name when no new name provided', () {
@@ -47,7 +47,7 @@ void main() {
final resolved = header.resolve();
- expect(resolved.name, equals('originalName'));
+ expect(resolved.name, 'originalName');
});
test('ResponseHeaderAlias.resolve resolves with alias name', () {
@@ -73,8 +73,8 @@ void main() {
final resolved = alias.resolve();
- expect(resolved.name, equals('aliasName'));
- expect(resolved.description, equals('description'));
+ expect(resolved.name, 'aliasName');
+ expect(resolved.description, 'description');
});
test(
@@ -102,7 +102,7 @@ void main() {
final resolved = alias.resolve(name: 'overrideName');
- expect(resolved.name, equals('overrideName'));
+ expect(resolved.name, 'overrideName');
},
);
@@ -135,8 +135,8 @@ void main() {
final resolved = secondAlias.resolve();
- expect(resolved.name, equals('secondAliasName'));
- expect(resolved.description, equals('description'));
+ expect(resolved.name, 'secondAliasName');
+ expect(resolved.description, 'description');
});
});
}
diff --git a/packages/tonik_generate/test/src/response/response_class_generator_test.dart b/packages/tonik_generate/test/src/response/response_class_generator_test.dart
index ae48ec6..f5438a5 100644
--- a/packages/tonik_generate/test/src/response/response_class_generator_test.dart
+++ b/packages/tonik_generate/test/src/response/response_class_generator_test.dart
@@ -80,21 +80,21 @@ void main() {
// Required header field
final xTestField = fields[0];
expect(xTestField.name, 'xTest');
- expect(xTestField.modifier, equals(FieldModifier.final$));
+ expect(xTestField.modifier, FieldModifier.final$);
expect(xTestField.type?.accept(emitter).toString(), 'String');
expect(xTestField.annotations.isEmpty, isTrue);
// Optional header field
final xOptionalField = fields[2];
expect(xOptionalField.name, 'xOptional');
- expect(xOptionalField.modifier, equals(FieldModifier.final$));
+ expect(xOptionalField.modifier, FieldModifier.final$);
expect(xOptionalField.type?.accept(emitter).toString(), 'int?');
expect(xOptionalField.annotations.isEmpty, isTrue);
// Body field
final bodyField = fields[1];
expect(bodyField.name, 'body');
- expect(bodyField.modifier, equals(FieldModifier.final$));
+ expect(bodyField.modifier, FieldModifier.final$);
expect(bodyField.type?.accept(emitter).toString(), 'String');
expect(bodyField.annotations.isEmpty, isTrue);
@@ -158,14 +158,14 @@ void main() {
final headerField = fields[0];
expect(headerField.name, 'bodyHeader');
expect(headerField.type?.accept(emitter).toString(), 'String');
- expect(headerField.modifier, equals(FieldModifier.final$));
+ expect(headerField.modifier, FieldModifier.final$);
expect(headerField.annotations.isEmpty, isTrue);
// Body field should keep original name
final bodyField = fields[1];
expect(bodyField.name, 'body');
expect(bodyField.type?.accept(emitter).toString(), 'int');
- expect(bodyField.modifier, equals(FieldModifier.final$));
+ expect(bodyField.modifier, FieldModifier.final$);
expect(bodyField.annotations.isEmpty, isTrue);
// Verify constructor parameters maintain the same names
diff --git a/packages/tonik_generate/test/src/response/response_generator_test.dart b/packages/tonik_generate/test/src/response/response_generator_test.dart
index c1920a8..1c99560 100644
--- a/packages/tonik_generate/test/src/response/response_generator_test.dart
+++ b/packages/tonik_generate/test/src/response/response_generator_test.dart
@@ -84,7 +84,7 @@ void main() {
);
final result = generator.generate(aliasResponse);
- expect(result.filename, equals('alias_response.dart'));
+ expect(result.filename, 'alias_response.dart');
expect(result.code, contains('typedef AliasResponse ='));
});
@@ -115,7 +115,7 @@ void main() {
);
final result = generator.generate(response);
- expect(result.filename, equals('single_body_response.dart'));
+ expect(result.filename, 'single_body_response.dart');
expect(result.code, contains('class SingleBodyResponse'));
});
diff --git a/packages/tonik_generate/test/src/server/server_file_generator_test.dart b/packages/tonik_generate/test/src/server/server_file_generator_test.dart
index 32de2f7..3015cab 100644
--- a/packages/tonik_generate/test/src/server/server_file_generator_test.dart
+++ b/packages/tonik_generate/test/src/server/server_file_generator_test.dart
@@ -109,7 +109,7 @@ void main() {
final fileContent = File(generatedFile.path).readAsStringSync();
// Check file name
- expect(actualFileName, equals('server.dart'));
+ expect(actualFileName, 'server.dart');
// Check file content
expect(fileContent, contains('sealed class Server'));
diff --git a/packages/tonik_parse/test/model/model_alias_test.dart b/packages/tonik_parse/test/model/model_alias_test.dart
index cf390d3..dfe2fd8 100644
--- a/packages/tonik_parse/test/model/model_alias_test.dart
+++ b/packages/tonik_parse/test/model/model_alias_test.dart
@@ -85,7 +85,7 @@ void main() {
context: context,
);
- expect(alias.resolved, equals(stringModel));
+ expect(alias.resolved, stringModel);
});
test('resolves single-level alias', () {
@@ -102,7 +102,7 @@ void main() {
context: context,
);
- expect(outerAlias.resolved, equals(stringModel));
+ expect(outerAlias.resolved, stringModel);
});
test('resolves multi-level alias', () {
@@ -124,7 +124,7 @@ void main() {
context: context,
);
- expect(level1.resolved, equals(stringModel));
+ expect(level1.resolved, stringModel);
});
});
}
diff --git a/packages/tonik_parse/test/model/model_multiple_types_test.dart b/packages/tonik_parse/test/model/model_multiple_types_test.dart
index d2f9dca..9ce32d2 100644
--- a/packages/tonik_parse/test/model/model_multiple_types_test.dart
+++ b/packages/tonik_parse/test/model/model_multiple_types_test.dart
@@ -66,7 +66,7 @@ void main() {
expect(stringOrNumber.isNullable, isFalse);
final oneOf = stringOrNumber.model as OneOfModel;
- expect(oneOf.models.length, equals(2));
+ expect(oneOf.models.length, 2);
expect(
oneOf.models.map((m) => m.model).toList(),
containsAll([isA(), isA()]),
@@ -109,7 +109,7 @@ void main() {
expect(nullableMultiType.isNullable, isTrue);
final oneOf = nullableMultiType.model as OneOfModel;
- expect(oneOf.models.length, equals(2));
+ expect(oneOf.models.length, 2);
expect(
oneOf.models.map((m) => m.model).toList(),
containsAll([isA(), isA()]),
@@ -128,7 +128,7 @@ void main() {
expect(nullableMultiType.isNullable, isTrue);
final oneOf = nullableMultiType.model as OneOfModel;
- expect(oneOf.models.length, equals(2));
+ expect(oneOf.models.length, 2);
expect(
oneOf.models.map((m) => m.model).toList(),
containsAll([isA(), isA()]),
@@ -150,7 +150,7 @@ void main() {
expect(listModel.content, isA());
final content = listModel.content as OneOfModel;
- expect(content.models.length, equals(2));
+ expect(content.models.length, 2);
expect(
content.models.map((m) => m.model).toList(),
containsAll([isA(), isA