diff --git a/Sources/OpenFoodFactsSDK/Extensions/KeyDecodingContainer+Helpers.swift b/Sources/OpenFoodFactsSDK/Extensions/KeyDecodingContainer+Helpers.swift index 0dc5a67..c39f0f3 100644 --- a/Sources/OpenFoodFactsSDK/Extensions/KeyDecodingContainer+Helpers.swift +++ b/Sources/OpenFoodFactsSDK/Extensions/KeyDecodingContainer+Helpers.swift @@ -9,7 +9,7 @@ extension KeyedDecodingContainer { return nil } - public func decodeStringOrInt(forKey key: Key) throws -> Int? { + public func decodeIntOrString(forKey key: Key) throws -> Int? { if let intVal = try? decode(Int.self, forKey: key) { return intVal } @@ -19,4 +19,14 @@ extension KeyedDecodingContainer { 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 + } + } diff --git a/Sources/OpenFoodFactsSDK/OpenFoodFactsClient.swift b/Sources/OpenFoodFactsSDK/OpenFoodFactsClient.swift index 604e026..e241af2 100644 --- a/Sources/OpenFoodFactsSDK/OpenFoodFactsClient.swift +++ b/Sources/OpenFoodFactsSDK/OpenFoodFactsClient.swift @@ -57,7 +57,7 @@ public actor OpenFoodFactsClient { ) async throws -> SearchResponseEnvelope { guard var components = URLComponents( - url: config.apiURL.appendingPathComponent("/search"), + url: config.searchURL, resolvingAgainstBaseURL: true) else { throw OFFError.invalidURL @@ -76,19 +76,19 @@ public actor OpenFoodFactsClient { for param in parameters { switch param { case .query(let q): - queryItems.append(URLQueryItem(name: "search_terms", value: q)) - case .tag(let tag, let value): - // V2 allows dynamic tags like `brands_tags=coca` - queryItems.append( - URLQueryItem(name: "\(tag.rawValue)_tags", value: value)) + queryItems.append(URLQueryItem(name: "q", value: q)) + // case .tag(let tag, let value): + // // V2 allows dynamic tags like `brands_tags=coca` + // queryItems.append( + // URLQueryItem(name: "\(tag.rawValue)_tags", value: value)) case .page(let p): queryItems.append(URLQueryItem(name: "page", value: String(p))) case .pageSize(let s): queryItems.append( URLQueryItem(name: "page_size", value: String(s))) - case .sort(let s): - queryItems.append( - URLQueryItem(name: "sort_by", value: s.rawValue)) + // case .sort(let s): + // queryItems.append( + // URLQueryItem(name: "sort_by", value: s.rawValue)) } } @@ -140,11 +140,11 @@ public struct SearchResponseEnvelope: Sendable, Decodable { public let count: Int public let page: Int public let pageSize: Int - public let products: [Product] + public let hits: [Product] public let pageCount: Int private enum CodingKeys: String, CodingKey { - case count, page, products + case count, page, hits case pageSize = "page_size" case pageCount = "page_count" } diff --git a/Sources/OpenFoodFactsSDK/OpenFoodFactsConfig.swift b/Sources/OpenFoodFactsSDK/OpenFoodFactsConfig.swift index 771fed6..6d26a0c 100644 --- a/Sources/OpenFoodFactsSDK/OpenFoodFactsConfig.swift +++ b/Sources/OpenFoodFactsSDK/OpenFoodFactsConfig.swift @@ -4,6 +4,7 @@ public struct OpenFoodFactsConfig: Sendable { public let baseURL: URL public let userAgent: UserAgent public let apiURL: URL + public let searchURL: URL public struct UserAgent: CustomStringConvertible, Sendable { public let appName: String @@ -62,5 +63,6 @@ public struct OpenFoodFactsConfig: Sendable { self.baseURL = environment.url self.userAgent = userAgent self.apiURL = self.baseURL.appendingPathComponent("/api/v2") + self.searchURL = URL(string: "https://search.openfoodfacts.org/search")! } } diff --git a/Sources/OpenFoodFactsSDK/Schemas/NutriscoreData.swift b/Sources/OpenFoodFactsSDK/Schemas/NutriscoreData.swift index 174f218..7607087 100644 --- a/Sources/OpenFoodFactsSDK/Schemas/NutriscoreData.swift +++ b/Sources/OpenFoodFactsSDK/Schemas/NutriscoreData.swift @@ -35,10 +35,10 @@ public struct NutriscoreData: Sendable, Codable { isBeverage = try container.decodeIfPresent( Int.self, forKey: .isBeverage) - isCheese = try container.decodeStringOrInt(forKey: .isCheese) - isWater = try container.decodeStringOrInt(forKey: .isWater) - isFat = try container.decodeStringOrInt(forKey: .isFat) - energy = try container.decodeStringOrInt(forKey: .energy) + isCheese = try container.decodeIntOrString(forKey: .isCheese) + isWater = try container.decodeIntOrString(forKey: .isWater) + isFat = try container.decodeIntOrString(forKey: .isFat) + energy = try container.decodeIntOrString(forKey: .energy) } diff --git a/Sources/OpenFoodFactsSDK/Schemas/Product.swift b/Sources/OpenFoodFactsSDK/Schemas/Product.swift index 83ccac7..4a57871 100644 --- a/Sources/OpenFoodFactsSDK/Schemas/Product.swift +++ b/Sources/OpenFoodFactsSDK/Schemas/Product.swift @@ -147,7 +147,7 @@ public struct Product: Codable, Sendable, Identifiable { String.self, forKey: .productName) genericName = try container.decodeIfPresent( String.self, forKey: .genericName) - brands = try container.decodeIfPresent(String.self, forKey: .brands) + brands = try container.decodeStringOrArray(forKey: .brands) brandsTags = try container.decodeIfPresent( [String].self, forKey: .brandsTags) quantity = try container.decodeIfPresent(String.self, forKey: .quantity) diff --git a/Sources/OpenFoodFactsSDK/Schemas/SearchParameter.swift b/Sources/OpenFoodFactsSDK/Schemas/SearchParameter.swift index 20d41da..fcfdb5d 100644 --- a/Sources/OpenFoodFactsSDK/Schemas/SearchParameter.swift +++ b/Sources/OpenFoodFactsSDK/Schemas/SearchParameter.swift @@ -2,10 +2,10 @@ import Foundation public enum SearchParameter: Sendable, Hashable { case query(String) - case tag(tag: SearchTagType, value: String) + // case tag(tag: SearchTagType, value: String) case page(Int) case pageSize(Int) - case sort(SearchSort) + // case sort(SearchSort) } public enum SearchTagType: String, Sendable { diff --git a/Tests/OpenFoodFactsTests/OpenFoodFactsTests.swift b/Tests/OpenFoodFactsTests/OpenFoodFactsTests.swift index 6347dd2..f11e91e 100644 --- a/Tests/OpenFoodFactsTests/OpenFoodFactsTests.swift +++ b/Tests/OpenFoodFactsTests/OpenFoodFactsTests.swift @@ -35,15 +35,15 @@ final class OpenFoodFactsTests: XCTestCase { func testSearch() async throws { let response = try await client.search( .query("chocolate"), - .tag(tag: .brands, value: "milka"), + // .tag(tag: .brands, value: "milka"), .pageSize(5), - .sort(.popularity), + // .sort(.popularity), ) - let results = response.products + let results = response.hits let jsonResults = try JSONEncoder().encode(results) - _ = jsonResults + try jsonResults.write(to: .init(filePath: "./jsonResults.json")) XCTAssertFalse(results.isEmpty) XCTAssertEqual(results.count, 5)