Last active
December 27, 2017 20:31
-
-
Save vzsg/45f8da8843651f2b418bb73000d0dc99 to your computer and use it in GitHub Desktop.
Wrapping an external API for Vapor apps – service class approach
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
// A simple protocol that describes the services available on the external service | |
// FIXME: Beer is an immutable, Codable struct defined elsewhere, not important now | |
public protocol BreweryDatabase { | |
func search(_ query: String) throws -> [Beer] | |
func findBeer(id: String) throws -> Beer | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Vapor | |
// Vapor-specific implementation of the external service | |
public struct VaporBreweryDatabase: BreweryDatabase { | |
private let decoder: JSONDecoder | |
private let client: ClientFactoryProtocol | |
private let baseURL = "https://api.brewerydb.com/v2/" | |
private let defaultQuery: [String: Node] | |
public init(apiKey: String, client: ClientFactoryProtocol) { | |
self.decoder = JSONDecoder() | |
self.client = client | |
let dateFormatter = DateFormatter() | |
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" | |
decoder.dateDecodingStrategy = .formatted(dateFormatter) | |
defaultQuery = [ | |
"key": Node.string(apiKey), | |
"format": "json" | |
] | |
} | |
public func search(_ query: String) throws -> [Beer] { | |
let queryParameters = ["type": "beer", "q": query, "withBreweries": "Y"] | |
return try apiCall(path: "search", query: queryParameters) | |
} | |
public func findBeer(id: String) throws -> Beer { | |
return try apiCall(path: "beer/\(id)", query: ["type": "beer", "withBreweries": "Y"]) | |
} | |
private func apiCall<T: Codable>(path: String, query: [String: String] = [:]) throws -> T { | |
let request = Request(method: .get, uri: baseURL + path) | |
var queryObject = Node.object(defaultQuery) | |
try query.forEach { (key: String, value: String) in | |
try queryObject.set(key, value) | |
} | |
request.query = queryObject | |
let response = try client.respond(to: request) | |
let data = Data(bytes: response.body.bytes ?? []) | |
let apiResponse: ApiResponse<T> = try decoder.decode(ApiResponse<T>.self, from: data) | |
guard let responseData = apiResponse.data else { | |
throw BreweryError.serviceError(status: apiResponse.status, errorMessage: apiResponse.errorMessage) | |
} | |
return responseData | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
// All response types are wrapped in an object like this. | |
// This is an implementation detail of the brewerydb.com API! | |
struct ApiResponse<T: Codable>: Codable { | |
let message: String? | |
let data: T? | |
let errorMessage: String? | |
let status: String | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Vapor | |
let dropletStorageKey = "brewery-database" | |
// This extension will make it possible to access the preconfigured service | |
// from the Droplet anywhere. | |
// Assumption: it's a programmer error if you want to use brewery without setting it up correctly. | |
// ==> a fatal error is cleaner than mucking around with optionals. | |
public extension Droplet { | |
public var brewery: BreweryDatabase { | |
guard let brewery = self.storage[dropletStorageKey] as? BreweryDatabase else { | |
fatalError("BreweryProvider is not configured! Make sure that the Provider is properly registered!") | |
} | |
return brewery | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Vapor | |
// Use this in Config+Setup with the addProvider function. | |
public final class Provider: Vapor.Provider { | |
public static let repositoryName = "brewery-provider" | |
public func boot(_ config: Config) throws { | |
} | |
public func boot(_ droplet: Droplet) throws { | |
let apiKey: String = try droplet.config.get("brewery.key") | |
let client = droplet.client | |
let brewery = VaporBreweryDatabase(apiKey: apiKey, client: client) | |
droplet.storage[dropletStorageKey] = brewery | |
} | |
public func beforeRun(_ droplet: Droplet) throws { | |
} | |
public required init(config: Config) throws { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment