diff --git a/.gitignore b/.gitignore
index 2b5f284..d40b76a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,29 @@ DerivedData/
!default.perspectivev3
xcuserdata/
+## Other
+*.moved-aside
+*.xcuserstate
+*.xcscmblueprint
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## Build generated
+build/
+DerivedData/
+
+## Various settings
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+
## Other
*.moved-aside
*.xccheckout
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Json.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Json.xcscheme
deleted file mode 100644
index 2765ce0..0000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/Json.xcscheme
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Request-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Request-Package.xcscheme
deleted file mode 100644
index 3aaeb57..0000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/Request-Package.xcscheme
+++ /dev/null
@@ -1,106 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Request.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Request.xcscheme
deleted file mode 100644
index f3a3fe3..0000000
--- a/.swiftpm/xcode/xcshareddata/xcschemes/Request.xcscheme
+++ /dev/null
@@ -1,67 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.travis.yml b/.travis.yml
index c462ed9..51c1533 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,6 @@ language:
- swift
script:
- swift package generate-xcodeproj
- - xcodebuild clean test -project Request.xcodeproj -scheme "Request-Package" -destination "OS=13.0,name=iPhone XS" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO -quiet -enableCodeCoverage YES -derivedDataPath .build/derivedData
+ - xcodebuild clean test -project Request.xcodeproj -scheme "Request-Package" -destination "OS=13.0,name=iPhone 11" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO -quiet -enableCodeCoverage YES -derivedDataPath .build/derivedData
after_success:
- bash <(curl -s https://codecov.io/bash) -D .build/derivedData
diff --git a/README.md b/README.md
index bc451dd..103e3b9 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,14 @@ Body(["key": "value"])
Body("myBodyContent")
Body(myJson)
```
+- `Timeout`
+
+Sets the timeout for a request or resource:
+```swift
+Timeout(60)
+Timeout(60, for: .request)
+Timeout(30, for: .resource)
+```
- `RequestParam`
Add a param directly
@@ -196,6 +204,17 @@ RequestChain {
}
```
+## Repeated Calls
+`.update` is used to run additional calls after the initial one. You can pass it either a number or a custom `Publisher`. You can also chain together multiple `.update`s. The two `.update`s in the following example are equivalent, so the end result is that the `Request` will be called once immediately and twice every 10 seconds thereafter.
+```swift
+Request {
+ Url("https://jsonplaceholder.typicode.com/todo")
+}
+.update(every: 10)
+.update(publisher: Timer.publish(every: 10, on: .main, in: .common).autoconnect())
+.call()
+```
+
## Json
`swift-request` includes support for `Json`.
`Json` is used as the response type in the `onJson` callback on a `Request` object.
diff --git a/Sources/Json/Json.swift b/Sources/Json/Json.swift
index 14a8302..983b2cc 100644
--- a/Sources/Json/Json.swift
+++ b/Sources/Json/Json.swift
@@ -96,6 +96,7 @@ public struct Json {
newSubs.remove(at: 0)
var json = self[subs.first!]
json[newSubs] = newValue
+ self[subs.first!] = json
}
}
}
diff --git a/Sources/Json/Literals.swift b/Sources/Json/Literals.swift
index b1ddc5f..8a2e95b 100644
--- a/Sources/Json/Literals.swift
+++ b/Sources/Json/Literals.swift
@@ -45,6 +45,8 @@ extension Json: ExpressibleByArrayLiteral {
extension Json: ExpressibleByDictionaryLiteral {
public init(dictionaryLiteral elements: (String, Any)...) {
- self.init { elements }
+ jsonData = Dictionary(elements, uniquingKeysWith: { (value1, value2) -> Any in
+ return value1
+ })
}
}
diff --git a/Sources/Request/Helpers/Auth.swift b/Sources/Request/Helpers/Auth.swift
index 7e942a9..dd7ae29 100644
--- a/Sources/Request/Helpers/Auth.swift
+++ b/Sources/Request/Helpers/Auth.swift
@@ -44,7 +44,7 @@ public struct Auth {
extension Auth {
/// Authenticates using `username` and `password` directly
public static func basic(username: String, password: String) -> Auth {
- return Auth(type: .basic, key: "\(username):\(password)")
+ return Auth(type: .basic, key: Data("\(username):\(password)".utf8).base64EncodedString())
}
/// Authenticates using a `token`
diff --git a/Sources/Request/Request/Request.swift b/Sources/Request/Request/Request.swift
index 0b0b4b9..9d08146 100644
--- a/Sources/Request/Request/Request.swift
+++ b/Sources/Request/Request/Request.swift
@@ -30,6 +30,7 @@ import Combine
/// - Precondition: The `Request` body must contain **exactly one** `Url`
public typealias Request = AnyRequest
+// TODO: Fix EXC_BAD_ACCESS instead of workaround with `struct`
/// Tha base class of `Request` to be used with a `Codable` `ResponseType` when using the `onObject` callback
///
/// *Example*:
@@ -40,9 +41,9 @@ public typealias Request = AnyRequest
/// .onObject { myCodableStructs in
/// ...
/// }
-public class AnyRequest: ObservableObject, Identifiable where ResponseType: Decodable {
- public var willChange = PassthroughSubject()
-
+public struct AnyRequest/*: ObservableObject, Identifiable*/ where ResponseType: Decodable {
+ public let combineIdentifier = CombineIdentifier()
+
private var params: CombinedParams
private var onData: ((Data) -> Void)?
@@ -50,15 +51,16 @@ public class AnyRequest: ObservableObject, Identifiable where Resp
private var onJson: ((Json) -> Void)?
private var onObject: ((ResponseType) -> Void)?
private var onError: ((RequestError) -> Void)?
+ private var updatePublisher: AnyPublisher?
- @Published public var response: Response = Response()
+ /*@Published*/ public var response: Response = Response()
public init(@RequestBuilder builder: () -> RequestParam) {
let params = builder()
if !(params is CombinedParams) {
self.params = CombinedParams(children: [params])
} else {
- self.params = builder() as! CombinedParams
+ self.params = params as! CombinedParams
}
self.response = Response()
}
@@ -68,38 +70,56 @@ public class AnyRequest: ObservableObject, Identifiable where Resp
self.response = Response()
}
+ internal init(params: CombinedParams,
+ onData: ((Data) -> Void)?,
+ onString: ((String) -> Void)?,
+ onJson: ((Json) -> Void)?,
+ onObject: ((ResponseType) -> Void)?,
+ onError: ((RequestError) -> Void)?,
+ updatePublisher: AnyPublisher?) {
+ self.params = params
+ self.onData = onData
+ self.onString = onString
+ self.onJson = onJson
+ self.onObject = onObject
+ self.onError = onError
+ self.updatePublisher = updatePublisher
+ }
+
/// Sets the `onData` callback to be run whenever `Data` is retrieved
public func onData(_ callback: @escaping (Data) -> Void) -> Self {
- self.onData = callback
- return self
+ Self.init(params: params, onData: callback, onString: onString, onJson: onJson, onObject: onObject, onError: onError, updatePublisher: updatePublisher)
}
/// Sets the `onString` callback to be run whenever a `String` is retrieved
public func onString(_ callback: @escaping (String) -> Void) -> Self {
- self.onString = callback
- return self
+ Self.init(params: params, onData: onData, onString: callback, onJson: onJson, onObject: onObject, onError: onError, updatePublisher: updatePublisher)
}
/// Sets the `onData` callback to be run whenever `Json` is retrieved
public func onJson(_ callback: @escaping (Json) -> Void) -> Self {
- self.onJson = callback
- return self
+ Self.init(params: params, onData: onData, onString: onString, onJson: callback, onObject: onObject, onError: onError, updatePublisher: updatePublisher)
}
/// Sets the `onObject` callback to be run whenever `Data` is retrieved
public func onObject(_ callback: @escaping (ResponseType) -> Void) -> Self {
- self.onObject = callback
- return self
+ Self.init(params: params, onData: onData, onString: onString, onJson: onJson, onObject: callback, onError: onError, updatePublisher: updatePublisher)
}
/// Handle any `RequestError`s thrown by the `Request`
public func onError(_ callback: @escaping (RequestError) -> Void) -> Self {
- self.onError = callback
- return self
+ Self.init(params: params, onData: onData, onString: onString, onJson: onJson, onObject: onObject, onError: callback, updatePublisher: updatePublisher)
}
/// Performs the `Request`, and calls the `onData`, `onString`, `onJson`, and `onError` callbacks when appropriate.
public func call() {
+ performRequest()
+ if let updatePublisher = self.updatePublisher {
+ updatePublisher.subscribe(self)
+ }
+ }
+
+ private func performRequest() {
// Url
guard var components = URLComponents(string: params.children!.filter({ $0.type == .url })[0].value as! String) else {
fatalError("Missing Url in Request body")
@@ -143,40 +163,95 @@ public class AnyRequest: ObservableObject, Identifiable where Resp
request.httpBody = body[0].value as? Data
}
+ // Configuration
+ let configuration = URLSessionConfiguration.default
+ let timeouts = params.children!.filter { $0.type == .timeout }
+ if timeouts.count > 0 {
+ for timeout in timeouts {
+ guard let (source, interval) = timeout.value as? (Timeout.Source, TimeInterval) else {
+ fatalError("Invalid Timeout \(timeout)")
+ }
+ if source.contains(.request) {
+ configuration.timeoutIntervalForRequest = interval
+ }
+ if source.contains(.resource) {
+ configuration.timeoutIntervalForResource = interval
+ }
+ }
+ }
+
+
// PERFORM REQUEST
- URLSession.shared.dataTask(with: request) { data, res, err in
- if res != nil {
- let statusCode = (res as! HTTPURLResponse).statusCode
+ URLSession(configuration: configuration).dataTask(with: request) { data, res, err in
+ if let res = res as? HTTPURLResponse {
+ let statusCode = res.statusCode
if statusCode < 200 || statusCode >= 300 {
- if self.onError != nil {
- self.onError!(RequestError(statusCode: statusCode, error: data))
+ if let onError = self.onError {
+ onError(RequestError(statusCode: statusCode, error: data))
return
}
}
+ } else if let err = err, let onError = self.onError {
+ onError(RequestError(statusCode: -1, error: err.localizedDescription.data(using: .utf8)))
}
- if data != nil {
- if self.onData != nil {
- self.onData!(data!)
+ if let data = data {
+ if let onData = self.onData {
+ onData(data)
}
- if self.onString != nil {
- if let string = String(data: data!, encoding: .utf8) {
- self.onString!(string)
+ if let onString = self.onString {
+ if let string = String(data: data, encoding: .utf8) {
+ onString(string)
}
}
- if self.onJson != nil {
- if let string = String(data: data!, encoding: .utf8) {
+ if let onJson = self.onJson {
+ if let string = String(data: data, encoding: .utf8) {
if let json = try? Json(string) {
- self.onJson!(json)
+ onJson(json)
}
}
}
- if self.onObject != nil {
- if let decoded = try? JSONDecoder().decode(ResponseType.self, from: data!) {
- self.onObject!(decoded)
+ if let onObject = self.onObject {
+ if let decoded = try? JSONDecoder().decode(ResponseType.self, from: data) {
+ onObject(decoded)
}
}
self.response.data = data
}
}.resume()
}
+
+ /// Sets the `Request` to be performed additional times after the initial `call`
+ public func update(publisher: T) -> Self {
+ var newPublisher = publisher
+ .map {_ in Void()}
+ .assertNoFailure()
+ .eraseToAnyPublisher()
+ if let updatePublisher = self.updatePublisher {
+ newPublisher = newPublisher.merge(with: updatePublisher).eraseToAnyPublisher()
+ }
+ return Self.init(params: params, onData: onData, onString: onString, onJson: onJson, onObject: onObject, onError: onError, updatePublisher: newPublisher)
+ }
+
+ /// Sets the `Request` to be repeated periodically after the initial `call`
+ public func update(every seconds: TimeInterval) -> Self {
+ self.update(publisher: Timer.publish(every: seconds, on: .main, in: .common).autoconnect())
+ }
+}
+
+extension AnyRequest : Subscriber {
+ public typealias Input = Void
+ public typealias Failure = Never
+
+ public func receive(subscription: Subscription) {
+ subscription.request(.unlimited)
+ }
+
+ public func receive(_ input: Void) -> Subscribers.Demand {
+ self.performRequest()
+ return .none
+ }
+
+ public func receive(completion: Subscribers.Completion) {
+ return
+ }
}
diff --git a/Sources/Request/Request/RequestBuilder.swift b/Sources/Request/Request/RequestBuilder.swift
index b91d26e..e258386 100644
--- a/Sources/Request/Request/RequestBuilder.swift
+++ b/Sources/Request/Request/RequestBuilder.swift
@@ -10,14 +10,27 @@ import Foundation
@_functionBuilder
public struct RequestBuilder {
public static func buildBlock(_ params: RequestParam...) -> RequestParam {
+
+ let resultParams = params.filter { $0.children == nil } + params.compactMap { $0.children }.joined()
// Multiple Urls
- if params.filter({ $0.type == .url }).count > 1 {
+ if resultParams.filter({ $0.type == .url }).count > 1 {
fatalError("You cannot specify more than 1 `Url`")
}
// Missing Url
- if params.filter({ $0.type == .url }).count < 1 {
+ if resultParams.filter({ $0.type == .url }).count < 1 {
fatalError("You must have a `Url`")
}
- return CombinedParams(children: params)
+ return CombinedParams(children: resultParams)
+ }
+
+ public static func buildBlock(_ param: RequestParam) -> RequestParam {
+ return param
+ }
+
+ public static func buildIf(_ param: RequestParam?) -> RequestParam {
+ if let param = param {
+ return param
+ }
+ return CombinedParams(children: [])
}
}
diff --git a/Sources/Request/Request/RequestParams/Body.swift b/Sources/Request/Request/RequestParams/Body.swift
index f269fe2..4b15cb2 100644
--- a/Sources/Request/Request/RequestParams/Body.swift
+++ b/Sources/Request/Request/RequestParams/Body.swift
@@ -26,9 +26,7 @@ import Foundation
/// }
public struct Body: RequestParam {
public var type: RequestParamType = .body
- public var key: String?
public var value: Any?
- public var children: [RequestParam]?
/// Creates the `Body` from key-value pairs
public init(_ dict: [String:Any]) {
diff --git a/Sources/Request/Request/RequestParams/Header.swift b/Sources/Request/Request/RequestParams/Header.swift
index 0cd7ad8..cb59051 100644
--- a/Sources/Request/Request/RequestParams/Header.swift
+++ b/Sources/Request/Request/RequestParams/Header.swift
@@ -11,7 +11,6 @@ public struct HeaderParam: RequestParam {
public var type: RequestParamType = .header
public var key: String?
public var value: Any?
- public var children: [RequestParam]? = nil
public init(key: String, value: String) {
self.key = key
@@ -23,7 +22,7 @@ public struct HeaderParam: RequestParam {
public struct Header {
/// Sets the value for any header
public static func `Any`(key: String, value: String) -> HeaderParam {
- return HeaderParam(key: key, value: value)
+ HeaderParam(key: key, value: value)
}
}
diff --git a/Sources/Request/Request/RequestParams/Method.swift b/Sources/Request/Request/RequestParams/Method.swift
index f6bc991..97eec73 100644
--- a/Sources/Request/Request/RequestParams/Method.swift
+++ b/Sources/Request/Request/RequestParams/Method.swift
@@ -23,9 +23,7 @@ public enum MethodType: String {
/// Sets the method of the `Request`
public struct Method: RequestParam {
public var type: RequestParamType = .method
- public var key: String?
public var value: Any?
- public var children: [RequestParam]? = nil
public init(_ type: MethodType) {
self.value = type
diff --git a/Sources/Request/Request/RequestParams/QueryParam.swift b/Sources/Request/Request/RequestParams/QueryParam.swift
index d7a893e..c0b0b2f 100644
--- a/Sources/Request/Request/RequestParams/QueryParam.swift
+++ b/Sources/Request/Request/RequestParams/QueryParam.swift
@@ -12,7 +12,6 @@ public struct QueryParam: RequestParam {
public var type: RequestParamType = .query
public var key: String?
public var value: Any?
- public var children: [RequestParam]?
public init(_ key: String, value: String) {
self.key = key
@@ -25,7 +24,6 @@ public struct QueryParam: RequestParam {
/// `[key:value, key2:value2]` becomes `?key=value&key2=value2`
public struct Query: RequestParam {
public var type: RequestParamType = .query
- public var key: String?
public var value: Any?
public var children: [RequestParam]? = []
diff --git a/Sources/Request/Request/RequestParams/RequestParam.swift b/Sources/Request/Request/RequestParams/RequestParam.swift
index 1d3d3c9..9c9efb8 100644
--- a/Sources/Request/Request/RequestParams/RequestParam.swift
+++ b/Sources/Request/Request/RequestParams/RequestParam.swift
@@ -15,6 +15,7 @@ public enum RequestParamType {
case body
case header
case combined
+ case timeout
}
/// A parameter used to build the `Request`
@@ -25,6 +26,12 @@ public protocol RequestParam {
var children: [RequestParam]? { get }
}
+public extension RequestParam {
+ var key: String? { nil }
+ var value: Any? { nil }
+ var children: [RequestParam]? { nil }
+}
+
/// A way to create a custom `RequestParam`
/// - Important: You will most likely want to use one of the builtin `RequestParam`s, such as: `Url`, `Method`, `Header`, `Query`, or `Body`.
public struct AnyParam: RequestParam {
diff --git a/Sources/Request/Request/RequestParams/Timeout.swift b/Sources/Request/Request/RequestParams/Timeout.swift
new file mode 100644
index 0000000..d2cc496
--- /dev/null
+++ b/Sources/Request/Request/RequestParams/Timeout.swift
@@ -0,0 +1,31 @@
+//
+// File.swift
+//
+//
+// Created by Carson Katri on 6/23/20.
+//
+
+import Foundation
+
+/// Sets the `timeoutIntervalForRequest` and/or `timeoutIntervalForResource` of the `Request`
+public struct Timeout: RequestParam {
+ public var type: RequestParamType = .timeout
+ public var value: Any? = nil
+
+ public init(_ timeout: TimeInterval, for source: Source = .all) {
+ self.value = (source, timeout)
+ }
+
+ public struct Source: OptionSet {
+ public let rawValue: Int
+
+ public init(rawValue: Int) {
+ self.rawValue = rawValue
+ }
+
+ public static let request = Self(rawValue: 1 << 0)
+ public static let resource = Self(rawValue: 1 << 1)
+
+ public static let all: Self = [.request, .resource]
+ }
+}
diff --git a/Sources/Request/SwiftUI/RequestView/RequestView.swift b/Sources/Request/SwiftUI/RequestView/RequestView.swift
index 7729d90..bf9876f 100644
--- a/Sources/Request/SwiftUI/RequestView/RequestView.swift
+++ b/Sources/Request/SwiftUI/RequestView/RequestView.swift
@@ -37,7 +37,7 @@ public struct RequestView : View where Content: View, Plac
}
public var body: some View {
- if data == nil || oldReq == nil || oldReq?.id != request.id {
+ if data == nil/* || oldReq == nil || oldReq?.id != request.id*/ {
let req = self.request.onData { data in
self.oldReq = self.request
self.data = data
diff --git a/Tests/RequestTests/JsonTests.swift b/Tests/RequestTests/JsonTests.swift
index 3306aa0..54b2131 100644
--- a/Tests/RequestTests/JsonTests.swift
+++ b/Tests/RequestTests/JsonTests.swift
@@ -54,17 +54,26 @@ class JsonTests: XCTestCase {
}
func testSubscripts() {
- guard let json = try? Json(complexJson) else {
+ guard var json = try? Json(complexJson) else {
XCTAssert(false)
return
}
let subscripts: [JsonSubscript] = ["projects", 0, "codeCov"]
- let _: [Any] = [
- json.firstName,
- json.projects[0],
- json["projects", 0, "stars"],
- json[subscripts]
- ]
+
+ json[] = 0
+
+ XCTAssertEqual(json["isEmployed"].bool, true)
+ json["isEmployed"] = false
+ XCTAssertEqual(json["isEmployed"].bool, false)
+
+ XCTAssertEqual(json["projects", 0, "stars"].int, 91)
+ json["projects", 0, "stars"] = 10
+ XCTAssertEqual(json["projects", 0, "stars"].int, 10)
+
+ XCTAssertEqual(json[subscripts].double, 0.98)
+ json[subscripts] = 0.49
+ XCTAssertEqual(json[subscripts].double, 0.49)
+
}
func testAccessors() {
@@ -84,6 +93,7 @@ class JsonTests: XCTestCase {
json.projects[1].codeCov.double,
json.projects[1].codeCov.doubleOptional as Any,
json.projects[1].passing.boolOptional as Any,
+ json.projects[1].passing.bool as Any,
json.value,
]
XCTAssert(true)
@@ -95,8 +105,14 @@ class JsonTests: XCTestCase {
return
}
json.firstName = "Cameron"
+ json.projects[0].stars = 100
json.likes = ["Hello", "World"]
+ json.projects[1] = ["name" : "hello", "description" : "world"]
XCTAssertEqual(json["firstName"].string, "Cameron")
+ XCTAssertEqual(json["projects"][0].stars.int, 100)
+ XCTAssertEqual(json["likes"][0].string, "Hello")
+ XCTAssertEqual(json["likes"][1].string, "World")
+ XCTAssertEqual(json["projects"][1]["name"].string, "hello")
}
func testStringify() {
@@ -119,6 +135,7 @@ class JsonTests: XCTestCase {
("measureParse", testMeasureParse),
("subscripts", testSubscripts),
("accessors", testAccessors),
+ ("set", testSet),
("stringify", testStringify),
("measureStringify", testMeasureStringify),
]
diff --git a/Tests/RequestTests/RequestTests.swift b/Tests/RequestTests/RequestTests.swift
index b044b86..b96738c 100644
--- a/Tests/RequestTests/RequestTests.swift
+++ b/Tests/RequestTests/RequestTests.swift
@@ -1,6 +1,4 @@
import XCTest
-import SwiftUI
-import Combine
import Json
@testable import Request
@@ -31,16 +29,31 @@ final class RequestTests: XCTestCase {
Url("https://jsonplaceholder.typicode.com/todos")
})
}
-
+
+ func testRequestWithCondition() {
+ let condition = true
+ performRequest(Request {
+ if condition {
+ Url(protocol: .https, url: "jsonplaceholder.typicode.com/todos")
+ }
+ if !condition {
+ Url("invalidurl")
+ }
+ })
+ }
+
func testPost() {
+ // Workaround for 'ambiguous reference' error.
+ let method = Method(.post)
performRequest(Request {
Url("https://jsonplaceholder.typicode.com/todos")
- Method(.post)
+ method
Body([
"title": "My Post",
"completed": true,
"userId": 3,
])
+ Body("{\"userId\" : 3,\"title\" : \"My Post\",\"completed\" : true}")
})
}
@@ -48,10 +61,11 @@ final class RequestTests: XCTestCase {
performRequest(Request {
Url("https://jsonplaceholder.typicode.com/todos")
Method(.get)
- Query(["userId":"1"])
+ Query(["userId":"1", "password": "2"])
+ Query([QueryParam("key", value: "value"), QueryParam("key2", value: "value2")])
})
}
-
+
func testComplexRequest() {
performRequest(Request {
Url("https://jsonplaceholder.typicode.com/todos")
@@ -60,6 +74,26 @@ final class RequestTests: XCTestCase {
Header.CacheControl(.noCache)
})
}
+
+ func testHeaders() {
+ performRequest(Request {
+ Url("https://jsonplaceholder.typicode.com/todos")
+ Header.Any(key: "Custom-Header", value: "value123")
+ Header.Accept(.json)
+ Header.Accept("text/html")
+ Header.Authorization(.basic(username: "carsonkatri", password: "password123"))
+ Header.Authorization(.bearer("authorizationToken"))
+ Header.CacheControl(.maxAge(1000))
+ Header.CacheControl(.maxStale(1000))
+ Header.CacheControl(.minFresh(1000))
+ Header.ContentLength(0)
+ Header.ContentType(.xml)
+ Header.Host("jsonplaceholder.typicode.com")
+ Header.Origin("www.example.com")
+ Header.Referer("redirectfrom.example.com")
+ Header.UserAgent(.firefoxMac)
+ })
+ }
func testObject() {
struct Todo: Decodable {
@@ -73,7 +107,7 @@ final class RequestTests: XCTestCase {
var response: [Todo]? = nil
var error: Data? = nil
- _ = AnyRequest<[Todo]> {
+ AnyRequest<[Todo]> {
Url("https://jsonplaceholder.typicode.com/todos")
}
.onError { err in
@@ -92,10 +126,65 @@ final class RequestTests: XCTestCase {
XCTAssert(true)
}
}
-
+
+ func testString() {
+ let expectation = self.expectation(description: #function)
+ var response: String? = nil
+ var error: Data? = nil
+
+ Request {
+ Url("https://jsonplaceholder.typicode.com/todos")
+ }
+ .onError { err in
+ error = err.error
+ expectation.fulfill()
+ }
+ .onString { result in
+ response = result
+ expectation.fulfill()
+ }
+ .call()
+ waitForExpectations(timeout: 10000)
+ if error != nil {
+ XCTAssert(false)
+ } else if response != nil {
+ XCTAssert(true)
+ }
+ }
+
+ func testJson() {
+ let expectation = self.expectation(description: #function)
+ var response: Json? = nil
+ var error: Data? = nil
+
+ Request {
+ Url("https://jsonplaceholder.typicode.com/todos")
+ }
+ .onError { err in
+ error = err.error
+ expectation.fulfill()
+ }
+ .onJson { result in
+ response = result
+ expectation.fulfill()
+ }
+ .call()
+ waitForExpectations(timeout: 10000)
+ if error != nil {
+ XCTAssert(false)
+ } else if let response = response, response.count > 0 {
+ XCTAssert(true)
+ }
+ }
+
func testRequestGroup() {
let expectation = self.expectation(description: #function)
var loaded: Int = 0
+ var datas: Int = 0
+ var strings: Int = 0
+ var jsons: Int = 0
+ var errors: Int = 0
+ let numberOfResponses = 10
RequestGroup {
Request {
Url("https://jsonplaceholder.typicode.com/todos")
@@ -106,18 +195,51 @@ final class RequestTests: XCTestCase {
Request {
Url("https://jsonplaceholder.typicode.com/todos/1")
}
+ Request {
+ Url("invalidURL")
+ }
}
.onData { (index, data) in
if data != nil {
loaded += 1
+ datas += 1
+ }
+ if loaded >= numberOfResponses {
+ expectation.fulfill()
+ }
+ }
+ .onString { (index, string) in
+ if string != nil {
+ loaded += 1
+ strings += 1
+ }
+ if loaded >= numberOfResponses {
+ expectation.fulfill()
+ }
+ }
+ .onJson { (index, json) in
+ if json != nil {
+ loaded += 1
+ jsons += 1
}
- if loaded >= 3 {
+ if loaded >= numberOfResponses {
expectation.fulfill()
}
}
+ .onError({ (index, error) in
+ loaded += 1
+ errors += 1
+ if loaded >= numberOfResponses {
+ expectation.fulfill()
+ }
+ })
.call()
waitForExpectations(timeout: 10000)
- XCTAssertEqual(loaded, 3)
+ XCTAssertEqual(loaded, numberOfResponses)
+ XCTAssertEqual(datas, 3)
+ XCTAssertEqual(strings, 3)
+ XCTAssertEqual(jsons, 3)
+ XCTAssertEqual(errors, 1)
}
func testRequestChain() {
@@ -126,6 +248,7 @@ final class RequestTests: XCTestCase {
RequestChain {
Request.chained { (data, err) in
Url("https://jsonplaceholder.typicode.com/todos")
+ Method(.get)
}
Request.chained { (data, err) in
let json = try? Json(data[0]!)
@@ -141,6 +264,27 @@ final class RequestTests: XCTestCase {
waitForExpectations(timeout: 10000)
XCTAssert(success)
}
+
+ func testRequestChainErrors() {
+ let expectation = self.expectation(description: #function)
+ var success = false
+ RequestChain {
+ Request.chained { (data, err) in
+ Url("invalidurl")
+ }
+ Request.chained { (data, err) in
+ Url("https://jsonplaceholder.typicode.com/thispagedoesnotexist")
+ }
+ }
+ .call { (data, errors) in
+ if errors.count == 2 {
+ success = true
+ }
+ expectation.fulfill()
+ }
+ waitForExpectations(timeout: 10000)
+ XCTAssert(success)
+ }
func testAnyRequest() {
let expectation = self.expectation(description: #function)
@@ -167,15 +311,75 @@ final class RequestTests: XCTestCase {
waitForExpectations(timeout: 10000)
XCTAssert(success)
}
+
+ func testError() {
+ let expectation = self.expectation(description: #function)
+ var success = false
+
+ Request {
+ Url("https://jsonplaceholder.typicode./todos")
+ }
+ .onError { err in
+ print(err)
+ success = true
+ expectation.fulfill()
+ }
+ .call()
+ waitForExpectations(timeout: 10000)
+ XCTAssert(success)
+ }
+
+ func testUpdate() {
+ let expectation = self.expectation(description: #function)
+ var numResponses = 0
+
+ Request {
+ Url("https://jsonplaceholder.typicode.com/todos")
+ }
+ .update(every: 1)
+ .onData { data in
+ numResponses += 1
+ if numResponses >= 3 {
+ expectation.fulfill()
+ }
+ }
+ .call()
+ waitForExpectations(timeout: 10000)
+ }
+
+ func testTimeout() {
+ let expectation = self.expectation(description: #function)
+
+ Request {
+ Url("http://10.255.255.1")
+ Timeout(1, for: .all)
+ }
+ .onError { error in
+ if let err = error.error, let msg = String(data: err, encoding: .utf8) {
+ if msg == "The request timed out." {
+ expectation.fulfill()
+ }
+ }
+ }
+ .call()
+
+ waitForExpectations(timeout: 2000)
+ }
static var allTests = [
("simpleRequest", testSimpleRequest),
("post", testPost),
("query", testQuery),
("complexRequest", testComplexRequest),
+ ("headers", testHeaders),
("onObject", testObject),
+ ("onString", testString),
+ ("onJson", testJson),
("requestGroup", testRequestGroup),
("requestChain", testRequestChain),
+ ("requestChainErrors", testRequestChainErrors),
("anyRequest", testAnyRequest),
+ ("testError", testError),
+ ("testUpdate", testUpdate),
]
}