From 2a4f7d285c0464a8c90baa4227b53e3f277e855e Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Sat, 30 Sep 2023 12:45:46 +0200 Subject: [PATCH] chore: preparation of the 3.0.0 release (#809) * chore: preparation of the 3.0.0 release Impacted files: * `api_add_product_image_test.dart`: minor refactoring * `api_get_product_test.dart`: minor refactoring * `api_not_food_get_product_test.dart`: minor refactoring * `configuration_test.dart`: minor refactoring * `image_helper.dart`: methods `getProductImageRootUrl`, `getBarcodeSubPath` are moved to `UriProductHelper`; method `buildUrl` loses one parameter; method `buildUrl` renamed as `getLocalizedProductImageUrl` * `open_food_api_client.dart`: minor refactoring * `open_food_api_configuration.dart`: minor refactoring * `product_helper.dart`: minor refactoring * `README.md`: added migration instructions * `uri_helper.dart`: now we use `domain` instead of `host` for `UriProductHelper`; methods `getProductImageRootUrl`, `getBarcodeSubPath` are moved from `ImageHelper` * chore: additional fix around url field in product image Impacted files: * `api_json_to_from_test.dart`: minor change to make the test more relevant * `json_helper.dart`: added field `url` in the to/fromJSON image conversion methods * `product_helper.dart`: minor fix * chore: Object instead of dynamic Impacted file: * `json_helper.dart`: `Object` instead of `dynamic` --- README.md | 16 +++- lib/src/open_food_api_client.dart | 2 +- lib/src/utils/image_helper.dart | 73 +++++-------------- lib/src/utils/json_helper.dart | 22 +++--- .../utils/open_food_api_configuration.dart | 10 ++- lib/src/utils/product_helper.dart | 8 +- lib/src/utils/uri_helper.dart | 38 ++++++++-- test/api_add_product_image_test.dart | 3 +- test/api_get_product_test.dart | 6 +- test/api_json_to_from_test.dart | 2 +- test/api_not_food_get_product_test.dart | 9 +-- test/configuration_test.dart | 3 +- 12 files changed, 100 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 8ef1292c3c..473383bd81 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This plugin also allows you to edit a product or upload a new one to Open Food F ## Migrating from 2.x.x to 3.x.x (breaking changes) -Starting with version 3.0.0, we now enforce all clients to provide a valid user agent. +- Starting with version 3.0.0, we now enforce all clients to provide a valid user agent. For this, please ensure to set the SDK before using any other functionality: ```dart @@ -32,6 +32,20 @@ OpenFoodAPIConfiguration.userAgent = UserAgent( ); ``` +- `QueryType` has been deleted. Now, for API calls you have to provide a `UriProductHelper` parameter. By default it will point you to openfoodfacts/prod. + +- For `RobotoffAPIClient.getRandomInsights` and `RobotoffAPIClient.getQuestions`, a list of countries instead of a single country as parameter. + +- Use `OpenFoodFactsCountry.fromOffTag` instead of `CountryHelper.fromJson`. + +- `OpenFoodAPIClient.getOrderedNutrients` now uses a `OpenFoodFactsCountry` parameter instead of a 2-letter country code. + +- Methods `getProductImageRootUrl` and `getBarcodeSubPath` are moved to `UriProductHelper` from `ImageHelper` + +- Method `buildUrl` renamed as `getLocalizedProductImageUrl` in `ImageHelper` + +- Removal of deprecated code. + ## Migrating from 1.x.x to 2.x.x (breaking changes) - Now the only entry point is `import 'package:openfoodfacts/openfoodfacts.dart';` diff --git a/lib/src/open_food_api_client.dart b/lib/src/open_food_api_client.dart index 5379c9cc1e..19f0223d1b 100644 --- a/lib/src/open_food_api_client.dart +++ b/lib/src/open_food_api_client.dart @@ -1263,7 +1263,7 @@ class OpenFoodAPIClient { if (filename == null) { return null; } - return '${ImageHelper.getProductImageRootUrl(barcode, root: uriHelper.imageUrlBase)}/$filename'; + return '${uriHelper.getProductImageRootUrl(barcode)}/$filename'; } /// Unselect a product image. diff --git a/lib/src/utils/image_helper.dart b/lib/src/utils/image_helper.dart index 2dac059c6a..1ac75c51c3 100644 --- a/lib/src/utils/image_helper.dart +++ b/lib/src/utils/image_helper.dart @@ -13,45 +13,38 @@ class ImageHelper { /// Returns the [image] full url - for a specific [imageSize] if needed. /// - /// Returns null is [barcode] is null. - /// E.g. "https://static.openfoodfacts.org/images/products/359/671/046/2858/front_fr.4.100.jpg" - static String? buildUrl( - final String? barcode, + /// E.g. "https://images.openfoodfacts.org/images/products/359/671/046/2858/front_fr.4.100.jpg" + static String getLocalizedProductImageUrl( + final String barcode, final ProductImage image, { final ImageSize? imageSize, final UriProductHelper uriHelper = uriHelperFoodProd, - final String? root, }) => - barcode == null - ? null - : '${getProductImageRootUrl( - barcode, - uriHelper: uriHelper, - root: root, - )}' - '/' - '${getProductImageFilename( - image, - imageSize: imageSize, - )}'; + '${uriHelper.getProductImageRootUrl(barcode)}' + '/' + '${getProductImageFilename( + image, + imageSize: imageSize, + )}'; - /// Returns the [image] full url for an uploaded image. + /// Returns the [image] full url for an uploaded ("raw") image. /// - /// E.g. "https://static.openfoodfacts.org/images/products/359/671/046/2858/1.400.jpg" + /// E.g. "https://images.openfoodfacts.org/images/products/359/671/046/2858/1.400.jpg" static String getUploadedImageUrl( final String barcode, final int imageId, final ImageSize imageSize, { final UriProductHelper uriHelper = uriHelperFoodProd, }) => - '${getProductImageRootUrl(barcode, uriHelper: uriHelper)}' + '${uriHelper.getProductImageRootUrl(barcode)}' '/' - '${_getUploadedImageFilename(imageId, imageSize)}'; + '${getUploadedImageFilename(imageId, imageSize)}'; /// Returns the [image] filename - for a specific [imageSize] if needed. /// /// By default uses the own [image]'s size field. /// E.g. "front_fr.4.100.jpg" + /// cf. https://github.com/openfoodfacts/smooth-app/issues/3065 static String getProductImageFilename( final ProductImage image, { final ImageSize? imageSize, @@ -62,7 +55,9 @@ class ImageHelper { '.jpg'; /// Returns the filename of an uploaded image. - static String _getUploadedImageFilename( + /// + /// cf. https://github.com/openfoodfacts/smooth-app/issues/3065 + static String getUploadedImageFilename( final int imageId, final ImageSize imageSize, ) { @@ -80,38 +75,4 @@ class ImageHelper { return '$imageId.jpg'; } } - - /// Returns the barcode sub-folder (without trailing '/'). - /// - /// For instance: - /// * `12345678` for barcode `12345678` - /// * `123/456/789` for barcode `123456789` - /// * `123/456/789/0` for barcode `1234567890` - /// * `123/456/789/0123` for barcode `1234567890123` - static String getBarcodeSubPath(final String barcode) { - if (barcode.length < 9) { - return barcode; - } - final String p1 = barcode.substring(0, 3); - final String p2 = barcode.substring(3, 6); - final String p3 = barcode.substring(6, 9); - if (barcode.length == 9) { - return '$p1/$p2/$p3'; - } - final String p4 = barcode.substring(9); - return '$p1/$p2/$p3/$p4'; - } - - /// Returns the web folder of the product images (without trailing '/') - /// - /// E.g. "https://static.openfoodfacts.org/images/products/359/671/046/2858" - static String getProductImageRootUrl( - final String barcode, { - final UriProductHelper uriHelper = uriHelperFoodProd, - String? root, - }) { - root ??= uriHelper.imageUrlBase; - final String separator = root.endsWith('/') ? '' : '/'; - return '$root$separator${getBarcodeSubPath(barcode)}'; - } } diff --git a/lib/src/utils/json_helper.dart b/lib/src/utils/json_helper.dart index e45f8532cc..3ca4cae3b9 100644 --- a/lib/src/utils/json_helper.dart +++ b/lib/src/utils/json_helper.dart @@ -138,8 +138,8 @@ class JsonHelper { } final int? width = JsonObject.parseInt(numberObject['w']); final int? height = JsonObject.parseInt(numberObject['h']); + final String? url = numberObject['url']; - // TODO(monsieurtanuki): add field "url"? var image = ProductImage( field: field, size: size, @@ -154,6 +154,7 @@ class JsonHelper { y2: y2, width: width, height: height, + url: url, ); imageList.add(image); } @@ -191,19 +192,22 @@ class JsonHelper { continue; } final Map item = {}; - item['sizes'] = >{}; + item['sizes'] = >{}; bool first = true; for (final ProductImage productImage in list) { if (productImage.size == null) { continue; } - final Map size = {}; + final Map size = {}; if (productImage.width != null) { size['w'] = productImage.width!; } if (productImage.height != null) { size['h'] = productImage.height!; } + if (productImage.url != null) { + size['url'] = productImage.url!; + } item['sizes']![productImage.size!.number] = size; if (first) { first = false; @@ -211,25 +215,25 @@ class JsonHelper { item['rev'] = productImage.rev.toString(); } if (productImage.imgid != null) { - item['imgid'] = productImage.imgid; + item['imgid'] = productImage.imgid!; } if (productImage.angle != null) { item['angle'] = productImage.angle!.degree.toString(); } if (productImage.coordinatesImageSize != null) { - item['coordinates_image_size'] = productImage.coordinatesImageSize; + item['coordinates_image_size'] = productImage.coordinatesImageSize!; } if (productImage.x1 != null) { - item['x1'] = productImage.x1; + item['x1'] = productImage.x1!; } if (productImage.y1 != null) { - item['y1'] = productImage.y1; + item['y1'] = productImage.y1!; } if (productImage.x2 != null) { - item['x2'] = productImage.x2; + item['x2'] = productImage.x2!; } if (productImage.y2 != null) { - item['y2'] = productImage.y2; + item['y2'] = productImage.y2!; } } } diff --git a/lib/src/utils/open_food_api_configuration.dart b/lib/src/utils/open_food_api_configuration.dart index 5cc0d3a952..d4fdc5187e 100644 --- a/lib/src/utils/open_food_api_configuration.dart +++ b/lib/src/utils/open_food_api_configuration.dart @@ -14,6 +14,10 @@ import 'uri_helper.dart'; /// E.g. /// /// ```dart +/// OpenFoodAPIConfiguration.userAgent = UserAgent( +/// name: '', +/// ); +/// /// OpenFoodAPIConfiguration.globalLanguages = [ /// OpenFoodFactsLanguage.ENGLISH, /// ]; @@ -77,16 +81,14 @@ class OpenFoodAPIConfiguration { /// Uri of the main requests to the backend (OFF). const UriProductHelper uriHelperFoodProd = UriProductHelper( - host: 'world.openfoodfacts.org', - imageUrlBase: 'https://static.openfoodfacts.org/images/products/', + domain: 'openfoodfacts.org', ); /// Uri of the test requests to the backend (OFF). const UriProductHelper uriHelperFoodTest = UriProductHelper( - host: 'world.openfoodfacts.net', + domain: 'openfoodfacts.net', userInfoForPatch: HttpHelper.userInfoForTest, isTestMode: true, - imageUrlBase: 'https://static.openfoodfacts.net/images/products/', ); /// Uri of the main requests to the backend (RobotOff). diff --git a/lib/src/utils/product_helper.dart b/lib/src/utils/product_helper.dart index 64a916e5ea..68393d37c5 100644 --- a/lib/src/utils/product_helper.dart +++ b/lib/src/utils/product_helper.dart @@ -30,9 +30,13 @@ class ProductHelper { return; } + if (product.barcode == null) { + return; + } + for (ProductImage image in product.images!) { - image.url = ImageHelper.buildUrl( - product.barcode, + image.url = ImageHelper.getLocalizedProductImageUrl( + product.barcode!, image, uriHelper: uriHelper, ); diff --git a/lib/src/utils/uri_helper.dart b/lib/src/utils/uri_helper.dart index 8be6d95a98..497b3cb1ec 100644 --- a/lib/src/utils/uri_helper.dart +++ b/lib/src/utils/uri_helper.dart @@ -100,18 +100,19 @@ class UriHelper { /// [UriHelper] specific for products (e.g. off, obf, opf, opff). class UriProductHelper extends UriHelper { const UriProductHelper({ - required super.host, + required this.domain, super.scheme = 'https', super.isTestMode = false, this.userInfoForPatch, - required this.imageUrlBase, super.defaultAddUserAgentParameters = true, - }); + }) : super(host: 'world.$domain'); final String? userInfoForPatch; - /// Url base for images: needs to match more or less scheme and host. - final String imageUrlBase; + final String domain; + + /// Returns the product images folder (without trailing '/'). + String getImageUrlBase() => '$scheme://images.$domain/images/products'; Uri getPatchUri({ required final String path, @@ -121,4 +122,31 @@ class UriProductHelper extends UriHelper { addUserAgentParameters: false, userInfo: userInfoForPatch, ); + + /// Returns the web folder of the product images (without trailing '/') + /// + /// E.g. "https://images.openfoodfacts.org/images/products/359/671/046/2858" + String getProductImageRootUrl(final String barcode) => + '${getImageUrlBase()}/${getBarcodeSubPath(barcode)}'; + + /// Returns the barcode sub-folder (without trailing '/'). + /// + /// For instance: + /// * `12345678` for barcode `12345678` + /// * `123/456/789` for barcode `123456789` + /// * `123/456/789/0` for barcode `1234567890` + /// * `123/456/789/0123` for barcode `1234567890123` + static String getBarcodeSubPath(final String barcode) { + if (barcode.length < 9) { + return barcode; + } + final String p1 = barcode.substring(0, 3); + final String p2 = barcode.substring(3, 6); + final String p3 = barcode.substring(6, 9); + if (barcode.length == 9) { + return '$p1/$p2/$p3'; + } + final String p4 = barcode.substring(9); + return '$p1/$p2/$p3/$p4'; + } } diff --git a/test/api_add_product_image_test.dart b/test/api_add_product_image_test.dart index 46e7f157fb..365b4d34e1 100644 --- a/test/api_add_product_image_test.dart +++ b/test/api_add_product_image_test.dart @@ -165,9 +165,8 @@ void main() { final String? imgid = await getImgid(barcode, imageField, language); expect(imgid, isNotNull); - final String productImageRootUrl = ImageHelper.getProductImageRootUrl( + final String productImageRootUrl = uriHelper.getProductImageRootUrl( barcode, - root: uriHelper.imageUrlBase, ); final String uploadedImageUrl = '$productImageRootUrl/$imgid.jpg'; final List uploadedSize = await getJpegUrlSize(uploadedImageUrl); diff --git a/test/api_get_product_test.dart b/test/api_get_product_test.dart index e7e55b2f5f..0c5b8cfd6f 100644 --- a/test/api_get_product_test.dart +++ b/test/api_get_product_test.dart @@ -726,7 +726,7 @@ void main() { image.size == ImageSize.DISPLAY && image.language == OpenFoodFactsLanguage.GERMAN) .url, - 'https://static.openfoodfacts.org/images/products/500/011/254/8167/ingredients_de.7.400.jpg'); + 'https://images.openfoodfacts.org/images/products/500/011/254/8167/ingredients_de.7.400.jpg'); //Get product without setting ProductField configurations = ProductQueryConfiguration( @@ -779,7 +779,7 @@ void main() { image.size == ImageSize.DISPLAY && image.language == OpenFoodFactsLanguage.GERMAN) .url, - 'https://static.openfoodfacts.org/images/products/500/011/254/8167/ingredients_de.7.400.jpg'); + 'https://images.openfoodfacts.org/images/products/500/011/254/8167/ingredients_de.7.400.jpg'); //Get product without setting OpenFoodFactsLanguage configurations = ProductQueryConfiguration( @@ -853,7 +853,7 @@ void main() { image.size == ImageSize.DISPLAY && image.language == OpenFoodFactsLanguage.GERMAN) .url, - 'https://static.openfoodfacts.org/images/products/500/011/254/8167/ingredients_de.7.400.jpg'); + 'https://images.openfoodfacts.org/images/products/500/011/254/8167/ingredients_de.7.400.jpg'); final Set improvements = result.product!.getProductImprovements(); diff --git a/test/api_json_to_from_test.dart b/test/api_json_to_from_test.dart index 94dd0021e7..d803adbe2b 100644 --- a/test/api_json_to_from_test.dart +++ b/test/api_json_to_from_test.dart @@ -14,7 +14,7 @@ void main() { await OpenFoodAPIClient.getProductV3( ProductQueryConfiguration( BARCODE_DANISH_BUTTER_COOKIES, - fields: [ProductField.IMAGES], + fields: [ProductField.IMAGES, ProductField.BARCODE], version: ProductQueryVersion.v3, ), ); diff --git a/test/api_not_food_get_product_test.dart b/test/api_not_food_get_product_test.dart index 908d11fdb0..4269d30a76 100644 --- a/test/api_not_food_get_product_test.dart +++ b/test/api_not_food_get_product_test.dart @@ -8,16 +8,13 @@ void main() { OpenFoodAPIConfiguration.globalUser = TestConstants.PROD_USER; const UriProductHelper uriHelperBeautyProd = UriProductHelper( - host: 'world.openbeautyfacts.org', - imageUrlBase: 'https://static.openbeautyfacts.org/images/products/', + domain: 'openbeautyfacts.org', ); const UriProductHelper uriHelperProductsProd = UriProductHelper( - host: 'world.openproductsfacts.org', - imageUrlBase: 'https://static.openproductsfacts.org/images/products/', + domain: 'openproductsfacts.org', ); const UriProductHelper uriHelperPetFoodProd = UriProductHelper( - host: 'world.openpetfoodfacts.org', - imageUrlBase: 'https://static.openpetfoodfacts.org/images/products/', + domain: 'openpetfoodfacts.org', ); const String beautyBarcode = '4056489234692'; diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 9c45af41f8..4018f44b9e 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -197,9 +197,8 @@ void main() { test('Get Uri with different uriScheme', () { OpenFoodAPIConfiguration.uuid = null; final UriProductHelper uriHelper = UriProductHelper( - host: 'world.openfoodfacts.org', + domain: 'openfoodfacts.org', scheme: 'http', - imageUrlBase: 'whatever', ); final Uri uri = uriHelper.getUri( path: '/test/test.pl',