diff --git a/pubspec.lock b/pubspec.lock index 867486a..c0ed807 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" charcode: dependency: transitive description: @@ -205,10 +205,10 @@ packages: dependency: transitive description: name: coverage - sha256: "9086475ef2da7102a0c0a4e37e1e30707e7fb7b6d28c209f559a9c5f8ce42016" + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" crypto: dependency: transitive description: @@ -445,14 +445,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" - js: - dependency: transitive - description: - name: js - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.dev" - source: hosted - version: "0.7.2" json_annotation: dependency: transitive description: @@ -529,18 +521,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.18" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: @@ -918,26 +910,26 @@ packages: dependency: transitive description: name: test - sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + sha256: "54c516bbb7cee2754d327ad4fca637f78abfc3cbcc5ace83b3eda117e42cd71a" url: "https://pub.dev" source: hosted - version: "1.26.3" + version: "1.29.0" test_api: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.9" test_core: dependency: transitive description: name: test_core - sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + sha256: "394f07d21f0f2255ec9e3989f21e54d3c7dc0e6e9dbce160e5a9c1a6be0e2943" url: "https://pub.dev" source: hosted - version: "0.6.12" + version: "0.6.15" typed_data: dependency: transitive description: diff --git a/vaden/example/lib/src/controllers/order_controller.dart b/vaden/example/lib/src/controllers/order_controller.dart new file mode 100644 index 0000000..b1c9350 --- /dev/null +++ b/vaden/example/lib/src/controllers/order_controller.dart @@ -0,0 +1,28 @@ +import 'package:example/src/dtos/order_dto.dart'; +import 'package:example/src/enums/order_status.dart'; +import 'package:vaden/vaden.dart'; + +@Api(tag: 'orders', description: 'Order operations') +@Controller('/orders') +class OrderController { + @Get('/') + List getAll() { + return [ + OrderDto( + id: '1', + status: OrderStatus.pending, + history: [OrderStatus.pending], + ), + ]; + } + + @Get('/by-status/') + List getByStatus(@Param() String status) { + return []; + } + + @Post('/') + OrderDto create(@Body() OrderDto order) { + return order; + } +} diff --git a/vaden/example/lib/src/dtos/order_dto.dart b/vaden/example/lib/src/dtos/order_dto.dart new file mode 100644 index 0000000..07205c7 --- /dev/null +++ b/vaden/example/lib/src/dtos/order_dto.dart @@ -0,0 +1,15 @@ +import 'package:example/src/enums/order_status.dart'; +import 'package:vaden/vaden.dart'; + +@DTO() +class OrderDto { + final String id; + final OrderStatus status; + final List history; + + OrderDto({ + required this.id, + required this.status, + required this.history, + }); +} diff --git a/vaden/example/lib/src/enums/order_status.dart b/vaden/example/lib/src/enums/order_status.dart new file mode 100644 index 0000000..ef52842 --- /dev/null +++ b/vaden/example/lib/src/enums/order_status.dart @@ -0,0 +1,7 @@ +enum OrderStatus { + pending, + processing, + shipped, + delivered, + cancelled, +} diff --git a/vaden/example/lib/src/enums/product_category.dart b/vaden/example/lib/src/enums/product_category.dart new file mode 100644 index 0000000..038a121 --- /dev/null +++ b/vaden/example/lib/src/enums/product_category.dart @@ -0,0 +1,6 @@ +enum ProductCategory { + electronics, + clothing, + food, + books, +} diff --git a/vaden/example/lib/src/hello_controller.dart b/vaden/example/lib/src/hello_controller.dart index f105a47..c3542db 100644 --- a/vaden/example/lib/src/hello_controller.dart +++ b/vaden/example/lib/src/hello_controller.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:example/src/enums/product_category.dart'; import 'package:example/src/product_dto.dart'; import 'package:vaden/vaden.dart'; @@ -33,9 +34,9 @@ class HelloController { @Get('/object') List object() { return [ - ProductDto(name: 'Product 1', price: 100.0), - ProductDto(name: 'Product 2', price: 200.0), - ProductDto(name: 'Product 3', price: 300.0), + ProductDto(name: 'Product 1', price: 100.0, category: ProductCategory.electronics), + ProductDto(name: 'Product 2', price: 200.0, category: ProductCategory.clothing), + ProductDto(name: 'Product 3', price: 300.0, category: ProductCategory.food), ]; } diff --git a/vaden/example/lib/src/product_dto.dart b/vaden/example/lib/src/product_dto.dart index 5ebccb3..3fc23b9 100644 --- a/vaden/example/lib/src/product_dto.dart +++ b/vaden/example/lib/src/product_dto.dart @@ -1,9 +1,15 @@ +import 'package:example/src/enums/product_category.dart'; import 'package:vaden/vaden.dart'; @DTO() class ProductDto { final String name; final double price; + final ProductCategory category; - ProductDto({required this.name, required this.price}); + ProductDto({ + required this.name, + required this.price, + required this.category, + }); } diff --git a/vaden_class_scanner/lib/src/setups/initial.dart b/vaden_class_scanner/lib/src/setups/initial.dart index a9e7669..315060e 100644 --- a/vaden_class_scanner/lib/src/setups/initial.dart +++ b/vaden_class_scanner/lib/src/setups/initial.dart @@ -1,4 +1,5 @@ import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; import 'package:build/build.dart'; import 'package:dart_style/dart_style.dart'; import 'package:path/path.dart' as p; @@ -191,6 +192,8 @@ ComponentRegistration _selectComponent({ priority = ComponentPriority.configuration; } else if (controllerChecker.hasAnnotationOf(classElement)) { bodyBuffer.writeln(controllerSetup(classElement)); + _collectFieldTypeImports(classElement, importSet); + _collectMethodParamTypeImports(classElement, importSet); priority = ComponentPriority.controller; } else if (apiClientBuffer != null && apiClientChecker.hasAnnotationOf(classElement)) { @@ -204,6 +207,7 @@ ComponentRegistration _selectComponent({ priority = ComponentPriority.component; } else if (dtoChecker.hasAnnotationOf(classElement)) { dtoBuffer.writeln(dtoSetup(classElement)); + _collectFieldTypeImports(classElement, importSet); priority = ComponentPriority.other; } else if (controllerAdviceChecker.hasAnnotationOf(classElement)) { final (adviceBody, imports) = controllerAdviceSetup(classElement); @@ -344,3 +348,81 @@ Future writeAndFormatApplication(String text, BuildStep buildStep) async { await buildStep.writeAsString(outputId, text); } } + +/// Adds import URIs for non-primitive types referenced by the fields of +/// [classElement] (and its supertypes) that are not already covered by +/// annotation-based scanning. This handles enums and other types defined +/// in separate files. +void _collectFieldTypeImports( + ClassElement classElement, + Set importSet, +) { + ClassElement? current = classElement; + while (current != null) { + for (final field in current.fields) { + if (field.isSynthetic || field.isStatic || field.isPrivate) continue; + _addTypeImport(field.type, importSet); + } + final superType = current.supertype; + if (superType == null || superType.isDartCoreObject) break; + current = superType.element as ClassElement?; + } +} + +/// Adds import URIs for non-primitive types referenced by method parameters +/// of [classElement], covering @Param, @Query and @Body enum types in +/// controllers. Traverses supertypes to match the same method resolution +/// used by [controllerSetup]. +void _collectMethodParamTypeImports( + ClassElement classElement, + Set importSet, +) { + final visited = {}; + + void collectFromMethods(InterfaceElement element) { + for (final method in element.methods) { + if (method.isStatic || method.isPrivate) continue; + final name = method.name; + if (name == null || !visited.add(name)) continue; + for (final param in method.formalParameters) { + _addTypeImport(param.type, importSet); + } + _addTypeImport(method.returnType, importSet); + } + } + + collectFromMethods(classElement); + + for (final supertype in classElement.allSupertypes) { + if (supertype.element.name == 'Object') continue; + collectFromMethods(supertype.element); + } +} + +/// Resolves the library URI for [type] and adds it to [importSet] if it is +/// a non-core type (enum, class, etc.) defined outside of dart:core. +void _addTypeImport(DartType type, Set importSet) { + // Unwrap Future / FutureOr + if (type.isDartAsyncFuture || type.isDartAsyncFutureOr) { + if (type is ParameterizedType && type.typeArguments.isNotEmpty) { + _addTypeImport(type.typeArguments.first, importSet); + } + return; + } + + // Unwrap List / Set / Map type arguments + if (type is ParameterizedType && type.typeArguments.isNotEmpty) { + for (final arg in type.typeArguments) { + _addTypeImport(arg, importSet); + } + } + + final element = type.element; + if (element == null) return; + + // Skip dart:core types (int, String, bool, etc.) + final uri = element.library?.uri.toString() ?? ''; + if (uri.isEmpty || uri.startsWith('dart:')) return; + + importSet.add("'$uri';"); +}