Initial commit
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -88,3 +88,4 @@ fastlane/test_output
|
|||||||
# https://github.com/johnno1962/injectionforxcode
|
# https://github.com/johnno1962/injectionforxcode
|
||||||
|
|
||||||
iOSInjectionProject/
|
iOSInjectionProject/
|
||||||
|
.DS_Store
|
||||||
|
|||||||
7
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
29
Package.swift
Normal file
29
Package.swift
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// swift-tools-version: 5.9
|
||||||
|
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "swift-openfoodfacts-sdk",
|
||||||
|
platforms: [
|
||||||
|
.macOS(.v14)
|
||||||
|
],
|
||||||
|
products: [
|
||||||
|
// Products define the executables and libraries a package produces, making them visible to other packages.
|
||||||
|
.library(
|
||||||
|
name: "swift-openfoodfacts-sdk",
|
||||||
|
targets: ["swift-openfoodfacts-sdk"]
|
||||||
|
),
|
||||||
|
.executable(name: "exe", targets: ["exe"])
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||||
|
// Targets can depend on other targets in this package and products from dependencies.
|
||||||
|
.target(
|
||||||
|
name: "swift-openfoodfacts-sdk"),
|
||||||
|
.executableTarget(name: "exe", dependencies: ["swift-openfoodfacts-sdk"]),
|
||||||
|
.testTarget(
|
||||||
|
name: "swift-openfoodfacts-sdkTests",
|
||||||
|
dependencies: ["swift-openfoodfacts-sdk"]),
|
||||||
|
]
|
||||||
|
)
|
||||||
12
Sources/exe/main.swift
Normal file
12
Sources/exe/main.swift
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import Foundation
|
||||||
|
import swift_openfoodfacts_sdk
|
||||||
|
|
||||||
|
let off = OpenFoodFactsClient()
|
||||||
|
print("Hello world")
|
||||||
|
|
||||||
|
do {
|
||||||
|
let res = try await off.getProductByBarcode("3017620422003")
|
||||||
|
print(res)
|
||||||
|
} catch {
|
||||||
|
print("\(error)")
|
||||||
|
}
|
||||||
255
Sources/swift-openfoodfacts-sdk/swift_openfoodfacts_sdk.swift
Normal file
255
Sources/swift-openfoodfacts-sdk/swift_openfoodfacts_sdk.swift
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
// The Swift Programming Language
|
||||||
|
// https://docs.swift.org/swift-book
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class OpenFoodFactsClient {
|
||||||
|
let version: Int = 2
|
||||||
|
public var prod: Bool = false
|
||||||
|
var baseURL: URL? {
|
||||||
|
if prod {
|
||||||
|
return URL(string: "https://world.openfoodfacts.org/api/v\(version)")
|
||||||
|
} else {
|
||||||
|
return URL(string: "https://world.openfoodfacts.net/api/v\(version)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
public func getProductByBarcode(_ barcode: String) async throws -> Result {
|
||||||
|
guard let endpoint = baseURL?.appendingPathComponent("product/\(barcode)") else { throw OFFError.invalidURL }
|
||||||
|
var request = URLRequest(url: endpoint)
|
||||||
|
request.setValue("application/json", forHTTPHeaderField: "accept")
|
||||||
|
let (data, response) = try await URLSession.shared.data(for: request)
|
||||||
|
|
||||||
|
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
|
||||||
|
throw OFFError.invalidResponse
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
return try JSONDecoder().decode(Result.self, from: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Result: Codable {
|
||||||
|
var code: String
|
||||||
|
var status: Int
|
||||||
|
var status_verbose: String
|
||||||
|
var product: Product
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Product: Codable {
|
||||||
|
var abbreviated_product_name: String // Abbreviated name in requested language
|
||||||
|
var code: String // barcode of the product (can be EAN-13 or internal codes for some food stores), for products without a barcode, Open Food Facts assigns a number starting with the 200 reserved prefix
|
||||||
|
var codes_tags: [String] // A value which is the type of barcode "code-13" or "code-8" and A series of mask for the barcode It helps retrieve barcodes starting by ]
|
||||||
|
var generic_name: String // Legal name of the product as regulated by the European authorities.
|
||||||
|
var id: String // internal identifier for the product, usually set to the value of code, except on the producers platform where it is prefixed by the owner
|
||||||
|
var lc: String // Main language of the product. This is a duplicate of lang property (for historical reasons).
|
||||||
|
var lang: String // Main language of the product.
|
||||||
|
// This should be the main language of product packaging (if one is predominant).
|
||||||
|
// Main language is also used to decide which ingredients list to parse.
|
||||||
|
var nova_group: Int // Nova group as an Int from 1 to 4. See https://world.openfoodfacts.org/nova
|
||||||
|
var nova_groups: String
|
||||||
|
var obsolete: String
|
||||||
|
var obsolete_since_date: String // A date at which the product was declared obsolete. This means it's not produced any more.
|
||||||
|
var product_name: String // The name of the product
|
||||||
|
var product_name_en: String // The name of the product can also be in many other languages like product_name_fr (for French).
|
||||||
|
var product_quantity: String // The size in g or ml for the whole product. It's a normalized version of the quantity field.
|
||||||
|
var quantity: String // Quantity and Unit.
|
||||||
|
var additives_n: Int // Number of food additives.
|
||||||
|
var checked: String
|
||||||
|
var complete: Int
|
||||||
|
var completeness: Float
|
||||||
|
var food_groups: String
|
||||||
|
var food_groups_tags: [String]
|
||||||
|
var nutrient_levels: NutrientLevel
|
||||||
|
|
||||||
|
var pnns_groups_1: String // Category of food according to French Nutrition and Health Program
|
||||||
|
var pnns_groups_1_tags: [String]
|
||||||
|
var pnns_groups_2: String // Sub Category of food according to French Nutrition and Health Program
|
||||||
|
var pnns_groups_2_tags: [String]
|
||||||
|
var popularity_key: Int
|
||||||
|
var popularity_tags: [String]
|
||||||
|
var scans_n: Int
|
||||||
|
var unique_scans_n: Int
|
||||||
|
var serving_quantity: String // Normalized version of serving_size.Note that this is NOT the number of servings by product.(in perl, see normalize_serving_size)
|
||||||
|
var serving_size: String // Serving size text(generally in g or ml).We expect a quantity + unit but the user is free to input any string.
|
||||||
|
var brands: String
|
||||||
|
var brands_tags: [String]
|
||||||
|
var categories: String
|
||||||
|
var categories_hierarchy: [String]
|
||||||
|
var categories_lc: String
|
||||||
|
var categories_tags: [String]
|
||||||
|
var checkers_tags: [String]
|
||||||
|
var countries: String // List of countries where the product is sold.
|
||||||
|
var countries_hierarchy: [String]
|
||||||
|
var countries_lc: String
|
||||||
|
var countries_tags: [String]
|
||||||
|
var manufacturing_places: String
|
||||||
|
var nutrient_levels_tags: [String]
|
||||||
|
var image_front_small_url: String
|
||||||
|
var image_front_thumb_url: String
|
||||||
|
var image_front_url: String
|
||||||
|
var image_nutrition_small_url: String
|
||||||
|
var image_nutrition_thumb_url: String
|
||||||
|
var image_nutrition_url: String
|
||||||
|
var image_small_url: String
|
||||||
|
var image_thumb_url: String
|
||||||
|
var image_url: String
|
||||||
|
var ingredients: [Ingredient]
|
||||||
|
var nutriments: Nutrition.Nutriments
|
||||||
|
|
||||||
|
public struct NutrientLevel: Codable {
|
||||||
|
var fat: TrafficLightIndicator
|
||||||
|
var salt: TrafficLightIndicator
|
||||||
|
var saturated_fat: TrafficLightIndicator
|
||||||
|
var sugars: TrafficLightIndicator
|
||||||
|
|
||||||
|
public enum TrafficLightIndicator: String, Codable {
|
||||||
|
case low
|
||||||
|
case moderate
|
||||||
|
case high
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case fat, salt, sugars
|
||||||
|
case saturated_fat = "saturated-fat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Ingredient: Codable {
|
||||||
|
var additives_tags: [String]?
|
||||||
|
var allergens: String?
|
||||||
|
var allergens_lc: String?
|
||||||
|
var allergens_hierarchy: [String]?
|
||||||
|
var allergens_tags: [String]?
|
||||||
|
var id: String
|
||||||
|
var percent_estimate: Float?
|
||||||
|
var percent_max: Float?
|
||||||
|
var percent_min: Float?
|
||||||
|
var text: String?
|
||||||
|
var vegan: String?
|
||||||
|
var vegetarian: String?
|
||||||
|
var ingredients: [Ingredient]?
|
||||||
|
var ingredients_analysis: IngredientAnalysis?
|
||||||
|
var ingredients_analysis_tags: [String]?
|
||||||
|
var ingredients_from_or_that_may_be_from_palm_oil_n: Int?
|
||||||
|
var ingredients_from_palm_oil_n: Int?
|
||||||
|
var ingredients_hierarchy: [String]?
|
||||||
|
var ingredients_n: Int?
|
||||||
|
var ingredients_n_tags: [String]?
|
||||||
|
var ingredients_original_tags: [String]?
|
||||||
|
var ingredients_percent_analysis: Int?
|
||||||
|
var ingredients_tags: [String]?
|
||||||
|
var ingredients_lc: String? // Language that was used to parse the ingredient list. If ingredients_text is available for the product main language (lang), ingredients_lc=lang, otherwise we look at ingredients_text fields for other languages and set ingredients_lc to the first non-empty ingredient_text.
|
||||||
|
var ingredients_text: String? // Raw list of ingredients. This will get automatically parsed and get used to compute the Eco-Score or find allergens, etc..
|
||||||
|
// It's a copy of ingredients_text in the main language of the product (see lang proprety).
|
||||||
|
var ingredients_text_with_allergens: String? // Same text as ingredients_text but where allergens have HTML elements around them to identify them
|
||||||
|
var ingredients_that_may_be_from_palm_oil_n: Int?
|
||||||
|
var ingredients_with_specified_percent_n: Int?
|
||||||
|
var ingredients_with_specified_percent_sum: Int?
|
||||||
|
var ingredients_with_unspecified_percent_n: Int?
|
||||||
|
var ingredients_with_unspecified_percent_sum: Int?
|
||||||
|
var known_ingredients_n: Int?
|
||||||
|
var origins: String?
|
||||||
|
var origins_lc: String?
|
||||||
|
var traces: String?
|
||||||
|
var unknown_ingredients_n: Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct IngredientAnalysis: Codable {
|
||||||
|
var palmOil: [String]?
|
||||||
|
var vegan: [String]?
|
||||||
|
var vegetarian: [String]?
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case palmOil = "en:palm-oil"
|
||||||
|
case vegan = "en:vegan-status-unknown"
|
||||||
|
case vegetarian = "en:vegetarian-status-unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Nutrition: Codable {
|
||||||
|
var no_nutrition_data: String? // When a product does not have nutrition data displayed on the packaging, the user can check the field "Nutrition facts are not specified on the product". By doing so, the no_nutrition_data field takes the value "on". This case is frequent (thousands of products).
|
||||||
|
var nutrition_data_per: ServingKind // ➔ The nutrition data on the package can be per serving or per 100g.
|
||||||
|
// This is essential to understand if <nutrient>_value and <nutrient> values in nutriments applies for a serving or for 100g.
|
||||||
|
// IMPORTANT: When writing products, this setting applies to all existing nutrients values for the product, not only the nutrient values sent in the write request. So it should not be changed unless all nutrients values are provided with values that match the nutrition_data_per field.
|
||||||
|
// Allowed: serving┃100g
|
||||||
|
var nutrition_data_prepared_per: ServingKind
|
||||||
|
|
||||||
|
public enum ServingKind: String, Codable {
|
||||||
|
case serving
|
||||||
|
case hundredGrams = "100g"
|
||||||
|
}
|
||||||
|
|
||||||
|
var nutriments: Nutriments
|
||||||
|
|
||||||
|
public struct Nutriments: Codable {
|
||||||
|
var alcohol: Float // Quantity of alcohol (per 100g or per serving) in a standard unit (g or ml)
|
||||||
|
var carbohydrates: Float
|
||||||
|
var energy: Float // It is the same as energy-kj if we have it, or computed from energy-kcal otherwise (per 100g or per serving) in kj
|
||||||
|
var energy_value: Float // energy_value will be equal to energy-kj_value if we have it or to energy-kcal_value otherwise
|
||||||
|
var energy_unit: EnergyUnit // Equal to energy-kj_unit if we have it or to energy-kcal_unit otherwise Allowed: kcal┃kj
|
||||||
|
var energyKcal: Float // energy-kcal, energy in kcal, if it is specified (per 100g or per serving) in a standard unit (g or ml)
|
||||||
|
var energyKj: Float // energy in kj, if it is specified (per 100g or per serving) in a standard unit (g or ml)
|
||||||
|
var fat: Float
|
||||||
|
var fruitsVegetablesLegumesEstimateFromIngredients: Float // An estimate, from the ingredients list of the percentage of fruits, vegetable and legumes. This is an important information for Nutri-Score (2023 version) computation.
|
||||||
|
var fruitsVegetablesNutsEstimateFromIngredients: Float
|
||||||
|
var proteins: Float
|
||||||
|
var salt: Float
|
||||||
|
var saturatedFat: Float
|
||||||
|
var sodium: Float
|
||||||
|
var sugars: Float
|
||||||
|
var erythritol: Float?
|
||||||
|
|
||||||
|
// pattern: (?<nutrient>[\w-]+)_unit]: enum
|
||||||
|
// The unit in which the nutrient for 100g or per serving is measured.
|
||||||
|
// The possible values depends on the nutrient.
|
||||||
|
// g for grams
|
||||||
|
// mg for milligrams
|
||||||
|
// μg for micrograms
|
||||||
|
// cl for centiliters
|
||||||
|
// ml for mililiters
|
||||||
|
// dv for recommended daily intakes (aka Dietary Reference Intake)
|
||||||
|
// % vol for alcohol vol per 100 ml
|
||||||
|
// 🤓 code: see the Units module, and Food:default_unit_for_nid function
|
||||||
|
// Allowed: 公斤┃公升┃kg┃кг┃l┃л┃毫克┃mg┃мг┃mcg┃µg┃oz┃fl oz┃dl┃дл┃cl┃кл┃斤┃g┃∅┃●┃kj┃克┃公克┃г┃мл┃ml┃mmol/l┃毫升┃% vol┃ph┃%┃% dv┃% vol (alcohol)┃iu┃mol/l┃mval/l┃ppm┃<EFBFBD>rh┃<EFBFBD>fh┃<EFBFBD>e┃<EFBFBD>dh┃gpg
|
||||||
|
// [pattern: (?<nutrient>[\w-]+)_100g]: number
|
||||||
|
// The standardized value of a serving of 100g (or 100ml for liquids) for the nutrient.
|
||||||
|
// This is computed from the nutrient property, the serving size (if needed), and the nutrient_unit field.
|
||||||
|
// Note: If you want to characterize products in a uniform way, this is the value you should use.
|
||||||
|
// [pattern: (?<nutrient>[\w-]+)_serving]: number
|
||||||
|
// The standardized value of a serving for this product.
|
||||||
|
// [pattern: (?<nutrient>[\w-]+)_value]: number
|
||||||
|
// The value input by the user / displayed on the product for the nutrient.
|
||||||
|
// per 100g or serving, depending on nutrition_data_per
|
||||||
|
// in the unit of corresponding _unit field.
|
||||||
|
// [pattern: (?<nutrient>[\w-]+)_prepared]: number
|
||||||
|
// The value for nutrient for prepared product.
|
||||||
|
// [pattern: (?<nutrient>[\w-]+)_prepared_unit]: string
|
||||||
|
// The unit in which the nutrient of prepared product is measured.
|
||||||
|
// [pattern: (?<nutrient>[\w-]+)_prepared_100g]: number
|
||||||
|
// The standardized value of a serving of 100g (or 100ml for liquids) for the nutrient, for prepared product.
|
||||||
|
// [pattern: (?<nutrient>[\w-]+)_prepared_serving]: number
|
||||||
|
// The standardized value of a serving for the prepared product.
|
||||||
|
// [pattern: (?<nutrient>[\w-]+)_prepared_value]: number
|
||||||
|
|
||||||
|
public enum EnergyUnit: String, Codable {
|
||||||
|
case kcal, kJ
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case alcohol, carbohydrates, energy, energy_value, energy_unit, fat, proteins, salt, sodium, sugars, erythritol
|
||||||
|
case energyKcal = "energy-kcal"
|
||||||
|
case energyKj = "energy-kj"
|
||||||
|
case fruitsVegetablesLegumesEstimateFromIngredients = "fruits-vegetables-legumes-estimate-from-ingredients"
|
||||||
|
case fruitsVegetablesNutsEstimateFromIngredients = "fruits-vegetables-nuts-estimate-from-ingredients"
|
||||||
|
case saturatedFat = "saturated-fat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OFFError: Error {
|
||||||
|
case invalidURL, invalidResponse
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import XCTest
|
||||||
|
@testable import swift_openfoodfacts_sdk
|
||||||
|
|
||||||
|
final class swift_openfoodfacts_sdkTests: XCTestCase {
|
||||||
|
func testExample() throws {
|
||||||
|
// XCTest Documentation
|
||||||
|
// https://developer.apple.com/documentation/xctest
|
||||||
|
|
||||||
|
// Defining Test Cases and Test Methods
|
||||||
|
// https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user