Skip to content

Commit

Permalink
Change syntax to support multiple services
Browse files Browse the repository at this point in the history
  • Loading branch information
EthanLipnik committed Nov 11, 2021
1 parent 6a3e914 commit e7b14b0
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 118 deletions.
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,37 @@ CiboKit is used in our app, *Allike*, to find nearby restaurants using Swift’s
Initialize the framework using the Yelp API. You can create an app and get credentials for Yelp [here](https://fusion.yelp.com).

```swift
let cibo = Cibo(.yelp(.init(clientID: "INSERT_CLIENT_ID",
apiKey: "INSERT_API_KEY")))
let cibo = Cibo(.yelp(clientID: "INSERT_CLIENT_ID",
apiKey: "INSERT_API_KEY"))
```
This is a variadic function, so you can supply multiple credentials at once.

***WARNING**: Do not supply multiple credentials for the same service*

#### You can use multiple services with the same Cibo instance. Just go through the specific service you want, for example, with Yelp you would use `cibo.yelp?.search`.
##### Available services
* Yelp – `yelp?`
* Default – `default!` *(if you aren't sure the Cibo instance has any credentials, be sure to unwrap this)*

#### Then you can use this object to search.

##### Asynchronously

```swift
try await cibo.search(.food,
try await cibo.default.search(.food,
coordinates: Coordinates(latitude: 0,
longitude: 0),
radius: 40000,
limit: 50)
// or to use default values based on the API
try await cibo.search(coordinates: Coordinates(latitude: 0,
try await cibo.default.search(coordinates: Coordinates(latitude: 0,
longitude: 0))
```

##### Synchronously

```swift
cibo.search(coordinates: Coordinates(latitude: 0,
cibo.default.search(coordinates: Coordinates(latitude: 0,
longitude: 0)) { result in
switch result {
case .success(let locations):
Expand Down
94 changes: 94 additions & 0 deletions Sources/CiboKit/API.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// Api.swift
//
//
// Created by Ethan Lipnik on 11/10/21.
//

import Foundation

public struct Api {
public let authentication: Authentication

private func createSearchRequest(type: SearchType = .food,
coordinates: Coordinates,
radius: Int,
limit: Int) throws -> (session: URLSession, request: URLRequest) {
switch authentication {
case .yelp(_, let apiKey):
guard var urlComponents = URLComponents(string: "https://api.yelp.com/v3/businesses/search") else { throw URLError(.badURL) }

urlComponents.queryItems = [
URLQueryItem(name: "term", value: type.rawValue),
URLQueryItem(name: "latitude", value: "\(coordinates.latitude)"),
URLQueryItem(name: "longitude", value: "\(coordinates.longitude)"),
URLQueryItem(name: "radius", value: "\(radius)"),
URLQueryItem(name: "limit", value: "\(limit)")
]

guard let url = urlComponents.url else { throw URLError(.badURL) }
let urlRequest = URLRequest(url: url)

let sessionConfiguration = URLSessionConfiguration.default
sessionConfiguration.httpAdditionalHeaders = [
"Authorization": "Bearer \(apiKey)"
]
let session = URLSession(configuration: sessionConfiguration)

return (session, urlRequest)
}
}

// MARK: - Asynchronous
@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
public func search(_ type: SearchType = .food,
coordinates: Coordinates,
radius: Int = 8000,
limit: Int = 20) async throws -> [Location] {

let searchRequest = try createSearchRequest(type: type,
coordinates: coordinates,
radius: radius,
limit: limit)

switch authentication {
case .yelp:
let data = try await searchRequest.session.data(for: searchRequest.request).0

let yelpItems = try JSONDecoder().decode(YelpSearchResponse.self, from: data)

return yelpItems.businesses.map(Location.init)
}
}

// MARK: - Synchronous
public func search(_ type: SearchType = .food,
coordinates: Coordinates,
radius: Int = 8000,
limit: Int = 20,
completion: @escaping (Result<[Location], Error>) -> Void) {

do {
let searchRequest = try createSearchRequest(type: type,
coordinates: coordinates,
radius: radius,
limit: limit)

switch authentication {
case .yelp:
searchRequest.session.dataTask(with: searchRequest.request) { data, response, error in
do {
guard let data = data else { throw URLError(.badServerResponse) }
let yelpItems = try JSONDecoder().decode(YelpSearchResponse.self, from: data)

completion(.success(yelpItems.businesses.map(Location.init)))
} catch {
completion(.failure(error))
}
}.resume()
}
} catch {
completion(.failure(error))
}
}
}
9 changes: 1 addition & 8 deletions Sources/CiboKit/Authentication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,12 @@
import Foundation

public enum Authentication {
case yelp(Yelp)
case yelp(clientID: String, apiKey: String)

var key: String {
switch self {
case .yelp:
return "yelp"
}
}

var credentials: (clientID: String, apiKey: String) {
switch self {
case .yelp(let yelp):
return (yelp.clientID, yelp.apiKey)
}
}
}
106 changes: 19 additions & 87 deletions Sources/CiboKit/CiboKit.swift
Original file line number Diff line number Diff line change
@@ -1,104 +1,35 @@
import Foundation
#if canImport(SwiftUI)
import SwiftUI
#endif

public class Cibo {
var authentication: Authentication!

public init() { }
// MARK: - Authentications
public var authentications: [Authentication] = []

public init(_ authentication: Authentication) {
self.authentication = authentication
// MARK: - Services
public var yelp: Api?
public var `default`: Api! {
return yelp
}

@available(iOS 15.0, tvOS 15.0, watchOS 8.0, macOS 12.0, *)
public func search(_ type: SearchType = .food,
coordinates: Coordinates,
radius: Int = 8000,
limit: Int = 20) async throws -> [Location] {

let searchRequest = try createSearchRequest(type: type,
coordinates: coordinates,
radius: radius,
limit: limit)

switch authentication {
case .yelp:
let data = try await searchRequest.session.data(for: searchRequest.request).0

let yelpItems = try JSONDecoder().decode(YelpSearchResponse.self, from: data)

return yelpItems.businesses.map(Location.init)
case .none:
throw CocoaError(.coderValueNotFound)
}
}
// MARK: - Initializations
public init() { }

public func search(_ type: SearchType = .food,
coordinates: Coordinates,
radius: Int = 8000,
limit: Int = 20,
completion: @escaping (Result<[Location], Error>) -> Void) {
public init(_ authentication: Authentication...) {
self.authentications = authentication

do {
let searchRequest = try createSearchRequest(type: type,
coordinates: coordinates,
radius: radius,
limit: limit)

switch authentication {
authentication.forEach { method in
switch method {
case .yelp:
searchRequest.session.dataTask(with: searchRequest.request) { data, response, error in
do {
guard let data = data else { throw URLError(.badServerResponse) }
let yelpItems = try JSONDecoder().decode(YelpSearchResponse.self, from: data)

completion(.success(yelpItems.businesses.map(Location.init)))
} catch {
completion(.failure(error))
}
}.resume()
case .none:
throw URLError(.userAuthenticationRequired)
self.yelp = Api(authentication: method)
}
} catch {
completion(.failure(error))
}
}

private func createSearchRequest(type: SearchType = .food,
coordinates: Coordinates,
radius: Int,
limit: Int) throws -> (session: URLSession, request: URLRequest) {
switch authentication {
case .yelp(let yelp):
guard var urlComponents = URLComponents(string: "https://api.yelp.com/v3/businesses/search") else { throw URLError(.badURL) }

urlComponents.queryItems = [
URLQueryItem(name: "term", value: type.rawValue),
URLQueryItem(name: "latitude", value: "\(coordinates.latitude)"),
URLQueryItem(name: "longitude", value: "\(coordinates.longitude)"),
URLQueryItem(name: "radius", value: "\(radius)"),
URLQueryItem(name: "limit", value: "\(limit)")
]

guard let url = urlComponents.url else { throw URLError(.badURL) }
let urlRequest = URLRequest(url: url)

let sessionConfiguration = URLSessionConfiguration.default
sessionConfiguration.httpAdditionalHeaders = [
"Authorization": "Bearer \(yelp.apiKey)"
]
let session = URLSession(configuration: sessionConfiguration)

return (session, urlRequest)
case .none:
throw CocoaError(.fileReadUnknown)
}
}
}

// MARK: - SwiftUI Integration
#if canImport(SwiftUI)
import SwiftUI

@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
private struct CiboKey: EnvironmentKey {
static let defaultValue = Cibo.init()
}
Expand All @@ -110,3 +41,4 @@ extension EnvironmentValues {
set { self[CiboKey.self] = newValue }
}
}
#endif
18 changes: 0 additions & 18 deletions Sources/CiboKit/Yelp/Yelp.swift

This file was deleted.

0 comments on commit e7b14b0

Please sign in to comment.