Skip to content

Commit

Permalink
Fix missing error handling for incompatible device artifacts (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
lfroms authored Dec 2, 2024
1 parent 7559460 commit 4844ce6
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 51 deletions.
25 changes: 19 additions & 6 deletions Tophat/Extensions/ApplicationError+LocalizedError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,32 @@ import TophatFoundation
extension ApplicationError: LocalizedError {
public var errorDescription: String? {
switch self {
case .missingProvisioningProfile, .deviceNotProvisioned, .applicationNotSigned:
return "This application canʼt be installed"
case .missingProvisioningProfile, .deviceNotProvisioned, .applicationNotSigned, .incompatible:
"This application canʼt be installed"
default:
return nil
nil
}
}

public var failureReason: String? {
switch self {
case .missingProvisioningProfile:
return "The selected device requires that the application contains an embedded provisioning profile, but none was found in the application bundle."

case .deviceNotProvisioned:
return "The selected device is not provisioned."

case .applicationNotSigned:
return "The selected device requires that the application is signed with an Apple Development or Enterprise certificate."

case .incompatible(let application, let device):
let applicationPlatformDescription = String(describing: application.platform)
let applicationTargetsDescription = application.targets.map { String(describing: $0) }.formatted(.list(type: .and))
let devicePlatformDescription = String(describing: device.runtime.platform)
let deviceTargetDescription = String(describing: device.type)

return "The application was built for \(applicationPlatformDescription) \(applicationTargetsDescription), but \(device.name) is \(devicePlatformDescription.indefiniteArticle) \(devicePlatformDescription) \(deviceTargetDescription)."

default:
return nil
}
Expand All @@ -35,11 +46,13 @@ extension ApplicationError: LocalizedError {
var recoverySuggestion: String? {
switch self {
case .deviceNotProvisioned:
return "Add your device to the Apple Developer Portal before building the application."
"Add your device to the Apple Developer Portal before building the application."
case .applicationNotSigned:
return "Ensure that the application is signed and try again."
"Ensure that the application is signed and try again."
case .incompatible:
"Ensure that the correct artifact is being downloaded and that the correct platform and destination are being specified."
default:
return nil
nil
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extension InstallationTicketMachineError: LocalizedError {
switch self {
case .noCompatibleDevices(let providedBuildTypes):
let text = description(for: providedBuildTypes)
return "Select \(text.startsWithVowel ? "an" : "a") \(text) using the Tophat menu and try again."
return "Select \(text.indefiniteArticle) \(text) using the Tophat menu and try again."
case .noSelectedDevices:
return "Select a device from the Tophat menu and try again."
}
Expand All @@ -42,20 +42,7 @@ extension InstallationTicketMachineError: LocalizedError {
providedBuildTypes.map { "\($0.key) \(description(for: $0.value))" }.formatted(.list(type: .or))
}

private func description(for platforms: Set<Platform>) -> String {
platforms.map { String(describing: $0) }.formatted(.list(type: .or))
}

private func description(for targets: Set<DeviceType>) -> String {
targets.map { String(describing: $0) }.formatted(.list(type: .or))
}

}

extension Character {
var isVowel: Bool { "aeiou".contains { String($0).compare(String(self).folding(options: .diacriticInsensitive, locale: nil), options: .caseInsensitive) == .orderedSame } }
}

extension StringProtocol {
var startsWithVowel: Bool { first?.isVowel == true }
}
40 changes: 40 additions & 0 deletions Tophat/Extensions/String+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// String+Extensions.swift
// Tophat
//
// Created by Lukas Romsicki on 2024-12-02.
// Copyright © 2024 Shopify. All rights reserved.
//

import Foundation

extension String {
var indefiniteArticle: String {
startsWithVowel ? "an" : "a"
}
}

extension String {
var isValidURL: Bool {
if let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue),
let match = detector.firstMatch(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) {
return match.range.length == self.utf16.count
}

return false
}
}

extension Character {
var isVowel: Bool {
"aeiou".contains {
String($0).compare(String(self).folding(options: .diacriticInsensitive, locale: nil), options: .caseInsensitive) == .orderedSame
}
}
}

extension StringProtocol {
var startsWithVowel: Bool {
first?.isVowel == true
}
}
20 changes: 0 additions & 20 deletions Tophat/Extensions/String+IsValidURL.swift

This file was deleted.

6 changes: 2 additions & 4 deletions Tophat/Models/AndroidApplication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,9 @@ struct AndroidApplication: Application {
}

func validateEligibility(for device: Device) throws {
guard platform == device.runtime.platform else {
throw ApplicationError.incompatibleDeviceType
guard platform == device.runtime.platform, targets.contains(device.type) else {
throw ApplicationError.incompatible(application: self, device: device)
}

// Android applications can be installed on any Android device.
}
}

Expand Down
8 changes: 2 additions & 6 deletions Tophat/Models/AppleApplication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,8 @@ struct AppleApplication: Application {
}

func validateEligibility(for device: Device) throws {
guard platform == device.runtime.platform else {
throw ApplicationError.incompatibleDeviceType
}

if !targets.contains(device.type) {
throw ApplicationError.incompatibleDeviceType
guard platform == device.runtime.platform, targets.contains(device.type) else {
throw ApplicationError.incompatible(application: self, device: device)
}

if device.type == .simulator {
Expand Down
4 changes: 3 additions & 1 deletion Tophat/Models/ApplicationError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
// Copyright © 2022 Shopify. All rights reserved.
//

import TophatFoundation

enum ApplicationError: Error {
case failedToReadBundleIdentifier
case incompatibleDeviceType
case incompatible(application: Application, device: Device)
case missingProvisioningProfile
case deviceNotProvisioned
case applicationNotSigned
Expand Down

0 comments on commit 4844ce6

Please sign in to comment.