|
| 1 | +import Foundation |
| 2 | + |
| 3 | +struct SerpAPIResponse: Codable { |
| 4 | + var searchMetadata: SearchMetadata |
| 5 | + var organicResults: [OrganicResult] |
| 6 | + |
| 7 | + struct SearchMetadata: Codable { |
| 8 | + var id: String |
| 9 | + var status: String |
| 10 | + var jsonEndpoint: String |
| 11 | + var createdAt: String |
| 12 | + var processedAt: String |
| 13 | + var totalTimeTaken: Double |
| 14 | + } |
| 15 | + |
| 16 | + struct OrganicResult: Codable { |
| 17 | + var position: Int |
| 18 | + var title: String |
| 19 | + var link: String |
| 20 | + var snippet: String |
| 21 | + |
| 22 | + func toWebSearchResult() -> WebSearchResult.WebPage { |
| 23 | + return WebSearchResult.WebPage(urlString: link, title: title, snippet: snippet) |
| 24 | + } |
| 25 | + } |
| 26 | + |
| 27 | + func toWebSearchResult() -> WebSearchResult { |
| 28 | + return WebSearchResult(webPages: organicResults.map { $0.toWebSearchResult() }) |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +struct SerpAPISearchService: SearchService { |
| 33 | + let engine: WebSearchProvider.SerpAPIEngine |
| 34 | + let endpoint: URL = .init(string: "https://serpapi.com/search.json")! |
| 35 | + let apiKey: String |
| 36 | + |
| 37 | + init(engine: WebSearchProvider.SerpAPIEngine, apiKey: String) { |
| 38 | + self.engine = engine |
| 39 | + self.apiKey = apiKey |
| 40 | + } |
| 41 | + |
| 42 | + func search(query: String) async throws -> WebSearchResult { |
| 43 | + var request = URLRequest(url: endpoint) |
| 44 | + request.httpMethod = "GET" |
| 45 | + var urlComponents = URLComponents(url: endpoint, resolvingAgainstBaseURL: true)! |
| 46 | + urlComponents.queryItems = [ |
| 47 | + URLQueryItem(name: "q", value: query), |
| 48 | + URLQueryItem(name: "engine", value: engine.rawValue), |
| 49 | + URLQueryItem(name: "api_key", value: apiKey) |
| 50 | + ] |
| 51 | + |
| 52 | + guard let url = urlComponents.url else { |
| 53 | + throw URLError(.badURL) |
| 54 | + } |
| 55 | + |
| 56 | + request = URLRequest(url: url) |
| 57 | + request.httpMethod = "GET" |
| 58 | + |
| 59 | + let (data, response) = try await URLSession.shared.data(for: request) |
| 60 | + |
| 61 | + guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { |
| 62 | + throw URLError(.badServerResponse) |
| 63 | + } |
| 64 | + |
| 65 | + // Parse the response into WebSearchResult |
| 66 | + let decoder = JSONDecoder() |
| 67 | + |
| 68 | + do { |
| 69 | + let searchResponse = try decoder.decode(SerpAPIResponse.self, from: data) |
| 70 | + return searchResponse.toWebSearchResult() |
| 71 | + } catch { |
| 72 | + throw error |
| 73 | + } |
| 74 | + } |
| 75 | +} |
| 76 | + |
0 commit comments