Improve compose posting error messages

This commit is contained in:
Shadowfacts 2020-09-09 18:33:59 -04:00
parent e19a6528ad
commit 7c4bbfd730
Signed by untrusted user: shadowfacts
GPG Key ID: 94A5AB95422746E5
5 changed files with 57 additions and 23 deletions

View File

@ -50,22 +50,22 @@ public class Client {
let task = session.dataTask(with: request) { data, response, error in let task = session.dataTask(with: request) { data, response, error in
if let error = error { if let error = error {
completion(.failure(error)) completion(.failure(.networkError(error)))
return return
} }
guard let data = data, guard let data = data,
let response = response as? HTTPURLResponse else { let response = response as? HTTPURLResponse else {
completion(.failure(Error.invalidResponse)) completion(.failure(.invalidResponse))
return return
} }
guard response.statusCode == 200 else { guard response.statusCode == 200 else {
let mastodonError = try? self.decoder.decode(MastodonError.self, from: data) let mastodonError = try? self.decoder.decode(MastodonError.self, from: data)
let error = mastodonError.flatMap { Error.mastodonError($0.description) } ?? Error.unknownError let error: Error = mastodonError.flatMap { .mastodonError($0.description) } ?? .unexpectedStatus(response.statusCode)
completion(.failure(error)) completion(.failure(error))
return return
} }
guard let result = try? self.decoder.decode(Result.self, from: data) else { guard let result = try? self.decoder.decode(Result.self, from: data) else {
completion(.failure(Error.invalidModel)) completion(.failure(.invalidModel))
return return
} }
let pagination = response.allHeaderFields["Link"].flatMap { $0 as? String }.flatMap(Pagination.init) let pagination = response.allHeaderFields["Link"].flatMap { $0 as? String }.flatMap(Pagination.init)
@ -315,7 +315,8 @@ public class Client {
extension Client { extension Client {
public enum Error: LocalizedError { public enum Error: LocalizedError {
case unknownError case networkError(Swift.Error)
case unexpectedStatus(Int)
case invalidRequest case invalidRequest
case invalidResponse case invalidResponse
case invalidModel case invalidModel
@ -323,8 +324,13 @@ extension Client {
public var localizedDescription: String { public var localizedDescription: String {
switch self { switch self {
case .unknownError: case .networkError(let error):
return "Unknown Error" return "Network Error: \(error.localizedDescription)"
// todo: support more status codes
case .unexpectedStatus(413):
return "HTTP 413: Payload Too Large"
case .unexpectedStatus(let code):
return "HTTP Code \(code)"
case .invalidRequest: case .invalidRequest:
return "Invalid Request" return "Invalid Request"
case .invalidResponse: case .invalidResponse:

View File

@ -10,5 +10,5 @@ import Foundation
public enum Response<Result: Decodable> { public enum Response<Result: Decodable> {
case success(Result, Pagination?) case success(Result, Pagination?)
case failure(Error) case failure(Client.Error)
} }

View File

@ -22,16 +22,16 @@ public class InstanceSelector {
let request = URLRequest(url: url) let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error { if let error = error {
completion(.failure(error)) completion(.failure(.networkError(error)))
return return
} }
guard let data = data, guard let data = data,
let response = response as? HTTPURLResponse else { let response = response as? HTTPURLResponse else {
completion(.failure(Client.Error.invalidResponse)) completion(.failure(.invalidResponse))
return return
} }
guard response.statusCode == 200 else { guard response.statusCode == 200 else {
completion(.failure(Client.Error.unknownError)) completion(.failure(.unexpectedStatus(response.statusCode)))
return return
} }
guard let result = try? decoder.decode([Instance].self, from: data) else { guard let result = try? decoder.decode([Instance].self, from: data) else {

View File

@ -12,14 +12,14 @@ import Combine
struct ComposeView: View { struct ComposeView: View {
@ObservedObject var draft: Draft @ObservedObject var draft: Draft
@EnvironmentObject var mastodonController: MastodonController @EnvironmentObject var mastodonController: MastodonController
@EnvironmentObject var uiState: ComposeUIState @EnvironmentObject var uiState: ComposeUIState
@State var isPosting = false
@State var postProgress: Double = 0 @State private var isPosting = false
@State var postTotalProgress: Double = 0 @State private var postProgress: Double = 0
@State var isShowingPostErrorAlert = false @State private var postTotalProgress: Double = 0
@State var postError: Error? @State private var isShowingPostErrorAlert = false
@State private var postError: PostError?
private let stackPadding: CGFloat = 8 private let stackPadding: CGFloat = 8
@ -191,6 +191,9 @@ struct ComposeView: View {
case let .failure(error): case let .failure(error):
self.isShowingPostErrorAlert = true self.isShowingPostErrorAlert = true
self.postError = error self.postError = error
self.postProgress = 0
self.postTotalProgress = 0
self.isPosting = false
case let .success(uploadedAttachments): case let .success(uploadedAttachments):
let request = Client.createStatus(text: draft.text, let request = Client.createStatus(text: draft.text,
@ -222,7 +225,7 @@ struct ComposeView: View {
} }
} }
private func uploadAttachments(_ completion: @escaping (Result<[Attachment], Error>) -> Void) { private func uploadAttachments(_ completion: @escaping (Result<[Attachment], AttachmentUploadError>) -> Void) {
let group = DispatchGroup() let group = DispatchGroup()
var anyFailed = false var anyFailed = false
@ -239,13 +242,13 @@ struct ComposeView: View {
let formAttachment = FormAttachment(mimeType: mimeType, data: data, fileName: "file") let formAttachment = FormAttachment(mimeType: mimeType, data: data, fileName: "file")
let request = Client.upload(attachment: formAttachment, description: compAttachment.attachmentDescription) let request = Client.upload(attachment: formAttachment, description: compAttachment.attachmentDescription)
self.mastodonController.run(request) { (response) in self.mastodonController.run(request) { (response) in
postProgress += 1
switch response { switch response {
case let .failure(error): case let .failure(error):
uploadedAttachments[index] = .failure(error) uploadedAttachments[index] = .failure(error)
anyFailed = true anyFailed = true
case let .success(attachment, _): case let .success(attachment, _):
postProgress += 1
uploadedAttachments[index] = .success(attachment) uploadedAttachments[index] = .success(attachment)
} }
@ -274,14 +277,37 @@ struct ComposeView: View {
} }
} }
fileprivate struct AttachmentUploadError: LocalizedError { fileprivate protocol PostError: LocalizedError {}
extension PostError {
var localizedDescription: String {
if let self = self as? Client.Error {
return self.localizedDescription
} else if let self = self as? AttachmentUploadError {
return self.localizedDescription
} else {
return "Unknown Error"
}
}
}
extension Client.Error: PostError {}
fileprivate struct AttachmentUploadError: PostError {
let errors: [Error?] let errors: [Error?]
var localizedDescription: String { var localizedDescription: String {
return errors.enumerated().compactMap { (index, error) -> String? in return errors.enumerated().compactMap { (index, error) -> String? in
guard let error = error else { return nil } guard let error = error else { return nil }
return "Attachment \(index + 1): \(error.localizedDescription)" let description: String
}.joined(separator: ", ") // need to downcast to use more specific localizedDescription impl from Pachyderm
if let error = error as? Client.Error {
description = error.localizedDescription
} else {
description = error.localizedDescription
}
return "Attachment \(index + 1): \(description)"
}.joined(separator: ",\n")
} }
} }

View File

@ -21,6 +21,8 @@ struct WrappedProgressView: UIViewRepresentable {
func updateUIView(_ uiView: UIProgressView, context: Context) { func updateUIView(_ uiView: UIProgressView, context: Context) {
if total > 0 { if total > 0 {
uiView.setProgress(Float(value / total), animated: true) uiView.setProgress(Float(value / total), animated: true)
} else {
uiView.setProgress(0, animated: true)
} }
} }
} }