😑 using search-a-licious API now...
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user