Skip to content

Commit a09ab73

Browse files
committed
Make synchronous http requests work
1 parent df6939f commit a09ab73

File tree

6 files changed

+193
-32
lines changed

6 files changed

+193
-32
lines changed

Package.resolved

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ let package = Package(
1818
targets: ["Example"]),
1919
],
2020
dependencies: [
21-
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.2.0"))
21+
.package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "4.0.0"),
2222
],
2323
targets: [
2424
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
2525
// Targets can depend on other targets in this package, and on products in packages this package depends on.
2626
.target(
2727
name: "PodcastAPI",
28-
dependencies: ["Alamofire"]),
28+
dependencies: ["SwiftyJSON"]),
2929
.testTarget(
3030
name: "PodcastAPITests",
3131
dependencies: ["PodcastAPI"]),

Sources/Example/main.swift

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,66 @@ import Foundation
1010
import PodcastAPI
1111

1212
let apiKey = ProcessInfo.processInfo.environment["LISTEN_API_KEY", default: ""]
13+
let client = PodcastAPI.Client(apiKey: apiKey, synchronousRequest: true)
14+
//let client = PodcastAPI.Client(apiKey: apiKey)
1315

14-
let client = PodcastAPI.Client(apiKey: apiKey)
1516
var parameters: [String: String] = [:]
16-
parameters["q"] = "startup"
17-
client.search(parameters: parameters)
17+
//parameters["q"] = "startup"
18+
//parameters["sort_by_date"] = "1"
19+
//client.search(parameters: parameters) { response in
20+
// if let error = response.error {
21+
// switch (error) {
22+
// case PodcastApiError.apiConnectionError:
23+
// print("Can't connect to Listen API server")
24+
// case PodcastApiError.authenticationError:
25+
// print("wrong api key")
26+
// default:
27+
// print("unknown error")
28+
// }
29+
// } else {
30+
// if let json = response.toJson() {
31+
// print(json)
32+
// }
33+
// }
34+
//}
35+
36+
//parameters["ids"] = "3302bc71139541baa46ecb27dbf6071a,68faf62be97149c280ebcc25178aa731,37589a3e121e40debe4cef3d9638932a,9cf19c590ff0484d97b18b329fed0c6a"
37+
//parameters["rsses"] = "https://rss.art19.com/recode-decode,https://rss.art19.com/the-daily,https://www.npr.org/rss/podcast.php?id=510331,https://www.npr.org/rss/podcast.php?id=510331"
38+
//client.batchFetchPodcasts(parameters: parameters) { response in
39+
// if let error = response.error {
40+
// switch (error) {
41+
// case PodcastApiError.apiConnectionError:
42+
// print("Can't connect to Listen API server")
43+
// case PodcastApiError.authenticationError:
44+
// print("wrong api key")
45+
// default:
46+
// print("unknown error")
47+
// }
48+
// } else {
49+
// if let json = response.toJson() {
50+
// print(json["podcasts"].count)
51+
// }
52+
// }
53+
//}
54+
55+
parameters["id"] = "4d3fe717742d4963a85562e9f84d8c79"
56+
parameters["reason"] = "the podcaster wants to delete it"
57+
client.deletePodcast(parameters: parameters) { response in
58+
if let error = response.error {
59+
switch (error) {
60+
case PodcastApiError.apiConnectionError:
61+
print("Can't connect to Listen API server")
62+
case PodcastApiError.authenticationError:
63+
print("wrong api key")
64+
case PodcastApiError.notFoundError:
65+
print("not found")
66+
default:
67+
print("unknown error")
68+
}
69+
} else {
70+
if let json = response.toJson() {
71+
print(json)
72+
}
73+
}
74+
}
75+
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import Foundation
2+
import SwiftyJSON
3+
4+
public class ApiResponse {
5+
var data: Data?
6+
var response: URLResponse?
7+
var httpError: Error?
8+
public var error: PodcastApiError?
9+
10+
public init(data: Data?, response: URLResponse?, httpError: Error?, apiError: PodcastApiError? ) {
11+
self.data = data
12+
self.response = response
13+
self.httpError = httpError
14+
self.error = apiError
15+
16+
self.checkAndSetApiError()
17+
}
18+
19+
public func toJson() -> JSON? {
20+
if let data = data {
21+
do {
22+
let json = try JSON(data: data)
23+
return json
24+
} catch {
25+
return nil
26+
}
27+
}
28+
return nil
29+
}
30+
31+
private func checkAndSetApiError() {
32+
if let httpResponse = self.response as? HTTPURLResponse {
33+
switch httpResponse.statusCode {
34+
case 200..<300:
35+
self.error = nil
36+
case 400:
37+
self.error = PodcastApiError.invalidRequestError
38+
case 401:
39+
self.error = PodcastApiError.authenticationError
40+
case 404:
41+
self.error = PodcastApiError.notFoundError
42+
case 429:
43+
self.error = PodcastApiError.tooManyRequestsError
44+
case 500..<600:
45+
self.error = PodcastApiError.serverError
46+
default:
47+
self.error = nil
48+
}
49+
}
50+
}
51+
}
Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,90 @@
11
import Foundation
2-
import Alamofire
32

43
let BASE_URL_PROD = "https://listen-api.listennotes.com/api/v2"
54
let BASE_URL_TEST = "https://listen-api-test.listennotes.com/api/v2"
65

7-
//extension Request {
8-
// public func debugLog() -> Self {
9-
// #if DEBUG
10-
// debugPrint(self)
11-
// #endif
12-
// return self
13-
// }
14-
//}
156

167
public class Client {
178
private var apiKey: String
189
private var baseUrl: String = BASE_URL_PROD
1910
private var userAgent: String = "podcast-api-swift"
2011
private var responseTimeout: Int = 30000
12+
private var synchronousRequest: Bool = false
2113

22-
public init(apiKey: String) {
14+
public convenience init(apiKey: String) {
15+
self.init(apiKey: apiKey, synchronousRequest: false)
16+
}
17+
18+
public init(apiKey: String, synchronousRequest: Bool) {
2319
self.apiKey = apiKey
2420

2521
if apiKey.trimmingCharacters(in: .whitespacesAndNewlines).count == 0 {
2622
self.baseUrl = BASE_URL_TEST
2723
}
24+
25+
self.synchronousRequest = synchronousRequest
2826
}
2927

3028
public func setUserAgent(userAgent: String) {
3129
self.userAgent = userAgent;
3230
}
3331

34-
public func search(parameters: [String: String]) {
35-
let url = "\(self.baseUrl)/search"
36-
let result = self.query(address: url)
37-
print(result)
32+
public func search(parameters: [String: String], completion: @escaping (ApiResponse) -> ()) {
33+
self.sendHttpRequest(path: "search", method: "GET", parameters: parameters, completion: completion)
34+
35+
}
36+
37+
public func batchFetchPodcasts(parameters: [String: String], completion: @escaping (ApiResponse) -> ()) {
38+
self.sendHttpRequest(path: "podcasts", method: "POST", parameters: parameters, completion: completion)
3839
}
3940

40-
func query(address: String) -> String {
41-
let url = URL(string: address)
42-
let semaphore = DispatchSemaphore(value: 0)
41+
public func deletePodcast(parameters: [String: String], completion: @escaping (ApiResponse) -> ()) {
42+
self.sendHttpRequest(path: "deletePodcast", method: "DELETE", parameters: parameters, completion: completion)
43+
}
44+
45+
func sendHttpRequest(path: String, method: String, parameters: [String: String], completion: ((ApiResponse) -> ())?) {
46+
let urlString = "\(self.baseUrl)/\(path)"
47+
48+
var request: URLRequest
4349

44-
var result: String = ""
50+
if method == "POST" {
51+
request = URLRequest(url: URL(string: urlString)!)
52+
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
53+
var data = [String]()
54+
for(key, value) in parameters {
55+
data.append(key + "=\(value)")
56+
}
57+
let postData = data.map { String($0) }.joined(separator: "&")
58+
request.httpBody = postData.data(using: .utf8)
59+
} else {
60+
var components = URLComponents(string: urlString)!
61+
components.queryItems = parameters.map { (key, value) in
62+
URLQueryItem(name: key, value: value)
63+
}
64+
components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
65+
request = URLRequest(url: components.url!)
66+
}
67+
request.httpMethod = method
68+
request.setValue(self.apiKey, forHTTPHeaderField: "X-ListenAPI-Key")
4569

46-
let task = URLSession.shared.dataTask(with: url!) {(data, response, error) in
47-
result = String(data: data!, encoding: String.Encoding.utf8)!
48-
semaphore.signal()
70+
let sema: DispatchSemaphore? = self.synchronousRequest ? DispatchSemaphore(value: 0) : nil;
71+
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
72+
if let error = error {
73+
completion?(ApiResponse(data: data, response: response, httpError: error, apiError: PodcastApiError.apiConnectionError))
74+
if let sema = sema {
75+
sema.signal()
76+
}
77+
return
78+
}
79+
completion?(ApiResponse(data: data, response: response, httpError: error, apiError: nil))
80+
if let sema = sema {
81+
sema.signal()
82+
}
4983
}
5084

5185
task.resume()
52-
semaphore.wait()
53-
return result
86+
if let sema = sema {
87+
sema.wait()
88+
}
5489
}
5590
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by Wenbin Fang on 5/13/21.
6+
//
7+
8+
import Foundation
9+
10+
public enum PodcastApiError: Error {
11+
case authenticationError
12+
case apiConnectionError
13+
case tooManyRequestsError
14+
case invalidRequestError
15+
case notFoundError
16+
case serverError
17+
}

0 commit comments

Comments
 (0)