Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature_2/#210 - add nutrient order and list #272

Merged
merged 3 commits into from
Nov 10, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions lib/openfoodfacts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:http/http.dart';
import 'package:openfoodfacts/interface/JsonObject.dart';
import 'package:openfoodfacts/model/KnowledgePanels.dart';
import 'package:openfoodfacts/model/OcrIngredientsResult.dart';
import 'package:openfoodfacts/model/OrderedNutrients.dart';
import 'package:openfoodfacts/model/TaxonomyAdditive.dart';
import 'package:openfoodfacts/model/TaxonomyAllergen.dart';
import 'package:openfoodfacts/model/TaxonomyCategory.dart';
Expand Down Expand Up @@ -855,4 +856,33 @@ class OpenFoodAPIClient {
return KnowledgePanels.empty();
}
}

/// Returns the nutrient hierarchy specific to a country, localized.
///
/// [cc] is the country code, as ISO 3166-1 alpha-2
static Future<OrderedNutrients?> getOrderedNutrients({
required final String cc,
required final OpenFoodFactsLanguage language,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we keep all the features in sync and allow to pass a list instead. Is a list of languages even supported on the server

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In first approach I don't think it would make sense to support several languages at the same time: here we're talking about nutrients, not localized food or ingredient names where we would need fallbacks.

Copy link
Member

@M123-dev M123-dev Nov 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay makes sense

final QueryType? queryType,
}) async {
final Uri uri = UriHelper.getUri(
path: 'cgi/nutrients.pl',
queryType: queryType,
queryParameters: <String, String>{'cc': cc, 'lc': language.code},
);

try {
final Response response = await HttpHelper().doGetRequest(
uri,
userAgent: OpenFoodAPIConfiguration.userAgent,
);
if (response.statusCode != 200) {
return null;
}
final json = jsonDecode(response.body);
return OrderedNutrients.fromJson(json);
} catch (exception) {
return null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably don't need to give detailed error messages since the package user doesn't provide any custom info themselves but at least when a error occurs we should throw the exception, shouldn't we?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I've just pushed fixes - please review them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, feel free to merge

}
}
}
102 changes: 51 additions & 51 deletions test/ordered_nutrient_test.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import 'dart:convert';

import 'package:openfoodfacts/model/OrderedNutrient.dart';
import 'package:openfoodfacts/model/OrderedNutrients.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:openfoodfacts/utils/OpenFoodAPIConfiguration.dart';
import 'package:openfoodfacts/utils/QueryType.dart';
import 'package:test/test.dart';
import 'package:http/http.dart' as http;
import 'package:openfoodfacts/utils/HttpHelper.dart';

/// Tests related to [OrderedNutrient] and [OrderedNutrients]
void main() {
OpenFoodAPIConfiguration.globalQueryType = QueryType.TEST;

OrderedNutrient? _findOrderedNutrient(
final List<OrderedNutrient>? list,
final String nutrientId,
) {
if (list == null) {
return null;
}
for (final OrderedNutrient item in list) {
if (item.id == nutrientId) {
return item;
}
final OrderedNutrient? found =
_findOrderedNutrient(item.subNutrients, nutrientId);
if (found != null) {
return found;
}
}
return null;
}

group('$OpenFoodAPIClient ordered nutrients', () {
test('find expected nutrients', () async {
// Very long list, experimentally created from the 3 initial URLs.
Expand Down Expand Up @@ -126,65 +142,49 @@ void main() {
'zinc',
};

const List<String> urls = [
'https://fr.openfoodfacts.org/cgi/nutrients.pl',
'https://us.openfoodfacts.org/cgi/nutrients.pl',
'https://us-es.openfoodfacts.org/cgi/nutrients.pl',
];
for (final String url in urls) {
final http.Response response =
await HttpHelper().doGetRequest(Uri.parse(url));
final json = jsonDecode(response.body);
final OrderedNutrients orderedNutrients =
OrderedNutrients.fromJson(json);
const Set<String> countries = {'fr', 'br', 'us'};
const OpenFoodFactsLanguage language = OpenFoodFactsLanguage.AFRIKAANS;
for (final String country in countries) {
final OrderedNutrients? orderedNutrients =
await OpenFoodAPIClient.getOrderedNutrients(
cc: country,
language: language,
);
expect(orderedNutrients, isNotNull);
for (final String expectedNutrient in expectedNutrients) {
expect(
_findOrderedNutrient(orderedNutrients.nutrients, expectedNutrient),
_findOrderedNutrient(orderedNutrients!.nutrients, expectedNutrient),
isNotNull,
reason: 'Could not find $expectedNutrient in $url',
reason:
'Could not find nutrient $expectedNutrient for country $country',
);
}
}
});

test('check localized "energy"', () async {
const String nutrientId = 'energy';
const Map<String, String> energies = {
'https://fr.openfoodfacts.org/cgi/nutrients.pl': 'Énergie',
'https://us.openfoodfacts.org/cgi/nutrients.pl': 'Energy',
'https://us-es.openfoodfacts.org/cgi/nutrients.pl': 'Energía',
const Map<OpenFoodFactsLanguage, String> energies = {
OpenFoodFactsLanguage.FRENCH: 'Énergie',
OpenFoodFactsLanguage.SPANISH: 'Energía',
OpenFoodFactsLanguage.ENGLISH: 'Energy',
OpenFoodFactsLanguage.PORTUGUESE: 'Energia',
};
for (final String url in energies.keys) {
final http.Response response =
await HttpHelper().doGetRequest(Uri.parse(url));
final json = jsonDecode(response.body);
final OrderedNutrients orderedNutrients =
OrderedNutrients.fromJson(json);
final OrderedNutrient? found =
_findOrderedNutrient(orderedNutrients.nutrients, nutrientId);
expect(found, isNotNull);
expect(found!.name, energies[url]);
const Set<String> countries = {'us', 'it', 'br'};
for (final OpenFoodFactsLanguage language in energies.keys) {
for (final String country in countries) {
final OrderedNutrients? orderedNutrients =
await OpenFoodAPIClient.getOrderedNutrients(
cc: country,
language: language,
);
expect(orderedNutrients, isNotNull);
final OrderedNutrient? found =
_findOrderedNutrient(orderedNutrients!.nutrients, nutrientId);
expect(found, isNotNull);
expect(found!.name, energies[language]);
}
}
});
});
}

OrderedNutrient? _findOrderedNutrient(
final List<OrderedNutrient>? list,
final String nutrientId,
) {
if (list == null) {
return null;
}
for (final OrderedNutrient item in list) {
if (item.id == nutrientId) {
return item;
}
final OrderedNutrient? found =
_findOrderedNutrient(item.subNutrients, nutrientId);
if (found != null) {
return found;
}
}
return null;
}