From 1d808b477243a6281ada3053a62a14b08d3b3a05 Mon Sep 17 00:00:00 2001 From: Rexios Date: Mon, 6 Jan 2025 12:44:43 -0500 Subject: [PATCH] Add package names endpoint (#55) --- lib/src/endpoints.dart | 3 + lib/src/models/search_results_model.dart | 22 ++++ .../models/search_results_model.mapper.dart | 122 ++++++++++++++++++ lib/src/pub_api_client_base.dart | 17 ++- test/pubdev_api_test.dart | 7 +- 5 files changed, 167 insertions(+), 4 deletions(-) diff --git a/lib/src/endpoints.dart b/lib/src/endpoints.dart index 0cb436f..eb427dc 100644 --- a/lib/src/endpoints.dart +++ b/lib/src/endpoints.dart @@ -47,6 +47,9 @@ class Endpoint { /// Retrieve all package names on pub.dev String get packageNames => '$apiUrl/package-names'; + /// Package names for name completion + String get packageNameCompletion => '$apiUrl/package-name-completion-data'; + /// Url to add and remove likes String likePackage(String name) => '$accountUrl/likes/$name'; diff --git a/lib/src/models/search_results_model.dart b/lib/src/models/search_results_model.dart index 36ba5d2..59d968a 100644 --- a/lib/src/models/search_results_model.dart +++ b/lib/src/models/search_results_model.dart @@ -13,6 +13,28 @@ abstract class PaginatedResults { const PaginatedResults(); } +/// Package Names Model +@MappableClass() +class PackageNamesResults extends PaginatedResults { + final List packages; + + @override + List get results => packages; + + final String? nextUrl; + + @override + String? get next => nextUrl; + + const PackageNamesResults({ + required this.packages, + this.nextUrl, + }); + + static const fromMap = PackageNamesResultsMapper.fromMap; + static const fromJson = PackageNamesResultsMapper.fromJson; +} + /// Search Results Model @MappableClass() class SearchResults extends PaginatedResults diff --git a/lib/src/models/search_results_model.mapper.dart b/lib/src/models/search_results_model.mapper.dart index a28fc47..9bbd23e 100644 --- a/lib/src/models/search_results_model.mapper.dart +++ b/lib/src/models/search_results_model.mapper.dart @@ -6,6 +6,128 @@ part of 'search_results_model.dart'; +class PackageNamesResultsMapper extends ClassMapperBase { + PackageNamesResultsMapper._(); + + static PackageNamesResultsMapper? _instance; + static PackageNamesResultsMapper ensureInitialized() { + if (_instance == null) { + MapperContainer.globals.use(_instance = PackageNamesResultsMapper._()); + } + return _instance!; + } + + @override + final String id = 'PackageNamesResults'; + + static List _$packages(PackageNamesResults v) => v.packages; + static const Field> _f$packages = + Field('packages', _$packages); + static String? _$nextUrl(PackageNamesResults v) => v.nextUrl; + static const Field _f$nextUrl = + Field('nextUrl', _$nextUrl, opt: true); + + @override + final MappableFields fields = const { + #packages: _f$packages, + #nextUrl: _f$nextUrl, + }; + + static PackageNamesResults _instantiate(DecodingData data) { + return PackageNamesResults( + packages: data.dec(_f$packages), nextUrl: data.dec(_f$nextUrl)); + } + + @override + final Function instantiate = _instantiate; + + static PackageNamesResults fromMap(Map map) { + return ensureInitialized().decodeMap(map); + } + + static PackageNamesResults fromJson(String json) { + return ensureInitialized().decodeJson(json); + } +} + +mixin PackageNamesResultsMappable { + String toJson() { + return PackageNamesResultsMapper.ensureInitialized() + .encodeJson(this as PackageNamesResults); + } + + Map toMap() { + return PackageNamesResultsMapper.ensureInitialized() + .encodeMap(this as PackageNamesResults); + } + + PackageNamesResultsCopyWith + get copyWith => _PackageNamesResultsCopyWithImpl( + this as PackageNamesResults, $identity, $identity); + @override + String toString() { + return PackageNamesResultsMapper.ensureInitialized() + .stringifyValue(this as PackageNamesResults); + } + + @override + bool operator ==(Object other) { + return PackageNamesResultsMapper.ensureInitialized() + .equalsValue(this as PackageNamesResults, other); + } + + @override + int get hashCode { + return PackageNamesResultsMapper.ensureInitialized() + .hashValue(this as PackageNamesResults); + } +} + +extension PackageNamesResultsValueCopy<$R, $Out> + on ObjectCopyWith<$R, PackageNamesResults, $Out> { + PackageNamesResultsCopyWith<$R, PackageNamesResults, $Out> + get $asPackageNamesResults => + $base.as((v, t, t2) => _PackageNamesResultsCopyWithImpl(v, t, t2)); +} + +abstract class PackageNamesResultsCopyWith<$R, $In extends PackageNamesResults, + $Out> implements ClassCopyWith<$R, $In, $Out> { + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get packages; + $R call({List? packages, String? nextUrl}); + PackageNamesResultsCopyWith<$R2, $In, $Out2> $chain<$R2, $Out2>( + Then<$Out2, $R2> t); +} + +class _PackageNamesResultsCopyWithImpl<$R, $Out> + extends ClassCopyWithBase<$R, PackageNamesResults, $Out> + implements PackageNamesResultsCopyWith<$R, PackageNamesResults, $Out> { + _PackageNamesResultsCopyWithImpl(super.value, super.then, super.then2); + + @override + late final ClassMapperBase $mapper = + PackageNamesResultsMapper.ensureInitialized(); + @override + ListCopyWith<$R, String, ObjectCopyWith<$R, String, String>> get packages => + ListCopyWith($value.packages, (v, t) => ObjectCopyWith(v, $identity, t), + (v) => call(packages: v)); + @override + $R call({List? packages, Object? nextUrl = $none}) => + $apply(FieldCopyWithData({ + if (packages != null) #packages: packages, + if (nextUrl != $none) #nextUrl: nextUrl + })); + @override + PackageNamesResults $make(CopyWithData data) => PackageNamesResults( + packages: data.get(#packages, or: $value.packages), + nextUrl: data.get(#nextUrl, or: $value.nextUrl)); + + @override + PackageNamesResultsCopyWith<$R2, PackageNamesResults, $Out2> + $chain<$R2, $Out2>(Then<$Out2, $R2> t) => + _PackageNamesResultsCopyWithImpl($value, $cast, t); +} + class SearchResultsMapper extends ClassMapperBase { SearchResultsMapper._(); diff --git a/lib/src/pub_api_client_base.dart b/lib/src/pub_api_client_base.dart index 4708837..750bd5c 100644 --- a/lib/src/pub_api_client_base.dart +++ b/lib/src/pub_api_client_base.dart @@ -143,12 +143,23 @@ class PubClient { } /// Returns a `List` of all packages listed on pub.dev - Future> packageNameCompletion() async { + Future> packageNames() async { final data = await _fetch(endpoint.packageNames); - final packages = data['packages'] as List; + final results = PackageNamesResults.fromMap(data); + return recursivePaging(results, (url) async { + final data = await _fetch(endpoint.nextPage(url)); + return PackageNamesResults.fromMap(data); + }); + } + + /// Package names for name completion + Future> packageNameCompletion() async { + final data = await _fetch(endpoint.packageNameCompletion); + // This result is not paginated + final packages = data['packages'] as List; /// Need to map to convert dynamic into String - return packages.map((item) => item as String).toList(); + return packages.cast(); } /// Searches pub for [query] and can [page] results. diff --git a/test/pubdev_api_test.dart b/test/pubdev_api_test.dart index 5dd1f7b..3a8ba13 100644 --- a/test/pubdev_api_test.dart +++ b/test/pubdev_api_test.dart @@ -214,8 +214,13 @@ void main() { expect(documentation2.versions.length, greaterThan(0)); }); - test('Get package names', () async { + test('Get package name completion', () async { final packages = await _client.packageNameCompletion(); + expect(packages.length, equals(20000)); + }); + + test('Get package names', () async { + final packages = await _client.packageNames(); expect(packages.length, greaterThan(20000)); });