how can i cast server error message to AFError - ios

i am unable to find some way to cast custom message to AFError
class APIClient {
#discardableResult
static func performRequest<T:Decodable>(route:APIRouter, decoder: JSONDecoder = JSONDecoder(), completion:#escaping (Result<T, AFError>)->Void) -> DataRequest {
return AF.request(route).validate(statusCode: 200..<300).responseDecodable(decoder: decoder) { (response: DataResponse<T, AFError>) in
switch response.result {
case .success(let value):
completion(.success(value))
case .failure(let error):
let err = MYError(description: "here goes some custom error message")
let customErr = err .asAFError
completion(.failure(customErr!))
}
}
}
and my custom error message struct
struct MYError : Error{
let description : String
var localizedDescription: String {
return NSLocalizedString(description, comment: "")
}

Do you know what AFError you want to cast your error to? That's the first step to your answer because the AFError is a type of error class not one specific error. Here's my approach.
First I would change the error to an enum Errors defined as an Error just like the Struct you had. Then add your custom error as a case. Define your description and specify there what you would like the error message to be for each case (specific error).
Second, define what your Error should correspond to in an AFError for each case. This way you can specify and pass the data that will be required for some of the AFErrors like the example of SomeOtherError.
enum Errors: Error {
var localizedDescription: String {
var str: String = ""
switch self {
case .MyError: str = "Some custom error message"
case .SomeOtherError: str = "This would be another error and message"
}
return NSLocalizedString(str, comment: "")
}
var asAFError: AFError? {
switch self {
case .MyError: return .explicitlyCancelled
case .SomeOtherError(let data): return .sessionDeinitialized(data)
default: return nil
}
}
case MyError
case SomeOtherError(Data)
}
Lastly use a guard statement incase you throw and error that cannot be defined as an AFError, then pass that error to your completion like normal.
class APIClient {
#discardableResult
static func performRequest<T:Decodable>(route: APIRouter, decoder: JSONDecoder = JSONDecoder(), completion:#escaping (Result<T, AFError>)->Void) -> DataRequest {
return AF.request(route).validate(statusCode: 200..<300).responseDecodable(decoder: decoder) { (response: DataResponse<T, AFError>) in
switch response.result {
case .success(let value):
completion(.success(value))
case .failure(let error):
guard let err = Errors.MyError.asAFError
else { return error }
completion(.failure(err))
}
}
}
}

Related

How do I fix the prompt Open AI response?

I am using the OpenAI API to generate text completions for prompts, but I am encountering an issue where the API response returns an error
No value associated with key CodingKeys(stringValue: "object",
intValue: nil) ("object")
I have checked that the prompt I am sending is valid and I have tested with different prompts, but the issue persists. What could be causing this problem and how can I fix it? Any help or insights would be greatly appreciated.
Thank you.
class NetworkManager{
static let shared = NetworkManager()
#frozen enum Constants{
static let key = ""
}
private var client: OpenAISwift?
private init() {}
func setUp(){
self.client = OpenAISwift(authToken: Constants.key)
}
func getResponse(input: String, completion: #escaping (Result<String, Error>) -> Void) {
let prompt = """
translate from English to Nigerian Pidgin:
\(input)
Output:
"""
client?.sendCompletion(with: prompt, model: .gpt3(.davinci), maxTokens: 60) { result in
switch result {
case .success(let response):
let output = response.choices.first?.text ?? ""
print(response)
completion(.success(output))
case .failure(let error):
completion(.failure(error))
print(error.localizedDescription)
}
}
}
networkManager.getResponse(input: inputText) { result in
switch result {
case .success(let outputText):
DispatchQueue.main.async {
self.secondTextField.text = outputText
}
case .failure(let error):
print("Error translating text: \(error)")
}
}

Getting an error when calling the Alamofire in a class function

I'm using Alamofire for fetching the data.URLRequestConvertible protocol is using for constructing the request.When calling the URLRequestConvertible enum in requested class function through Alamofire I'm getting an error like (Invalid conversion from throwing function of type '(AFDataResponse) throws -> Void' (aka '(DataResponse<Any, AFError>) throws -> ()') to non-throwing function type '(AFDataResponse) -> Void' (aka '(DataResponse<Any, AFError>) -> ()')).In requested function where i'm fetching the result how i can used the generic?
Q1: Getting an error when fetching the result
Q2: How i can used the generic in a function
URLRequestConvertible enum:
enum Router: URLRequestConvertible{
case getAllDishes
var bseUrl : URL{
return URL(string: "https://yummie.glitch.me/")!
}
var method: HTTPMethod{
switch self {
default:
return .get
}
}
var path:String{
switch self{
case .getAllDishes:
return "dish-categories"
}
}
func asURLRequest() throws -> URLRequest {
let url = bseUrl.appendingPathComponent(path)
var request = URLRequest(url: url)
request.method = method
return request
}
}
Calling requested func:
class NetworkLayer{
class func requested(_ request:Router,completion:#escaping(Result<Data,Error>) -> Void){
ProgressHUD.show() //if response comes that loader run
AF.request(request).responseJSON{ (response) in
switch response.result{
case .success(let data):
do{
let getDishesData = data as? [String:Any]
let resp = try JSONSerialization.data(withJSONObject: getDishesData?["data"], options: .prettyPrinted)
completion(.success(response))
}
case .failure(let error):
completion(.failure(error))
}
}
}
You need to add Do-Catch Statement
catch – If the throwing method fails and raises an error, the execution will fall into this catch block.
class NetworkLayer{
class func requested(_ request:Router,completion:#escaping(Result<Data,Error>) -> Void){
ProgressHUD.show() //if response comes that loader run
AF.request(request).responseJSON{ (response) in
switch response.result{
case .success(let data):
do{
let getDishesData = data as? [String:Any]
let resp = try JSONSerialization.data(withJSONObject: getDishesData?["data"], options: .prettyPrinted)
completion(.success(response))
}catch{
print(error)
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
One more suggestion for you here no need to do JSONSerialization because responseJSON gives you direct response(That Alamofire will do JSONSerialization).
Final code
class NetworkLayer{
class func requested(_ request:Router,completion:#escaping(Result<Data,Error>) -> Void){
ProgressHUD.show() //if response comes that loader run
AF.request(request).responseJSON{ (response) in
switch response.result{
case .success(let response):
do{
print(response)
completion(.success(response))
}catch{
print(error)
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}

Error is not triggering on url session using combine

enum FailureReason : Error {
case sessionFailed(error: URLError)
case decodingFailed
case other(Error)
}
the custom error enum
private func performOperation<T: Decodable>(requestUrl: URLRequest, responseType: T.Type)->AnyPublisher<T, FailureReason>
{
return URLSession.shared.dataTaskPublisher(for: requestUrl)
.map(\.data)
.decode(type: T.self, decoder: JSONDecoder())
.mapError({ error -> FailureReason in
switch error {
case is Swift.DecodingError:
return .decodingFailed
case let urlError as URLError:
return .sessionFailed(error: urlError)
default:
return .other(error)
}
})
.eraseToAnyPublisher()
}
this is how my urlsession publisher looks like
func validateLogin(username : String , password :String) {
let url = "\(Constants.baseUrl)api/v1/auth/login/"
let htppbodyRequest = EmailLogin(username: username, password: password)
let httpBody = try! JSONEncoder().encode(htppbodyRequest)
cancellable = webservice.apiRequest(url: URL(string: url)!, resultType: User.self, httpMethodType: .post, requestBody: httpBody)
.map{ $0 }
.receive(on: RunLoop.main)
.sink(receiveCompletion: {
print("Received completion: \($0).")
}, receiveValue: { (user) in
print("user name is :\(user)")
self.subject.send(user)
})
User is the decodable struct. Even if i enter invalid username and password the Received completion of sink prints finished error is never thrown.
Subject is a passthroughSubject.
It seems you have a misunderstanding regarding when a URLSession.DataTaskPublisher should fail with an error. A data task only fails with an error in case there is a network error (such as no internet connection, SSL error, etc).
Inputting an incorrect username or password is not a network error and hence will not result in the data task throwing an error. Depending on your backend implementation, it might result in an error status code (not in the 200..<300 range) and an error response in the body of the request.
To check the status code of the HTTPURLResponse and throw an error in case it's incorrect, you can use tryMap on the dataTaskPublisher.
Here's how you can define convenience methods on URLSession.DataTaskPublisher that handle the status code of the HTTPURLResponse and throw an error in case it's incorrect.
enum NetworkingError: Error {
case decoding(DecodingError)
case incorrectStatusCode(Int)
case network(URLError)
case nonHTTPResponse
case unknown(Error)
}
extension Publisher {
func mapErrorToNetworkingError() -> AnyPublisher<Output, NetworkingError> {
mapError { error -> NetworkingError in
switch error {
case let decodingError as DecodingError:
return .decoding(decodingError)
case let networkingError as NetworkingError:
return networkingError
case let urlError as URLError:
return .network(urlError)
default:
return .unknown(error)
}
}
.eraseToAnyPublisher()
}
}
extension URLSession.DataTaskPublisher {
func emptyBodyResponsePublisher() -> AnyPublisher<Void, NetworkingError> {
httpResponseValidator()
.map { _ in Void() }
.eraseToAnyPublisher()
}
}
extension URLSession.DataTaskPublisher {
func httpResponseValidator() -> AnyPublisher<Output, NetworkingError> {
tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse else { throw NetworkingError.nonHTTPResponse }
let statusCode = httpResponse.statusCode
guard (200..<300).contains(statusCode) else { throw NetworkingError.incorrectStatusCode(statusCode) }
return (data, httpResponse)
}
.mapErrorToNetworkingError()
}
func httpResponseValidatorDataPublisher() -> AnyPublisher<Data, NetworkingError> {
httpResponseValidator()
.map(\.data)
.eraseToAnyPublisher()
}
func jsonDecodingPublisher<T:Decodable>(type: T.Type) -> AnyPublisher<T, NetworkingError> {
httpResponseValidatorDataPublisher()
.decode(type: T.self, decoder: JSONDecoder())
.mapErrorToNetworkingError()
}
}
And then you can simplify your performOperation function as below and it will throw an error in case the status code of the response is not in the expected range.
private func performOperation<T: Decodable>(requestUrl: URLRequest, responseType: T.Type) -> AnyPublisher<T, NetworkingError> {
URLSession.shared.dataTaskPublisher(for: requestUrl)
.jsonDecodingPublisher(type: T.self)
}

can't return observable of customError in network call

I want to use catchError for getting back my error as custom type.
At first, I want my network layer return Observable and then in ViewModel I subscribed it for .OnNext, .OnError, .OnCompleted events, But I don't know how should I handle Errors such as 4xx, 5xx network status code and then, them return my Custom Error Object!
My Login ViewModel :
func getAccessToken() {
let network = NetworkRequest()
network.logInRequest(tokenType: .guest, token: "cce577f6021608", secondKey: "09128147040", client: "cce577f6021608bc31424d209cbf5120c3683191").subscribe(onNext: { loginData in
self.token.onNext(loginData.access_token)
}, onError: { error in
print("The Error is: \(error.localizedDescription)")
}, onCompleted: {
print("Its Completed")
}).disposed(by: bag)
}
My network layer function:
class NetworkRequest: NSObject {
var rxProvider: MoyaProvider<WebServiceAPIs>
override init() {
rxProvider = MoyaProvider<WebServiceAPIs>( plugins: [ NetworkLoggerPlugin(verbose:true) ])
}
func logInRequest(tokenType: accessTokenTypeEnum, token: String, secondKey: String, client: String) -> Observable<LoginModel> {
return rxProvider.rx
.request(WebServiceAPIs.getAccessToken(tokenType: tokenType.rawValue, token: token, secondKey: secondKey, client: client))
.filterSuccessfulStatusCodes()
.catchError({ error -> Observable<NetworkError> in
return //Observable.just() => I want to return a custom network error as obserable
})
.map(LoginModel.self, atKeyPath: nil, using: JSONDecoder(), failsOnEmptyData: true).asObservable()
}
}
thanks for any help
In my experience, '.materialize()' operator is the perfect solution for handling HTTP errors.
Instead of separate events for success and error you get one single wrapper event with either success or error nested in it.
Moya returns MoyaError enum in error block which you can handle by extracting the error type using switch on MoyaError and then using statusCode to convert to NetworkError enum
func logInRequest(tokenType: accessTokenTypeEnum, token: String, secondKey: String, client: String) -> Observable<LoginModel> {
return sharedProvider.rx
.request(WebServiceAPIs.getAccessToken(tokenType: tokenType.rawValue, token: token, secondKey: secondKey, client: client))
.filterSuccessfulStatusCodes()
.catchError({ [weak self] error -> Observable<NetworkError> in
guard let strongSelf = self else { return Observable.empty() }
if let moyaError = error as? MoyaError {
let networkError = self?.createNetworkError(from: moyaError)
return Observable.error(networkError)
} else {
return Observable.error(NetworkError.somethingWentWrong(error.localizedDescription))
}
})
.map(LoginModel.self, atKeyPath: nil, using: JSONDecoder(), failsOnEmptyData: true).asObservable()
}
func createNetworkError(from moyaError: MoyaError) -> NetowrkError {
switch moyaError {
case .statusCode(let response):
return NetworkError.mapError(statusCode: response.statusCode)
case .underlying(let error, let response):
if let response = response {
return NetworkError.mapError(statusCode: response.statusCode)
} else {
if let nsError = error as? NSError {
return NetworkError.mapError(statusCode: nsError.code)
} else {
return NetworkError.notConnectedToInternet
}
}
default:
return NetworkError.somethingWentWrong("Something went wrong. Please try again.")
}
}
You can create your custom NetworkError enum like below which will map statusCode to custom NetworkError enum value. It will have errorDescription var which will return custom description to show in error view
enum NetworkError: Swift.Error {
case unauthorized
case serviceNotAvailable
case notConnectedToInternet
case somethingWentWrong(String)
static func mapError(statusCode: Int) -> NetworkError {
switch statusCode {
case 401:
return .unauthorized
case 501:
return .serviceNotAvailable
case -1009:
return .notConnectedToInternet
default:
return .somethingWentWrong("Something went wrong. Please try again.")
}
}
var errorDescription: String {
switch self {
case .unauthorized:
return "Unauthorised response from the server"
case .notConnectedToInternet:
return "Not connected to Internet"
case .serviceNotAvailable:
return "Service is not available. Try later"
case .somethingWentWrong(let errorMessage):
return errorMessage
}
}
}

How to use standard result type from Alamofile with no-type Success?

I'm trying to reuse Alamofire's Result type for own API callbacks.
Here is a shortened version of result type I'm using:
public enum Result<Value> {
case Success(Value)
case Failure(NSData?, ErrorType)
}
So for my API calls I'm using it in completion blocks:
func getUserContent(userId: String, completion: (result: Result<User>) -> Void) {
Alamofire.request(UserRouter.GetUser(userId))
.validate()
.responseJSON { (request, response, result) -> Void in
switch result {
case .Failure(_, let error):
completion(result: .Failure(nil, error))
case .Success(let value):
if let responseDict = value as? [String: AnyObject] {
do {
// synchronous call which parses response and
// creates User struct instance or throws exception
let user = try self.processUserResponse(responseDict)
completion(result: .Success(user))
} catch(let error) {
completion(result: .Failure(nil, error))
}
} else {
completion(result: .Failure(nil, MyAPIError.WrongResponseFormat))
}
}
}
}
I think its perfectly fits here but there is one issue. I have some calls with completion blocks which supposed to return either .Success with no value or .Failure.
E.g. deleteUser method should look something like:
func deleteUser(userId: String, completion: (result: Result<nil>) -> Void) {
// ... do some stuff here
}
so when I call it later I can do:
deleteUser(userId) { (result) -> Void in
switch result {
case .Success:
print("success")
case .Failure(nil, let error):
print("Error: \(error)")
}
}
But I can't create "empty" .Success. Result<nil> of course gives me a compile error. But I don't have any type to pass to some of .Success cases. Does anyone has a better solution that defining another Result Type with no type on .Success?
#ogreswamp is right! You can omit the type requirement with Void. Type Void is simply an empty tuple, in effect a tuple with zero elements, which can be written as (). Here is an example:
enum Result<T, E: ErrorType> {
case Success(T)
case Failure(E)
init(value: T) {
self = .Success(value)
}
init(error: E) {
self = .Failure(error)
}
}
Use this like
enum AuthenticationError: ErrorType {
case MissingEmail
case InvalidPassword
}
func signUp(email email: String, password: String) -> Result<Void, AuthenticationError>
You can return the result like
// Success
return Result(value: ())
// Failure
return Result(error: .InvalidPassword)
And finally, check the result
switch result {
case .Success(_):
print("Request SignUp was sent")
case .Failure(let error):
switch error {
case .InvalidEmail:
print("Invalid email")
default:
break
}
}
You will need to define your own Result type. Also note that Alamofire 3.0 uses a much different Result type that may better suit your needs.

Resources