Skip to content

Commit

Permalink
feat: #158 - introduction to folksonomy (#332)
Browse files Browse the repository at this point in the history
New files:
* `api_folksonomy_test.dart`: Tests around folksonomy
* `api_saveProduct_test.dart`: unrelated test fine-tuning
* `folksonomy.dart`: Client calls of the Folksonomy API (Open Food Facts)
* `KeyStats.dart`: Folksonomy: statistics around a tag key.
* `ProductList.dart`: Folksonomy: current value for a product and a tag key.
* `ProductStats.dart`: Folksonomy: statistics about the tag keys on a product.
* `ProductTag.dart`: Folksonomy product tag: for this barcode, that value is set for that key.

Impacted files:
* `JsonHelper.dart`: added a helper method around timestamps
* `OpenFoodAPIConfiguration.dart`: added reference URIs for folksonomy
* `openfoodfacts.dart`: exported folksonomy files
* `UriHelper.dart`: added a helper method for folksonomy URIs
  • Loading branch information
monsieurtanuki committed Dec 20, 2021
1 parent 208bc20 commit f7b254e
Show file tree
Hide file tree
Showing 15 changed files with 897 additions and 10 deletions.
380 changes: 380 additions & 0 deletions lib/folksonomy.dart
@@ -0,0 +1,380 @@
import 'dart:async';
import 'dart:convert';

import 'package:http/http.dart';

import 'model/KeyStats.dart';
import 'model/ProductList.dart';
import 'model/ProductStats.dart';
import 'model/ProductTag.dart';
import 'utils/HttpHelper.dart';
import 'utils/QueryType.dart';
import 'utils/UriHelper.dart';

/// Client calls of the Folksonomy API (Open Food Facts)
///
/// cf. https://api.folksonomy.openfoodfacts.org/docs
class FolksonomyAPIClient {
FolksonomyAPIClient._();

/// "hello world"
static Future<void> hello({
final QueryType? queryType,
}) async {
final Response response = await HttpHelper().doGetRequest(
UriHelper.getFolksonomyUri(
path: '/',
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
}

/// Returns all the [ProductStats], with an optional filter.
///
/// The result can be filtered with that [key], or with [key] = [value].
static Future<List<ProductStats>> getProductStats({
final String? key,
final String? value,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
/* TODO
if (owner != null) {
parameters['owner'] = owner;
}
*/
if (key == null && value != null) {
throw Exception(
'Does a value have a meaning without its key? I don\'t think so.');
}
if (key != null) {
parameters['k'] = key;
}
if (value != null) {
parameters['v'] = value;
}
final Response response = await HttpHelper().doGetRequest(
UriHelper.getFolksonomyUri(
path: 'products/stats',
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
final List<ProductStats> result = <ProductStats>[];
if (response.body == 'null') {
// not found
return result;
}
final List<dynamic> json = jsonDecode(response.body) as List<dynamic>;
for (var element in json) {
result.add(ProductStats.fromJson(element));
}
return result;
}

/// Returns all the products with that [key].
///
/// The key of the returned map is the barcode, the value is the tag value.
static Future<Map<String, String>> getProducts({
required final String key,
final String? value,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
/* TODO
if (owner != null) {
parameters['owner'] = owner;
}
*/
parameters['k'] = key;
if (value != null) {
parameters['v'] = value;
}
final Response response = await HttpHelper().doGetRequest(
UriHelper.getFolksonomyUri(
path: 'products',
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
final Map<String, String> result = <String, String>{};
final List<dynamic> json = jsonDecode(response.body) as List<dynamic>;
for (var element in json) {
final ProductList productList = ProductList.fromJson(element);
if (productList.key != key) {
throw Exception('Unexpected key: ${productList.key}');
}
result[productList.barcode] = productList.value;
}
return result;
}

/// Returns all the [ProductTag]s for this product
///
/// The key of the returned map is the tag key.
static Future<Map<String, ProductTag>> getProductTags({
required final String barcode,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
/* TODO
if (owner != null) {
parameters['owner'] = owner;
}
*/
final Response response = await HttpHelper().doGetRequest(
UriHelper.getFolksonomyUri(
path: 'product/$barcode',
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
final Map<String, ProductTag> result = <String, ProductTag>{};
if (response.body == 'null') {
// not found
return result;
}
final List<dynamic> json = jsonDecode(response.body) as List<dynamic>;
for (var element in json) {
final ProductTag productTag = ProductTag.fromJson(element);
result[productTag.key] = productTag;
}
return result;
}

/// Returns the [ProductTag] for this product and this tag key
///
/// Returns null if not found.
static Future<ProductTag?> getProductTag({
required final String barcode,
required final String key,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
/* TODO
if (owner != null) {
parameters['owner'] = owner;
}
*/
final Response response = await HttpHelper().doGetRequest(
UriHelper.getFolksonomyUri(
path: 'product/$barcode/$key',
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
if (response.body == 'null') {
// not found
return null;
}
final Map<String, dynamic> json =
jsonDecode(response.body) as Map<String, dynamic>;
return ProductTag.fromJson(json);
}

/// Returns all the [ProductTag]s for this product, with their subkeys.
///
/// The key of the returned map is the key.
static Future<Map<String, ProductTag>> getProductTagWithSubKeys({
required final String barcode,
required final String key,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
/* TODO
if (owner != null) {
parameters['owner'] = owner;
}
*/
final Response response = await HttpHelper().doGetRequest(
UriHelper.getFolksonomyUri(
path: 'product/$barcode/$key*', // look at the star!
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
final Map<String, ProductTag> result = <String, ProductTag>{};
if (response.body == 'null') {
// not found
return result;
}
final List<dynamic> json = jsonDecode(response.body) as List<dynamic>;
for (var element in json) {
final ProductTag productTag = ProductTag.fromJson(element);
result[productTag.key] = productTag;
}
return result;
}

/* TODO
Future<void> deleteProductTag({
required final String barcode,
required final String key,
required final int version,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
/* TODO
if (owner != null) {
parameters['owner'] = owner;
}
*/
final Response response = await HttpHelper().doDeleteRequest(
UriHelper.getFolksonomyUri(
path: 'product/$barcode/$key',
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
}
*/

/// Returns the versions of [ProductTag] for this [barcode] and [key].
static Future<List<ProductTag>> getProductTagVersions({
required final String barcode,
required final String key,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
/* TODO
if (owner != null) {
parameters['owner'] = owner;
}
*/
final Response response = await HttpHelper().doGetRequest(
UriHelper.getFolksonomyUri(
path: 'product/$barcode/$key/versions',
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
final List<ProductTag> result = <ProductTag>[];
if (response.body == 'null') {
// not found
return result;
}
final List<dynamic> json = jsonDecode(response.body) as List<dynamic>;
for (var element in json) {
result.add(ProductTag.fromJson(element));
}
return result;
}

/* TODO
/// productTag.version must be equal to previous version + 1
static Future<void> updateProductTag({
required final ProductTag productTag,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
/* TODO
if (owner != null) {
parameters['owner'] = owner;
}
*/
final Response response = await HttpHelper().doPutRequest(
UriHelper.getFolksonomyUri(
path: 'product',
queryParameters: parameters,
queryType: queryType,
),
productTag.toJson().toString(),
userAgent: OpenFoodAPIConfiguration.userAgent,
queryType: queryType,
);
_checkResponse(response);
}
*/

/* TODO
/// productTag.version must be equal to 1
static Future<void> addProductTag({
required final ProductTag productTag,
final User? user,
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
/* TODO
if (owner != null) {
parameters['owner'] = owner;
}
*/
final Response response = await HttpHelper().doPostRequest(
UriHelper.getFolksonomyUri(
path: 'product',
queryParameters: parameters,
queryType: queryType,
),
{}, // TODO later productTag.toJson(),
user,
queryType: queryType,
);
_checkResponse(response);
}
*/

/// Returns the list of tag keys with statistics.
static Future<Map<String, KeyStats>> getKeys({
final QueryType? queryType,
}) async {
final Map<String, String> parameters = <String, String>{};
/* TODO "The keys list can be restricted to private tags from some owner"
if (owner != null) {
parameters['owner'] = owner;
}
*/
final Response response = await HttpHelper().doGetRequest(
UriHelper.getFolksonomyUri(
path: 'keys',
queryParameters: parameters,
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
final Map<String, KeyStats> result = <String, KeyStats>{};
final List<dynamic> json = jsonDecode(response.body) as List<dynamic>;
for (var element in json) {
final KeyStats item = KeyStats.fromJson(element);
result[item.key] = item;
}
return result;
}

static Future<void> ping({
final QueryType? queryType,
}) async {
final Response response = await HttpHelper().doGetRequest(
UriHelper.getFolksonomyUri(
path: 'ping',
queryType: queryType,
),
queryType: queryType,
);
_checkResponse(response);
}

/// Throws a detailed exception if relevant. Does nothing if [response] is OK.
static void _checkResponse(final Response response) {
if (response.statusCode != 200) {
// TODO have a look at ValidationError in https://api.folksonomy.openfoodfacts.org/docs
throw Exception('Wrong status code: ${response.statusCode}');
}
}
}

0 comments on commit f7b254e

Please sign in to comment.