😑 using search-a-licious API now...

This commit is contained in:
cdricms
2025-12-06 18:43:14 +01:00
parent 44420002c7
commit dd191b585d
7 changed files with 35 additions and 23 deletions

View File

@@ -9,7 +9,7 @@ extension KeyedDecodingContainer {
return nil 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) { if let intVal = try? decode(Int.self, forKey: key) {
return intVal return intVal
} }
@@ -19,4 +19,14 @@ extension KeyedDecodingContainer {
return nil 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
}
} }

View File

@@ -57,7 +57,7 @@ public actor OpenFoodFactsClient {
) async throws -> SearchResponseEnvelope { ) 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))
} }
} }
@@ -140,11 +140,11 @@ public struct SearchResponseEnvelope: Sendable, Decodable {
public let count: Int public let count: Int
public let page: Int public let page: Int
public let pageSize: Int public let pageSize: Int
public let products: [Product] public let hits: [Product]
public let pageCount: Int public let pageCount: Int
private enum CodingKeys: String, CodingKey { private enum CodingKeys: String, CodingKey {
case count, page, products case count, page, hits
case pageSize = "page_size" case pageSize = "page_size"
case pageCount = "page_count" case pageCount = "page_count"
} }

View File

@@ -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")!
} }
} }

View File

@@ -35,10 +35,10 @@ public struct NutriscoreData: Sendable, Codable {
isBeverage = try container.decodeIfPresent( isBeverage = try container.decodeIfPresent(
Int.self, forKey: .isBeverage) Int.self, forKey: .isBeverage)
isCheese = try container.decodeStringOrInt(forKey: .isCheese) isCheese = try container.decodeIntOrString(forKey: .isCheese)
isWater = try container.decodeStringOrInt(forKey: .isWater) isWater = try container.decodeIntOrString(forKey: .isWater)
isFat = try container.decodeStringOrInt(forKey: .isFat) isFat = try container.decodeIntOrString(forKey: .isFat)
energy = try container.decodeStringOrInt(forKey: .energy) energy = try container.decodeIntOrString(forKey: .energy)
} }

View File

@@ -147,7 +147,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)

View File

@@ -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 {

View File

@@ -35,15 +35,15 @@ final class OpenFoodFactsTests: XCTestCase {
func testSearch() async throws { func testSearch() async throws {
let response = try await client.search( let response = try await client.search(
.query("chocolate"), .query("chocolate"),
.tag(tag: .brands, value: "milka"), // .tag(tag: .brands, value: "milka"),
.pageSize(5), .pageSize(5),
.sort(.popularity), // .sort(.popularity),
) )
let results = response.products let results = response.hits
let jsonResults = try JSONEncoder().encode(results) let jsonResults = try JSONEncoder().encode(results)
_ = jsonResults try jsonResults.write(to: .init(filePath: "./jsonResults.json"))
XCTAssertFalse(results.isEmpty) XCTAssertFalse(results.isEmpty)
XCTAssertEqual(results.count, 5) XCTAssertEqual(results.count, 5)