Skip to content

Commit 30fdfee

Browse files
authored
Fix/Directory access errors (#218)
2 parents 562ba6f + a26e9d2 commit 30fdfee

File tree

12 files changed

+76
-45
lines changed

12 files changed

+76
-45
lines changed

Lingua-App/Lingua/Lingua/Utils/Components/UI/ValidatingTextField.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ struct ValidatingTextField: View {
3737
VStack(alignment: .leading, spacing: 5) {
3838
TextField(title, text: $localText)
3939
.disabled(isDisabled)
40-
.cornerRadius(8)
4140
.focused($isFocused)
41+
.frame(minHeight: 20)
42+
.padding(.trailing)
4243
.onChange(of: isFocused) { focused in
4344
if !focused {
4445
validate()
@@ -51,6 +52,10 @@ struct ValidatingTextField: View {
5152
.font(.caption)
5253
}
5354
}
55+
.contentShape(Rectangle())
56+
.onTapGesture {
57+
isFocused = true
58+
}
5459
.onAppear {
5560
isValid = validation.validate(text)
5661
}

Sources/LinguaLib/Infrastructure/DirectoryOperations/DirectoryOperable.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,25 @@ public final class DirectoryOperator: DirectoryOperable {
1414
}
1515

1616
public func createDirectory(named directoryName: String, in outputDirectory: String) throws -> URL {
17-
let mainFolder = URL(fileURLWithPath: outputDirectory)
17+
guard !directoryName.isEmpty else {
18+
throw DirectoryOperationError.folderCreationFailed("Directory name is empty.")
19+
}
20+
21+
guard let decodedPath = outputDirectory.removingPercentEncoding else {
22+
throw DirectoryOperationError.folderCreationFailed("Invalid output directory path.")
23+
}
24+
25+
let mainFolder = URL(fileURLWithPath: decodedPath)
1826
let outputFolder = mainFolder.appendingPathComponent(directoryName)
27+
1928
do {
2029
try fileManagerProvider.manager.createDirectory(at: outputFolder, withIntermediateDirectories: true, attributes: nil)
30+
} catch let error as DirectoryOperationError {
31+
throw error
2132
} catch {
22-
throw DirectoryOperationError.folderCreationFailed
33+
throw DirectoryOperationError.folderCreationFailed(error.localizedDescription)
2334
}
35+
2436
return outputFolder
2537
}
2638

@@ -32,8 +44,10 @@ public final class DirectoryOperator: DirectoryOperable {
3244
for fileURL in fileURLs where fileURL.lastPathComponent.hasPrefix(prefix) {
3345
do {
3446
try fileManager.removeItem(at: fileURL)
47+
} catch let error as DirectoryOperationError {
48+
throw error
3549
} catch {
36-
throw DirectoryOperationError.removeItemFailed
50+
throw DirectoryOperationError.removeItemFailed(error.localizedDescription)
3751
}
3852
}
3953
}
@@ -47,7 +61,7 @@ public final class DirectoryOperator: DirectoryOperable {
4761
do {
4862
try fileManager.removeItem(at: fileURL)
4963
} catch {
50-
throw DirectoryOperationError.removeItemFailed
64+
throw DirectoryOperationError.removeItemFailed(error.localizedDescription)
5165
}
5266
}
5367
}
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import Foundation
22

3-
public enum DirectoryOperationError: LocalizedError {
4-
case folderCreationFailed
5-
case removeItemFailed
3+
public enum DirectoryOperationError: LocalizedError, Equatable {
4+
case folderCreationFailed(String)
5+
case removeItemFailed(String)
66

77
public var errorDescription: String? {
88
switch self {
9-
case .folderCreationFailed:
10-
return "Failed to create the folder in given directory"
11-
case .removeItemFailed:
12-
return "Failed to remove item in this folder"
9+
case .folderCreationFailed(let error):
10+
return "Failed to create the folder in given directory.\nReason: \(error)"
11+
case .removeItemFailed(let error):
12+
return "Failed to remove item in this folder.\nReason: \(error)"
1313
}
1414
}
1515
}

Sources/LinguaLib/Infrastructure/LocalizationGenerator/Generator/PlatformLocalizationGenerator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ final class PlatformLocalizationGenerator: PlatformLocalizationGenerating {
1111
}
1212

1313
func generateLocalizationFiles(data: [LocalizationSheet], config: Config.Localization) throws {
14-
data.forEach { data in
15-
try? localizedFileGenerator.generate(for: data, config: config)
14+
try data.forEach { data in
15+
try localizedFileGenerator.generate(for: data, config: config)
1616
}
1717
}
1818
}

Tests/LinguaTests/Application/Processor/LocalizationProcessorTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class LocalizationProcessorTests: XCTestCase {
3535
}
3636

3737
func test_process_throwsErrorWhenModuleLocalizationFails() async throws {
38-
let localizationModule = MockLocalizationModule(shouldThrow: true)
38+
let localizationModule = MockLocalizationModule(errorMessage: "Error_message")
3939
let (sut, actors) = makeSUT(localizationModule: localizationModule)
4040
let tempDirectoryURL = try createTemporaryDirectoryURL()
4141
let configPath = try createTemporaryConfigFile(data: createConfigData(in: tempDirectoryURL), tempDirectoryURL: tempDirectoryURL)
@@ -47,7 +47,7 @@ final class LocalizationProcessorTests: XCTestCase {
4747
XCTAssertEqual(actors.logger.messages, [.message(message: "Loading configuration file...", level: .info),
4848
.message(message: "Initializing localization module...", level: .info),
4949
.message(message: "Starting localization...", level: .info),
50-
.message(message: DirectoryOperationError.folderCreationFailed.localizedDescription, level: .error)])
50+
.message(message: DirectoryOperationError.folderCreationFailed("Error_message").localizedDescription, level: .error)])
5151
XCTAssertEqual(actors.mockLocalizationModule.messages, [])
5252
}
5353
}
@@ -85,7 +85,7 @@ private extension LocalizationProcessorTests {
8585
let mockLocalizationModule: MockLocalizationModule
8686
}
8787

88-
func makeSUT(localizationModule: MockLocalizationModule = MockLocalizationModule(),
88+
func makeSUT(localizationModule: MockLocalizationModule = MockLocalizationModule(errorMessage: .none),
8989
configFileGenerator: ConfigInitialFileGenerating = ConfigInitialFileGenerator.make()) -> (sut: LocalizationProcessor, actors: Actors) {
9090
let argumentParser = CommandLineParser()
9191
let logger = MockLogger()

Tests/LinguaTests/Application/Processor/Mock/MockLocalizationModule.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ final class MockLocalizationModule: ModuleLocalizing {
66
case localize(LocalizationPlatform)
77
}
88
private(set) var messages = [Message]()
9-
private let shouldThrow: Bool
9+
private let errorMessage: String?
1010

11-
init(shouldThrow: Bool = false) {
12-
self.shouldThrow = shouldThrow
11+
init(errorMessage: String?) {
12+
self.errorMessage = errorMessage
1313
}
1414

1515
func localize(for platform: LocalizationPlatform) async throws {
16-
if shouldThrow {
17-
throw DirectoryOperationError.folderCreationFailed
16+
if let errorMessage {
17+
throw DirectoryOperationError.folderCreationFailed(errorMessage)
1818
}
1919
messages.append(.localize(platform))
2020
}

Tests/LinguaTests/Infrastructure/DirectoryOperations/DirectoryOperatorTests.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,25 @@ final class DirectoryOperatorTests: XCTestCase {
1313

1414
func test_createDirectory_throwsError_onFailure() {
1515
let errorFileManager = MockFileManager()
16-
errorFileManager.shouldThrowErrorOnCreateDirectory = true
16+
errorFileManager.onCreateDirectoryError = "Create_failed"
1717
let sut = makeSUT(fileManager: errorFileManager)
1818
let outputDirectory = NSTemporaryDirectory()
1919
let directoryName = "TestDirectory"
2020

2121
XCTAssertThrowsError(try sut.createDirectory(named: directoryName, in: outputDirectory)) { error in
22-
XCTAssertEqual(error as? DirectoryOperationError, DirectoryOperationError.folderCreationFailed)
22+
XCTAssertEqual((error as? DirectoryOperationError), DirectoryOperationError.folderCreationFailed("Create_failed"))
23+
}
24+
}
25+
26+
func test_createDirectory_throwsError_onEmptyDirectory() {
27+
let errorFileManager = MockFileManager()
28+
errorFileManager.onCreateDirectoryError = "Directory name is empty."
29+
let sut = makeSUT(fileManager: errorFileManager)
30+
let outputDirectory = NSTemporaryDirectory()
31+
let directoryName = ""
32+
33+
XCTAssertThrowsError(try sut.createDirectory(named: directoryName, in: outputDirectory)) { error in
34+
XCTAssertEqual((error as? DirectoryOperationError)?.errorDescription, DirectoryOperationError.folderCreationFailed("Directory name is empty.").errorDescription)
2335
}
2436
}
2537

@@ -47,7 +59,7 @@ final class DirectoryOperatorTests: XCTestCase {
4759

4860
func test_removeFiles_throwsError_onRemoveItem() throws {
4961
let errorFileManager = MockFileManager()
50-
errorFileManager.shouldThrowErrorOnRemoveItem = true
62+
errorFileManager.onRemoveItemError = "Remove_failed"
5163
let sut = makeSUT(fileManager: errorFileManager)
5264
let outputDirectory = NSTemporaryDirectory()
5365
let directoryName = "TestDirectory"
@@ -64,7 +76,7 @@ final class DirectoryOperatorTests: XCTestCase {
6476
// Test the error case
6577
XCTAssertThrowsError(try sut.removeFiles(withPrefix: .packageName, in: createdDirectoryURL)) { error in
6678
XCTAssertEqual((error as? DirectoryOperationError)?.localizedDescription,
67-
DirectoryOperationError.removeItemFailed.localizedDescription)
79+
DirectoryOperationError.removeItemFailed("Remove_failed").localizedDescription)
6880
}
6981
}
7082
}

Tests/LinguaTests/Infrastructure/DirectoryOperations/Mock/MockFileManager.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import Foundation
33

44
final class MockFileManager: FileManager {
55
let files: [String]
6-
var shouldThrowErrorOnRemoveItem = false
7-
var shouldThrowErrorOnCreateDirectory = false
6+
var onRemoveItemError: String?
7+
var onCreateDirectoryError: String?
88

99
init(files: [String] = []) {
1010
self.files = files
@@ -17,16 +17,16 @@ final class MockFileManager: FileManager {
1717
override func createDirectory(at url: URL,
1818
withIntermediateDirectories createIntermediates: Bool,
1919
attributes: [FileAttributeKey : Any]? = nil) throws {
20-
if shouldThrowErrorOnCreateDirectory {
21-
throw DirectoryOperationError.folderCreationFailed
20+
if let onCreateDirectoryError {
21+
throw DirectoryOperationError.folderCreationFailed(onCreateDirectoryError)
2222
} else {
2323
try super.createDirectory(at: url, withIntermediateDirectories: createIntermediates, attributes: attributes)
2424
}
2525
}
2626

2727
override func removeItem(at URL: URL) throws {
28-
if shouldThrowErrorOnRemoveItem {
29-
throw NSError(domain: NSCocoaErrorDomain, code: NSFileWriteUnknownError, userInfo: nil)
28+
if let onRemoveItemError {
29+
throw DirectoryOperationError.removeItemFailed(onRemoveItemError)
3030
} else {
3131
try super.removeItem(at: URL)
3232
}

Tests/LinguaTests/Infrastructure/LocalizationGenerator/Generator/ConfigInitialFileGeneratorTests.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ final class ConfigInitialFileGeneratorTests: XCTestCase {
1616
}
1717

1818
func test_generate_throwsErrorOnFailure() {
19-
let (sut, _) = makeSUT(shouldFail: true)
19+
let (sut, _) = makeSUT(shouldFail: "error")
2020

2121
XCTAssertThrowsError(try sut.generate())
2222
}
@@ -38,9 +38,9 @@ private extension ConfigInitialFileGeneratorTests {
3838
let fileName: String
3939
}
4040

41-
func makeSUT(shouldFail: Bool = false, encoder: JSONEncoding? = nil) -> (sut: ConfigInitialFileGenerator, actors: Actors) {
41+
func makeSUT(shouldFail: String? = .none, encoder: JSONEncoding? = nil) -> (sut: ConfigInitialFileGenerator, actors: Actors) {
4242
let contentFilesCreator = MockContentFilesCreator()
43-
contentFilesCreator.shouldThrowError = shouldFail
43+
contentFilesCreator.errorMessage = shouldFail
4444
let config = Config.createTemplateConfig()
4545
let transformer = ConfigTransformer()
4646
let fileName = "config.json"

Tests/LinguaTests/Infrastructure/LocalizationGenerator/Generator/LocalizedPlatformFilesGeneratorTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import XCTest
33

44
final class LocalizedPlatformFilesGeneratorTests: XCTestCase {
55
func test_createPlatformFiles_createsFilesSuccessfully() throws {
6-
let (contentGenerator, filesCreator) = makeContentGeneratorAndCreator()
6+
let (contentGenerator, filesCreator) = makeContentGeneratorAndCreator(filesCreatorThrownErrorMessage: .none)
77
let fileExtension = "txt"
88
let fileNameGenerator = MockPlatformFilesNameGenerator(fileExtension: fileExtension)
99
let sut = makeSUT(contentGenerator: contentGenerator, filesCreator: filesCreator, fileNameGenerator: fileNameGenerator)
@@ -18,7 +18,7 @@ final class LocalizedPlatformFilesGeneratorTests: XCTestCase {
1818
}
1919

2020
func test_createPlatformFiles_throwsErrorAndLogsMessage_onCreateFilesFailure() {
21-
let (contentGenerator, filesCreator) = makeContentGeneratorAndCreator(shouldFilesCreatorThrowError: true)
21+
let (contentGenerator, filesCreator) = makeContentGeneratorAndCreator(filesCreatorThrownErrorMessage: "Error_message")
2222
let sut = makeSUT(contentGenerator: contentGenerator, filesCreator: filesCreator)
2323
let entries: [LocalizationEntry] = [.create(plural: true)]
2424
let outputFolder = URL(fileURLWithPath: NSTemporaryDirectory())
@@ -27,17 +27,17 @@ final class LocalizedPlatformFilesGeneratorTests: XCTestCase {
2727
sectionName: "SectionName",
2828
outputFolder: outputFolder,
2929
language: "en")) { error in
30-
XCTAssertEqual(error as? DirectoryOperationError, DirectoryOperationError.folderCreationFailed)
30+
XCTAssertEqual(error as? DirectoryOperationError, DirectoryOperationError.folderCreationFailed("Error_message"))
3131
}
3232
}
3333

3434
func makeContentGeneratorAndCreator(
35-
shouldFilesCreatorThrowError: Bool = false
35+
filesCreatorThrownErrorMessage: String?
3636
) -> (contentGenerator: MockLocalizedContentGenerator, filesCreator: MockContentFilesCreator) {
3737
let contentGenerator = MockLocalizedContentGenerator()
3838
contentGenerator.content = ("stringsContent", "stringsDictContent")
3939
let filesCreator = MockContentFilesCreator()
40-
filesCreator.shouldThrowError = shouldFilesCreatorThrowError
40+
filesCreator.errorMessage = filesCreatorThrownErrorMessage
4141
return (contentGenerator, filesCreator)
4242
}
4343
}

0 commit comments

Comments
 (0)