// The Swift Programming Language // https://docs.swift.org/swift-book import Foundation public class OpenFoodFactsClient { let version: Int = 0 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 -> ProductResponse { 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(ProductResponse.self, from: data) } } public struct SearchQuery { public let additivesTags: String? public let allergensTags: String? public let brandsTags: String? public let categoriesTags: String? public let countriesTagsEn: String? public let embCodesTags: String? public let labelsTags: String? public let manufacturingPlacesTags: String? public let nutritionGradesTags: String? public let originsTags: String? public let packagingTagsDe: String? public let purchasePlacesTags: String? } public func search(_ productName: String, queryParams: SearchQuery? = nil) async throws -> SearchResponse { let qp = Mirror(reflecting: queryParams ?? {}) var s: String = "?product_name=\(productName)&" for case let (label?, value) in qp.children { s += label.camelCaseToSnakeCase() + "=" + (value as! String) + "&" } guard let endpoint = baseURL?.appendingPathComponent("search\(s)") 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(SearchResponse.self, from: data) } } } enum OFFError: Error { case invalidURL, invalidResponse }