Compare commits
8 Commits
bc1abfdebc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
871cc4c6e5 | ||
|
|
b419e8c620 | ||
|
|
dd191b585d | ||
|
|
44420002c7 | ||
|
|
bdc4bb37f5 | ||
|
|
62ad13cb8c | ||
|
|
15fbc7b218 | ||
|
|
fd493ce546 |
@@ -8,4 +8,25 @@ extension KeyedDecodingContainer {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func decodeIntOrString(forKey key: Key) throws -> Int? {
|
||||||
|
if let intVal = try? decode(Int.self, forKey: key) {
|
||||||
|
return intVal
|
||||||
|
}
|
||||||
|
if let stringVal = try? decode(String.self, forKey: key) {
|
||||||
|
return Int(stringVal)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public func decodeStringOrArray(forKey key: Key) throws -> String? {
|
||||||
|
if let arrayVal = try? decode([String].self, forKey: key) {
|
||||||
|
return arrayVal.joined(separator: ",")
|
||||||
|
}
|
||||||
|
if let stringVal = try? decode(String.self, forKey: key) {
|
||||||
|
return stringVal
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public actor OpenFoodFactsClient {
|
|||||||
/// - barcode: The product barcode.
|
/// - barcode: The product barcode.
|
||||||
/// - fields: Optional list of fields to fetch (optimizes network usage).
|
/// - fields: Optional list of fields to fetch (optimizes network usage).
|
||||||
public func product(barcode: String, fields: [ProductField]? = nil)
|
public func product(barcode: String, fields: [ProductField]? = nil)
|
||||||
async throws -> Product?
|
async throws -> ProductResponseEnvelope
|
||||||
{
|
{
|
||||||
var url = config.apiURL.appendingPathComponent("/product/\(barcode)")
|
var url = config.apiURL.appendingPathComponent("/product/\(barcode)")
|
||||||
|
|
||||||
@@ -48,16 +48,16 @@ public actor OpenFoodFactsClient {
|
|||||||
|
|
||||||
let envelope = try JSONDecoder().decode(
|
let envelope = try JSONDecoder().decode(
|
||||||
ProductResponseEnvelope.self, from: data)
|
ProductResponseEnvelope.self, from: data)
|
||||||
return envelope.product
|
return envelope
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Search for products using V2 API.
|
/// Search for products using V2 API.
|
||||||
public func search(
|
public func search(
|
||||||
_ parameters: SearchParameter..., fields: [ProductField]? = nil
|
_ parameters: SearchParameter..., fields: [ProductField]? = nil
|
||||||
) async throws -> [Product] {
|
) async throws -> SearchResponseEnvelope {
|
||||||
guard
|
guard
|
||||||
var components = URLComponents(
|
var components = URLComponents(
|
||||||
url: config.apiURL.appendingPathComponent("/search"),
|
url: config.searchURL,
|
||||||
resolvingAgainstBaseURL: true)
|
resolvingAgainstBaseURL: true)
|
||||||
else {
|
else {
|
||||||
throw OFFError.invalidURL
|
throw OFFError.invalidURL
|
||||||
@@ -76,19 +76,19 @@ public actor OpenFoodFactsClient {
|
|||||||
for param in parameters {
|
for param in parameters {
|
||||||
switch param {
|
switch param {
|
||||||
case .query(let q):
|
case .query(let q):
|
||||||
queryItems.append(URLQueryItem(name: "search_terms", value: q))
|
queryItems.append(URLQueryItem(name: "q", value: q))
|
||||||
case .tag(let tag, let value):
|
// case .tag(let tag, let value):
|
||||||
// V2 allows dynamic tags like `brands_tags=coca`
|
// // V2 allows dynamic tags like `brands_tags=coca`
|
||||||
queryItems.append(
|
// queryItems.append(
|
||||||
URLQueryItem(name: "\(tag.rawValue)_tags", value: value))
|
// URLQueryItem(name: "\(tag.rawValue)_tags", value: value))
|
||||||
case .page(let p):
|
case .page(let p):
|
||||||
queryItems.append(URLQueryItem(name: "page", value: String(p)))
|
queryItems.append(URLQueryItem(name: "page", value: String(p)))
|
||||||
case .pageSize(let s):
|
case .pageSize(let s):
|
||||||
queryItems.append(
|
queryItems.append(
|
||||||
URLQueryItem(name: "page_size", value: String(s)))
|
URLQueryItem(name: "page_size", value: String(s)))
|
||||||
case .sort(let s):
|
// case .sort(let s):
|
||||||
queryItems.append(
|
// queryItems.append(
|
||||||
URLQueryItem(name: "sort_by", value: s.rawValue))
|
// URLQueryItem(name: "sort_by", value: s.rawValue))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ public actor OpenFoodFactsClient {
|
|||||||
|
|
||||||
let envelope = try JSONDecoder().decode(
|
let envelope = try JSONDecoder().decode(
|
||||||
SearchResponseEnvelope.self, from: data)
|
SearchResponseEnvelope.self, from: data)
|
||||||
return envelope.products ?? []
|
return envelope
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private Helpers
|
// MARK: - Private Helpers
|
||||||
@@ -124,14 +124,28 @@ public enum OFFError: Error {
|
|||||||
case invalidURL, invalidResponse
|
case invalidURL, invalidResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal Envelopes for Decoding
|
public struct ProductResponseEnvelope: Sendable, Decodable {
|
||||||
struct ProductResponseEnvelope: Decodable {
|
public let code: String?
|
||||||
let code: String?
|
public let product: Product?
|
||||||
let product: Product?
|
public let status: Int?
|
||||||
let status: Int?
|
public let statusVerbose: String?
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case code, product, status
|
||||||
|
case statusVerbose = "status_verbose"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SearchResponseEnvelope: Decodable {
|
public struct SearchResponseEnvelope: Sendable, Decodable {
|
||||||
let count: Int?
|
public let count: Int
|
||||||
let products: [Product]?
|
public let page: Int
|
||||||
|
public let pageSize: Int
|
||||||
|
public let hits: [Product]
|
||||||
|
public let pageCount: Int
|
||||||
|
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case count, page, hits
|
||||||
|
case pageSize = "page_size"
|
||||||
|
case pageCount = "page_count"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ public struct OpenFoodFactsConfig: Sendable {
|
|||||||
public let baseURL: URL
|
public let baseURL: URL
|
||||||
public let userAgent: UserAgent
|
public let userAgent: UserAgent
|
||||||
public let apiURL: URL
|
public let apiURL: URL
|
||||||
|
public let searchURL: URL
|
||||||
|
|
||||||
public struct UserAgent: CustomStringConvertible, Sendable {
|
public struct UserAgent: CustomStringConvertible, Sendable {
|
||||||
public let appName: String
|
public let appName: String
|
||||||
@@ -62,5 +63,6 @@ public struct OpenFoodFactsConfig: Sendable {
|
|||||||
self.baseURL = environment.url
|
self.baseURL = environment.url
|
||||||
self.userAgent = userAgent
|
self.userAgent = userAgent
|
||||||
self.apiURL = self.baseURL.appendingPathComponent("/api/v2")
|
self.apiURL = self.baseURL.appendingPathComponent("/api/v2")
|
||||||
|
self.searchURL = URL(string: "https://search.openfoodfacts.org/search")!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public struct Nutrient: Sendable {
|
|||||||
public var per100g: Double? { values["\(name)_100g"] }
|
public var per100g: Double? { values["\(name)_100g"] }
|
||||||
public var perServing: Double? { values["\(name)_serving"] }
|
public var perServing: Double? { values["\(name)_serving"] }
|
||||||
public var value: Double? { values["\(name)_value"] ?? values[name] }
|
public var value: Double? { values["\(name)_value"] ?? values[name] }
|
||||||
public var unit: String? { units["\(name)_unit"] }
|
public var unit: String? { units["\(name)_unit"] ?? _unit?.rawValue }
|
||||||
|
|
||||||
// Computed / Legacy
|
// Computed / Legacy
|
||||||
public var valueComputed: Double? { values["\(name)_value"] }
|
public var valueComputed: Double? { values["\(name)_value"] }
|
||||||
@@ -159,6 +159,147 @@ public struct Nutrient: Sendable {
|
|||||||
else { return nil }
|
else { return nil }
|
||||||
return UnitValue(value: rawValue, unit: unitEnum)
|
return UnitValue(value: rawValue, unit: unitEnum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var _unit: Units.Unit? {
|
||||||
|
switch name {
|
||||||
|
case "energy-kj": .init(rawValue: "kJ")
|
||||||
|
case "energy-kcal": .init(rawValue: "kcal")
|
||||||
|
case "energy": .init(rawValue: "kj")
|
||||||
|
case "energy-from-fat": .init(rawValue: "kJ")
|
||||||
|
case "fat": .init(rawValue: "g")
|
||||||
|
case "saturated-fat": .init(rawValue: "g")
|
||||||
|
case "butyric-acid": .init(rawValue: "g")
|
||||||
|
case "caproic-acid": .init(rawValue: "g")
|
||||||
|
case "caprylic-acid": .init(rawValue: "g")
|
||||||
|
case "capric-acid": .init(rawValue: "g")
|
||||||
|
case "lauric-acid": .init(rawValue: "g")
|
||||||
|
case "myristic-acid": .init(rawValue: "g")
|
||||||
|
case "palmitic-acid": .init(rawValue: "g")
|
||||||
|
case "Psicose": .init(rawValue: "g")
|
||||||
|
case "stearic-acid": .init(rawValue: "g")
|
||||||
|
case "arachidic-acid": .init(rawValue: "g")
|
||||||
|
case "behenic-acid": .init(rawValue: "g")
|
||||||
|
case "lignoceric-acid": .init(rawValue: "g")
|
||||||
|
case "cerotic-acid": .init(rawValue: "g")
|
||||||
|
case "montanic-acid": .init(rawValue: "g")
|
||||||
|
case "melissic-acid": .init(rawValue: "g")
|
||||||
|
case "unsaturated-fat": .init(rawValue: "g")
|
||||||
|
case "monounsaturated-fat": .init(rawValue: "g")
|
||||||
|
case "polyunsaturated-fat": .init(rawValue: "g")
|
||||||
|
case "omega-3-fat": .init(rawValue: "mg")
|
||||||
|
case "alpha-linolenic-acid": .init(rawValue: "g")
|
||||||
|
case "eicosapentaenoic-acid": .init(rawValue: "g")
|
||||||
|
case "docosahexaenoic-acid": .init(rawValue: "g")
|
||||||
|
case "omega-6-fat": .init(rawValue: "mg")
|
||||||
|
case "linoleic-acid": .init(rawValue: "g")
|
||||||
|
case "arachidonic-acid": .init(rawValue: "g")
|
||||||
|
case "gamma-linolenic-acid": .init(rawValue: "g")
|
||||||
|
case "dihomo-gamma-linolenic-acid": .init(rawValue: "g")
|
||||||
|
case "omega-9-fat": .init(rawValue: "mg")
|
||||||
|
case "oleic-acid": .init(rawValue: "g")
|
||||||
|
case "elaidic-acid": .init(rawValue: "g")
|
||||||
|
case "gondoic-acid": .init(rawValue: "g")
|
||||||
|
case "mead-acid": .init(rawValue: "g")
|
||||||
|
case "erucic-acid": .init(rawValue: "g")
|
||||||
|
case "nervonic-acid": .init(rawValue: "g")
|
||||||
|
case "trans-fat": .init(rawValue: "g")
|
||||||
|
case "cholesterol": .init(rawValue: "mg")
|
||||||
|
case "gamma-oryzanol": .init(rawValue: "mg")
|
||||||
|
case "carbohydrates-total": .init(rawValue: "g")
|
||||||
|
case "carbohydrates": .init(rawValue: "g")
|
||||||
|
case "sugars": .init(rawValue: "g")
|
||||||
|
case "added-sugars": .init(rawValue: "g")
|
||||||
|
case "sucrose": .init(rawValue: "g")
|
||||||
|
case "glucose": .init(rawValue: "g")
|
||||||
|
case "fructose": .init(rawValue: "g")
|
||||||
|
case "oligosaccharide": .init(rawValue: "g")
|
||||||
|
case "lactose": .init(rawValue: "g")
|
||||||
|
case "galactose": .init(rawValue: "g")
|
||||||
|
case "maltose": .init(rawValue: "g")
|
||||||
|
case "maltodextrins": .init(rawValue: "g")
|
||||||
|
case "starch": .init(rawValue: "g")
|
||||||
|
case "polyols": .init(rawValue: "g")
|
||||||
|
case "Erythritol": .init(rawValue: "g")
|
||||||
|
case "Isomalt": .init(rawValue: "g")
|
||||||
|
case "Maltitol": .init(rawValue: "g")
|
||||||
|
case "Sorbitol": .init(rawValue: "g")
|
||||||
|
case "fiber": .init(rawValue: "g")
|
||||||
|
case "soluble-fiber": .init(rawValue: "g")
|
||||||
|
case "insoluble-fiber": .init(rawValue: "g")
|
||||||
|
case "proteins": .init(rawValue: "g")
|
||||||
|
case "casein": .init(rawValue: "g")
|
||||||
|
case "serum-proteins": .init(rawValue: "g")
|
||||||
|
case "nucleotides": .init(rawValue: "g")
|
||||||
|
case "salt": .init(rawValue: "g")
|
||||||
|
case "added-salt": .init(rawValue: "g")
|
||||||
|
case "sodium": .init(rawValue: "g")
|
||||||
|
case "alcohol": .init(rawValue: "% vol")
|
||||||
|
case "vitamin-a": .init(rawValue: "µg")
|
||||||
|
case "beta-carotene": .init(rawValue: "g")
|
||||||
|
case "vitamin-d": .init(rawValue: "µg")
|
||||||
|
case "vitamin-e": .init(rawValue: "mg")
|
||||||
|
case "vitamin-k": .init(rawValue: "µg")
|
||||||
|
case "vitamin-c": .init(rawValue: "mg")
|
||||||
|
case "vitamin-b1": .init(rawValue: "mg")
|
||||||
|
case "vitamin-b2": .init(rawValue: "mg")
|
||||||
|
case "vitamin-pp": .init(rawValue: "mg")
|
||||||
|
case "vitamin-b6": .init(rawValue: "mg")
|
||||||
|
case "vitamin-b9": .init(rawValue: "µg")
|
||||||
|
case "folates": .init(rawValue: "µg")
|
||||||
|
case "vitamin-b12": .init(rawValue: "µg")
|
||||||
|
case "biotin": .init(rawValue: "µg")
|
||||||
|
case "pantothenic-acid": .init(rawValue: "mg")
|
||||||
|
case "silica": .init(rawValue: "mg")
|
||||||
|
case "bicarbonate": .init(rawValue: "mg")
|
||||||
|
case "Sulphate": .init(rawValue: "mg")
|
||||||
|
case "Nitrate": .init(rawValue: "mg")
|
||||||
|
case "potassium": .init(rawValue: "mg")
|
||||||
|
case "chloride": .init(rawValue: "mg")
|
||||||
|
case "calcium": .init(rawValue: "mg")
|
||||||
|
case "phosphorus": .init(rawValue: "mg")
|
||||||
|
case "iron": .init(rawValue: "mg")
|
||||||
|
case "magnesium": .init(rawValue: "mg")
|
||||||
|
case "zinc": .init(rawValue: "mg")
|
||||||
|
case "copper": .init(rawValue: "mg")
|
||||||
|
case "manganese": .init(rawValue: "mg")
|
||||||
|
case "fluoride": .init(rawValue: "mg")
|
||||||
|
case "selenium": .init(rawValue: "µg")
|
||||||
|
case "chromium": .init(rawValue: "µg")
|
||||||
|
case "molybdenum": .init(rawValue: "µg")
|
||||||
|
case "iodine": .init(rawValue: "µg")
|
||||||
|
case "caffeine": .init(rawValue: "mg")
|
||||||
|
case "taurine": .init(rawValue: "g")
|
||||||
|
case "chlorophyl": .init(rawValue: "g")
|
||||||
|
case "choline": .init(rawValue: "g")
|
||||||
|
case "phylloquinone": .init(rawValue: "g")
|
||||||
|
case "beta-glucan": .init(rawValue: "g")
|
||||||
|
case "inositol": .init(rawValue: "g")
|
||||||
|
case "carnitine": .init(rawValue: "g")
|
||||||
|
case "melatonin": .init(rawValue: "µg")
|
||||||
|
case "methylsulfonylmethane": .init(rawValue: "mg")
|
||||||
|
case "creatine": .init(rawValue: "g")
|
||||||
|
case "l-citrulline": .init(rawValue: "mg")
|
||||||
|
case "l-glutamine": .init(rawValue: "mg")
|
||||||
|
case "bcaa": .init(rawValue: "g")
|
||||||
|
case "l-valine": .init(rawValue: "mg")
|
||||||
|
case "l-leucine": .init(rawValue: "mg")
|
||||||
|
case "l-isoleucine": .init(rawValue: "mg")
|
||||||
|
case "l-arginine": .init(rawValue: "mg")
|
||||||
|
case "l-cysteine": .init(rawValue: "mg")
|
||||||
|
case "l-Glutathione": .init(rawValue: "mg")
|
||||||
|
case "iron II sulphate monohydrate": .init(rawValue: "mg")
|
||||||
|
case "potassium iodide": .init(rawValue: "mg")
|
||||||
|
case "copper II sulphate pentahydrate": .init(rawValue: "mg")
|
||||||
|
case "manganous sulphate monohydrate": .init(rawValue: "mg")
|
||||||
|
case "zinc sulphate monohydrate": .init(rawValue: "mg")
|
||||||
|
case "sodium selenite": .init(rawValue: "mg")
|
||||||
|
case "calcium iodate anhydrous": .init(rawValue: "mg")
|
||||||
|
case "cassia gum": .init(rawValue: "mg")
|
||||||
|
case "ammonium chloride": .init(rawValue: "mg")
|
||||||
|
case "choline chloride": .init(rawValue: "mg")
|
||||||
|
default: nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|||||||
@@ -23,4 +23,23 @@ public struct NutriscoreData: Sendable, Codable {
|
|||||||
case energy
|
case energy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public init(from decoder: any Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
saturatedFatRatio = try container.decodeIfPresent(
|
||||||
|
Float.self, forKey: .saturatedFatRatio)
|
||||||
|
saturatedFatRatioPoints = try container.decodeIfPresent(
|
||||||
|
Int.self, forKey: .saturatedFatRatioPoints)
|
||||||
|
saturatedFatRatioValue = try container.decodeIfPresent(
|
||||||
|
Float.self, forKey: .saturatedFatRatioValue)
|
||||||
|
|
||||||
|
isBeverage = try container.decodeIfPresent(
|
||||||
|
Int.self, forKey: .isBeverage)
|
||||||
|
isCheese = try container.decodeIntOrString(forKey: .isCheese)
|
||||||
|
isWater = try container.decodeIntOrString(forKey: .isWater)
|
||||||
|
isFat = try container.decodeIntOrString(forKey: .isFat)
|
||||||
|
energy = try container.decodeIntOrString(forKey: .energy)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,16 @@ public struct Product: Codable, Sendable, Identifiable {
|
|||||||
public let productName: String?
|
public let productName: String?
|
||||||
public let genericName: String?
|
public let genericName: String?
|
||||||
public let brands: String?
|
public let brands: String?
|
||||||
|
|
||||||
|
public var _brands: [String]? {
|
||||||
|
guard let brands = brands else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return brands.split(separator: ",").map {
|
||||||
|
$0.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public let brandsTags: [String]?
|
public let brandsTags: [String]?
|
||||||
public let quantity: String?
|
public let quantity: String?
|
||||||
|
|
||||||
@@ -147,7 +157,7 @@ public struct Product: Codable, Sendable, Identifiable {
|
|||||||
String.self, forKey: .productName)
|
String.self, forKey: .productName)
|
||||||
genericName = try container.decodeIfPresent(
|
genericName = try container.decodeIfPresent(
|
||||||
String.self, forKey: .genericName)
|
String.self, forKey: .genericName)
|
||||||
brands = try container.decodeIfPresent(String.self, forKey: .brands)
|
brands = try container.decodeStringOrArray(forKey: .brands)
|
||||||
brandsTags = try container.decodeIfPresent(
|
brandsTags = try container.decodeIfPresent(
|
||||||
[String].self, forKey: .brandsTags)
|
[String].self, forKey: .brandsTags)
|
||||||
quantity = try container.decodeIfPresent(String.self, forKey: .quantity)
|
quantity = try container.decodeIfPresent(String.self, forKey: .quantity)
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import Foundation
|
|||||||
|
|
||||||
public enum SearchParameter: Sendable, Hashable {
|
public enum SearchParameter: Sendable, Hashable {
|
||||||
case query(String)
|
case query(String)
|
||||||
case tag(tag: SearchTagType, value: String)
|
// case tag(tag: SearchTagType, value: String)
|
||||||
case page(Int)
|
case page(Int)
|
||||||
case pageSize(Int)
|
case pageSize(Int)
|
||||||
case sort(SearchSort)
|
// case sort(SearchSort)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SearchTagType: String, Sendable {
|
public enum SearchTagType: String, Sendable {
|
||||||
|
|||||||
7239
Sources/OpenFoodFactsSDK/Schemas/nutrients.txt
Normal file
7239
Sources/OpenFoodFactsSDK/Schemas/nutrients.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,11 +18,13 @@ final class OpenFoodFactsTests: XCTestCase {
|
|||||||
|
|
||||||
func testProductFetch() async throws {
|
func testProductFetch() async throws {
|
||||||
// Fetch specific fields only
|
// Fetch specific fields only
|
||||||
let product = try await client.product(
|
let response = try await client.product(
|
||||||
barcode: "3017620422003", // Nutella
|
barcode: "3017620422003", // Nutella
|
||||||
fields: [.code, .productName, .nutriscoreGrade, .nutriments]
|
fields: [.code, .productName, .nutriscoreGrade, .nutriments]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let product = response.product
|
||||||
|
|
||||||
XCTAssertEqual(product?.code, "3017620422003")
|
XCTAssertEqual(product?.code, "3017620422003")
|
||||||
XCTAssertNotNil(product?.productName)
|
XCTAssertNotNil(product?.productName)
|
||||||
XCTAssertNotNil(product?.nutriscoreGrade)
|
XCTAssertNotNil(product?.nutriscoreGrade)
|
||||||
@@ -31,13 +33,20 @@ final class OpenFoodFactsTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testSearch() async throws {
|
func testSearch() async throws {
|
||||||
let results = try await client.search(
|
let response = try await client.search(
|
||||||
.query("chocolate"),
|
.query("Peanut butter"),
|
||||||
.tag(tag: .brands, value: "milka"),
|
// .tag(tag: .brands, value: "milka"),
|
||||||
.pageSize(5),
|
.pageSize(5),
|
||||||
.sort(.popularity),
|
// .sort(.popularity),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let results = response.hits
|
||||||
|
let a = results.compactMap { $0.nutriments }
|
||||||
|
print(
|
||||||
|
a.compactMap { b in
|
||||||
|
b.fat.per100gUnitValue
|
||||||
|
})
|
||||||
|
|
||||||
let jsonResults = try JSONEncoder().encode(results)
|
let jsonResults = try JSONEncoder().encode(results)
|
||||||
try jsonResults.write(to: .init(filePath: "./jsonResults.json"))
|
try jsonResults.write(to: .init(filePath: "./jsonResults.json"))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user