is it possible swift func get <T> enum? - swift

there's two func get enum from parameter,
is it possible those func combined just one func?
func rushdownSetupListener(event: RushdownListenEvent, handler: #escaping ([Any], SocketAckEmitter) -> Void) -> UUID? {
guard let client = self.client else {
return nil
}
let listenerId = client.on(event.rawValue, callback: handler)
return listenerId
}
func hystoriaSetupListener(event: HystoriaListenEvent,handler: #escaping ([Any], SocketAckEmitter) -> Void) -> UUID? {
guard let client = client else {
return nil
}
let listenerId = client.on(event.rawValue, callback: handler)
return listenerId
}

Since both of those enums have a String as their raw value, they both conform to RawRepresentable where RawValue == String. Therefore, you can introduce a generic parameter type constrained to exactly that:
func setupListener<EventType>(event: EventType, handler: #escaping ([Any], SocketAckEmitter) -> Void) -> UUID?
where EventType: RawRepresentable, EventType.RawValue == String {
guard let client = self.client else {
return nil
}
let listenerId = client.on(event.rawValue, callback: handler)
return listenerId
}
You should be able to even simplify the function body to just:
client?.on(event.rawValue, callback: handler)

Related

Swift: URLSession extension configuration error: Cannot call value of non-function type 'URLSession'

I have this function:
class myClass: ObservableObject {
func doSomethingElse<T:Decodable>(url: URL,
config: URLSessionConfiguration,
type: T.Type) -> AnyPublisher<Int, any Error> {
return URLSession(configuration:config).dataTaskPublisher(for: url)
.tryMap{ response in
guard let valueResponse = response.response as? HTTPURLResponse else {
return 000
}
return valueResponse.statusCode
}.mapError{error in
return error
}
.eraseToAnyPublisher()
}
}
and it works just fine but I'm trying to add this function to URLSession extension:
extension URLSession {
func doSomethingElse<T:Decodable>(url: URL,
config: URLSessionConfiguration,
type: T.Type) -> AnyPublisher<Int, any Error> {
return self(configuration:config).dataTaskPublisher(for: url)
.tryMap{ response in
guard let valueResponse = response.response as? HTTPURLResponse else {
return 000
}
return valueResponse.statusCode
}.mapError{error in
return error
}
.eraseToAnyPublisher()
}
But I'm getting this error:
Cannot call value of non-function type 'URLSession'
Any of you knows why I'm getting this error? or how can configure URLSession to have the configuration?
I'll really appreciate your help
self is the specific instance of URLSession. You want Self, which is the type
Self(configuration:config)...
You may also want doSomethingElse to be a static function, since it doesn't actually reference the specific instance of self:
static func doSomethingElse<T:Decodable>(url: URL,
config: URLSessionConfiguration,
type: T.Type) -> AnyPublisher<Int, any Error> {
return Self(configuration:config).dataTaskPublisher(for: url)
.tryMap{ response in
guard let valueResponse = response.response as? HTTPURLResponse else {
return 000
}
return valueResponse.statusCode
}.mapError{error in
return error
}
.eraseToAnyPublisher()
}
Change
func doSomethingElse...{
return self(configuration:...
To
static func doSomethingElse...{
return Self(configuration:...
Call it as
URLSession.doSomethingElse...

How would you test this generic API?

I've got the following APIRequest protocol:
protocol APIRequest {
associatedtype Result
var httpMethod: HTTPMethod { get }
var pathComponents: [String] { get }
func handle(responseData: Data) throws -> Result
}
Additionally, I've got the following APIClient protocol:
protocol APIClientProtocol {
func perform<T : APIRequest>(_ request: T,
completion: #escaping ((Result<T.Result, APIError>) -> Void))
}
Then I've got a class which takes an APIClientProtocol and make the request. IE:
final class DataSource {
let client: APIClientProtocol
init(client: APIClientProtocol) {
self.client = client
}
func fetchThing(completion: #escaping (Result<Thing, APIError>) -> Void) {
let thingRequest = ThingRequest() // This is an APIRequest
client.perform(thingRequest, completion: { result in
switch result {
case .success(let thing):
completion(.success(thing))
case .failure(let error):
completion(.failure(error))
}
}
}
Now I want to write a test for DataSource but I need to mock APIClientProtocol in order to do that. How can I mock it?
Using a mock of URLProtocol is thinking too deep.
There are two basic ways to mock API.
Create a full mock server and connect to it instead of your real server. This way you can create integration tests.
Just mock the response directly
class APIClientProtocolMock: APIClientProtocol {
func perform<T : APIRequest>(
_ request: T,
completion: #escaping ((Result<T.Result, APIError>) -> Void)
) {
// replace with a background queue depending on your implementation
DispatchQueue.main.async {
completion(.success(/* put your mock response here */))
}
}
}
To be honest, I am not a big friend of unit testing on FE with extensive mocking because e2e tests (UI tests) do a much better job and you don't have to create hundreds of unnecessary protocols for mocking to work.
A real example:
protocol APIRequest {
associatedtype Result
var httpMethod: String { get }
var pathComponents: [String] { get }
func handle(responseData: Data) throws -> Result
}
protocol APIClientProtocol {
func perform<T : APIRequest>(
_ request: T,
completion: #escaping ((Result<T.Result, Error>) -> Void)
)
}
struct MockAPIRequest: APIRequest {
typealias Result = String
let httpMethod: String = "POST"
let pathComponents: [String] = ["url"]
func handle(responseData: Data) throws -> Result {
return "success"
}
}
struct SecondMockAPIRequest: APIRequest {
typealias Result = String
let httpMethod: String = "POST"
let pathComponents: [String] = ["url"]
func handle(responseData: Data) throws -> Result {
return "failed"
}
}
class MockAPIClientProtocol: APIClientProtocol {
var mockData: [String: Data] = [:]
// you can load mock data dynamically
func perform<T : APIRequest>(_ request: T, completion: #escaping ((Result<T.Result, Error>) -> Void)) {
let data = mockData[request.pathComponents.joined(separator: "/")] ?? Data()
completion(.success(try! request.handle(responseData: data)))
}
// you can add implementation for a specific type
func perform(_ request: SecondMockAPIRequest, completion: #escaping ((Result<SecondMockAPIRequest.Result, Error>) -> Void)) {
let data = mockData[request.pathComponents.joined(separator: "/")] ?? Data()
completion(.success(try! request.handle(responseData: data)))
}
}
let client = MockAPIClientProtocol()
client.mockData["url"] = Data()
client.perform(MockAPIRequest()) { result in
print(result) // success
}
client.perform(SecondMockAPIRequest()) { result in
print(result) // failed
}

Generic Decoder for Swift using a protocol

I tried to use a generic Json Decoder for all of my models using a protrocol.
//Here the definition of the protocol:
func fetch<T: Decodable>(with request: URLRequest, decode: #escaping (Decodable) -> T?, completion: #escaping (Result<T, APIError>) -> Void) {.. other Code}
//Here the implementation:
func getData(from endPoint: Endpoint, completion: #escaping (Result<ApiResponseArray<Codable>, APIError>) -> Void) {
let request = endPoint.request
fetch(with: request, decode: { json -> Decodable in
guard let dataResult = json as? modelData else { return nil }
return dataResult
}, completion: completion)
}
ApiResponseArray gives me the error: Protocol type 'Codable' (aka 'Decodable & Encodable') cannot conform to 'Decodable' because only concrete types can conform to protocols. But how can I implement a generic decoder and passing them different models. I think I have to modify my protocol definition but how? I would like to pass the model and then receive the decoded data for the model (in my example modelData). Its obvious that the program runs when I write:
func getData(from endPoint: Endpoint, completion: #escaping (Result, APIError>) I mean when I use the concrete Model, but I want to pass the model, so that I can use the class for different models.
Thanks,
Arnold
A protocol cannot conform to itself, Codable must be a concrete type or can only be used as a generic constraint.
In your context you have to do the latter, something like this
func fetch<T: Decodable>(with request: URLRequest, decode: #escaping (Data) throws -> T, completion: #escaping (Result<T, APIError>) -> Void) { }
func getData<T: Decodable>(_ : T.Type = T.self, from endPoint: Endpoint, completion: #escaping (Result<T, APIError>) -> Void) {
let request = endPoint.request
fetch(with: request, decode: { data -> T in
return try JSONDecoder().decode(T.self, from: data)
}, completion: completion)
}
A network request usually returns Data which is more reasonable as parameter type of the decode closure
I can suggest to you how to use Decodable with your API call structure by using Alamofire.
I have created RequestManager class which inherits from SessionManager and added request call inside which common to all.
class RequestManager: SessionManager {
// Create shared instance
static let shared = RequestManager()
// Create http headers
lazy var httpHeaders : HTTPHeaders = {
var httpHeader = HTTPHeaders()
httpHeader["Content-Type"] = "application/json"
httpHeader["Accept"] = "application/json"
return httpHeader
}()
//------------------------------------------------------------------------------
// MARK:-
// MARK:- Request Methods
//------------------------------------------------------------------------------
func responseRequest(_ url: String, method: Alamofire.HTTPMethod, parameter: Parameters? = nil, encoding: ParameterEncoding, header: HTTPHeaders? = nil, completionHandler: #escaping (DefaultDataResponse) -> Void) -> Void {
self.request(url, method: method, parameters: parameter, encoding: encoding, headers: header).response { response in
completionHandler(response)
}
}
}
Then after one more class created NetworkManager class which hold required get/post method call and decode json by JSONDecoder as follow:
class NetworkManager {
static let shared = NetworkManager()
var progressVC : ProgressVC?
//----------------------------------------------------------------
// MARK:-
// MARK:- Get Request Method
//----------------------------------------------------------------
func getResponse<T: Decodable>(_ url: String, parameter: Parameters? = nil, encoding: ParameterEncoding = URLEncoding.default, header: HTTPHeaders? = nil, showHUD: HUDFlag = .show, message: String? = "Please wait...", decodingType: T.Type, completion: #escaping (Decodable?, APIError?) -> Void) {
DispatchQueue.main.async {
self.showHideHud(showHUD: showHUD, message: "")
}
RequestManager.shared.responseRequest(url, method: .get, parameter: parameter, encoding: encoding, header: header) { response in
DispatchQueue.main.async {
self.showHideHud(showHUD: .hide, message: "")
}
guard let httpResponse = response.response else {
completion(nil, .requestFailed("Request Failed"))
return
}
if httpResponse.statusCode == 200 {
if let data = response.data {
do {
let genericModel = try JSONDecoder().decode(decodingType, from: data)
completion(genericModel, nil)
} catch {
do {
let error = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]
if let message = error!["message"] as? String {
completion(nil, .errorMessage(message)!)
} else if let message = error!["message"] as? Int {
completion(nil, .errorMessage(String(describing: "Bad Request = \(message)")))
}
} catch {
completion(nil, .jsonConversionFailure("JSON Conversion Failure"))
}
}
} else {
completion(nil, .invalidData("Invalid Data"))
}
} else {
completion(nil, .responseUnsuccessful("Response Unsuccessful"))
}
}
}
}
ProgressVC is my custom class to show progress view when api call.
After that, I have created DataManager class which will help me to create request url.
class DataManager: NSObject {
//------------------------------------------------------------------------------
// MARK:- Variables
//------------------------------------------------------------------------------
static let shared = DataManager()
let baseUrl = WebServiceURL.local
//------------------------------------------------------------------------------
// MARK:- Custom Methods
//------------------------------------------------------------------------------
// Get API url with endpoints
func getURL(_ endpoint: WSEndPoints) -> String {
return baseUrl + endpoint.rawValue
}
}
I have created following enum to send data or error in my completion block.
enum Result<T, U> where U: Error {
case success(T)
case failure(U)
}
Here is list of error which stored custom message related to status fired during api call.
enum APIError: Error {
case errorMessage(String)
case requestFailed(String)
case jsonConversionFailure(String)
case invalidData(String)
case responseUnsuccessful(String)
case jsonParsingFailure(String)
var localizedDescription: String {
switch self {
case.errorMessage(let msg):
return msg
case .requestFailed(let msg):
return msg
case .jsonConversionFailure(let msg):
return msg
case .invalidData(let msg):
return msg
case .responseUnsuccessful(let msg):
return msg
case .jsonParsingFailure(let msg):
return msg
}
}
}
Then after, I will extend this DataManager class to call web service based on module. So I will create Swift file and extend DataManager class and call relative API.
See following, In API call I will return relative model into Result like Result<StoreListModel, APIError>
extension DataManager {
// MARK:- Store List
func getStoreList(completion: #escaping (Result<StoreListModel, APIError>) -> Void) {
NetworkManager.shared.getResponse(getURL(.storeList), parameter: nil, encoding: JSONEncoding.default, header: getHeaders("bd_suvlascentralpos"), showHUD: .show, message: "Please wait...", decodingType: StoreListModel.self) { (decodableData, apiError) in
if apiError != nil {
completion(.failure(apiError!))
} else {
guard let userData = decodableData as? StoreListModel else {
completion(.failure(apiError!))
return
}
completion(.success(userData))
}
}
}
}
From completion block of request I will get decodable data which here safely type cast.
Use:
DataManager.shared.getStoreList { (result) in
switch result {
case .success(let storeListModel):
if let storeList = storeListModel, storeList.count > 0 {
self.arrStoreList = storeList
self.tblStoreList.isHidden = false
self.labelEmptyData.isHidden = true
self.tblStoreList.reloadData()
} else {
self.tblStoreList.isHidden = true
self.labelEmptyData.isHidden = false
}
break
case .failure(let error):
print(error.localizedDescription)
break
}
}
Note:- Some variables, models classes are my custom. You can replace it with your.

I have an error generating my model from QuickType in xcode with swift

I am working with alamofire 4.0, I generate my models from QuickType a very useful tool, but I am getting this error. In the extension of Alamofire, I do not understand what can happen since all this class returns the QuickType console.
This is the model I got from quicktype with Alamofire extension
import Foundation
import Alamofire
class LoginResponse: Codable {
let user: JSONNull?
let status: Int
let success: Bool
let message: String
init(user: JSONNull?, status: Int, success: Bool, message: String) {
self.user = user
self.status = status
self.success = success
self.message = message
}
}
// MARK: Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
fileprivate func newJSONDecoder() -> JSONDecoder {
let decoder = JSONDecoder()
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
decoder.dateDecodingStrategy = .iso8601
}
return decoder
}
fileprivate func newJSONEncoder() -> JSONEncoder {
let encoder = JSONEncoder()
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
encoder.dateEncodingStrategy = .iso8601
}
return encoder
}
// MARK: - Alamofire response handlers
extension DataRequest {
fileprivate func decodableResponseSerializer<T: Decodable>() -> DataResponseSerializer<T> {
return DataResponseSerializer { _, response, data, error in
guard error == nil else { return .failure(error!) }
guard let data = data else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNil))
}
return Result { try newJSONDecoder().decode(T.self, from: data) }
}
}
#discardableResult
fileprivate func responseDecodable<T: Decodable>(queue: DispatchQueue? = nil, completionHandler: #escaping (DataResponse<T>) -> Void) -> Self {
return response(queue: queue, responseSerializer: decodableResponseSerializer(), completionHandler: completionHandler)
}
#discardableResult
func responseLoginResponse(queue: DispatchQueue? = nil, completionHandler: #escaping (DataResponse<LoginResponse>) -> Void) -> Self {
return responseDecodable(queue: queue, completionHandler: completionHandler)
}
}
the error is in this line of code fileprivate func decodableResponseSerializer<T: Decodable>() -> DataResponseSerializer<T>:
fileprivate func decodableResponseSerializer<T: Decodable>() -> DataResponseSerializer<T> {
return DataResponseSerializer { _, response, data, error in
guard error == nil else { return .failure(error!) }
guard let data = data else {
return .failure(AFError.responseSerializationFailed(reason: .inputDataNil))
}
return Result { try newJSONDecoder().decode(T.self, from: data) }
}
}
and this says
Cannot specialize non-generic type 'DataResponseSerializer'

Swift: Alamofire Value of type 'Self' (Request) has no member 'responseJSON' extension

I created an extension of Request here is the code below:
extension Request {
public func responseIDEA(completionHandler: (AnyObject?, String?) -> Void) -> Self {
self.responseJSON() { response in
if response.result.error != nil {
completionHandler (nil, response.result.error!.localizedDescription)
}else {
completionHandler(response.result.value?.valueForKey("Data"), response.result.value?.valueForKey("Message") as? String)
}
}
}
}
But I have this error:
Value of type 'Self' has no member 'responseJSON'
Why I can't get responseJSON?
responseJSON is declared in two extensions - one in DataRequest, another in DownloadRequest.
Since Request is a superclass of both of those classes, Request does not have a responseJSON method.
Create two extensions for DataRequest and DownloadRequest:
extension DataRequest {
public func responseIDEA(completionHandler: (AnyObject?, String?) -> Void) -> Self {
self.responseJSON() { response in
if response.result.error != nil {
completionHandler (nil, response.result.error!.localizedDescription)
}else {
completionHandler(response.result.value?.valueForKey("Data"), response.result.value?.valueForKey("Message") as? String)
}
}
}
}
extension DownloadRequest {
public func responseIDEA(completionHandler: (AnyObject?, String?) -> Void) -> Self {
self.responseJSON() { response in
if response.result.error != nil {
completionHandler (nil, response.result.error!.localizedDescription)
}else {
completionHandler(response.result.value?.valueForKey("Data"), response.result.value?.valueForKey("Message") as? String)
}
}
}
}
Or just write one of them if you only ever need one of them.