Files
swift-nio/Sources/_NIOFileSystem/FileSystemError.swift
George Barnett 66a85ba0e2 Add back _NIOFileSystem (#3367)
Motivation:

In #3363 we converted `_NIOFileSystem` to `NIOFileSystem` and removed
the (unreleased) overloads for FilePath/NIOFilePath. This change adds
back `_NIOFileSystem` such that it matches the API it had at 2.86.0.

Modifications:

- Add back `_NIOFileSystem` and `_NIOFileSystemFoundationCompat` such
that their API is at 2.86.0

Result:

- `NIOFileSystem` uses `NIOFilePath`
- `_NIOFileSystem` uses `FilePath`
2025-09-02 14:56:58 +01:00

281 lines
9.3 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import SystemPackage
/// An error thrown as a result of interaction with the file system.
///
/// All errors have a high-level ``FileSystemError/Code-swift.struct`` which identifies the domain
/// of the error. For example an operation performed on a ``FileHandleProtocol`` which has already been
/// closed will result in a ``FileSystemError/Code-swift.struct/closed`` error code. Errors also
/// include a message describing what went wrong and how to remedy it (if applicable). The
/// ``FileSystemError/message`` is not static and may include dynamic information such as the path
/// of the file for which the operation failed, for example.
///
/// Errors may have a ``FileSystemError/cause``, an error which caused the operation to
/// fail which may be platform specific.
public struct FileSystemError: Error, Sendable {
/// A high-level error code to provide broad a classification.
public var code: Code
/// A message describing what went wrong and how it may be remedied.
public var message: String
/// An error which caused the operation to fail. This may include additional details
/// about the root cause of the failure.
public var cause: Error?
/// The location from which this error was thrown.
public var location: SourceLocation
public init(
code: Code,
message: String,
cause: Error?,
location: SourceLocation
) {
self.code = code
self.message = message
self.cause = cause
self.location = location
}
/// Creates a ``FileSystemError`` by wrapping the given `cause` and its location and code.
internal init(message: String, wrapping cause: FileSystemError) {
self.init(code: cause.code, message: message, cause: cause, location: cause.location)
}
}
extension FileSystemError: CustomStringConvertible {
public var description: String {
if let cause = self.cause {
return "\(self.code): \(self.message) (\(cause))"
} else {
return "\(self.code): \(self.message)"
}
}
}
extension FileSystemError: CustomDebugStringConvertible {
public var debugDescription: String {
if let cause = self.cause {
return """
\(String(reflecting: self.code)): \(String(reflecting: self.message)) \
(\(String(reflecting: cause)))
"""
} else {
return "\(String(reflecting: self.code)): \(String(reflecting: self.message))"
}
}
}
extension FileSystemError {
private func detailedDescriptionLines() -> [String] {
// Build up a tree-like description of the error. This allows nested causes to be formatted
// correctly, especially when they are also FileSystemErrors.
//
// An example is:
//
// FileSystemError: Closed
// Reason: Unable to open file at path 'foo.swift', the descriptor is closed.
// Cause: 'openat' system call failed with '(9) Bad file descriptor'.
// Source location: openFile(forReadingAt:_:) (FileSystem.swift:314)
var lines = [
"FileSystemError: \(self.code)",
"├─ Reason: \(self.message)",
]
if let error = self.cause as? FileSystemError {
lines.append("├─ Cause:")
let causeLines = error.detailedDescriptionLines()
// We know this will never be empty.
lines.append("│ └─ \(causeLines.first!)")
lines.append(contentsOf: causeLines.dropFirst().map { "\($0)" })
} else if let error = self.cause {
lines.append("├─ Cause: \(String(reflecting: error))")
}
lines.append(
"└─ Source location: \(self.location.function) (\(self.location.file):\(self.location.line))"
)
return lines
}
/// A detailed multi-line description of the error.
///
/// - Returns: A multi-line description of the error.
public func detailedDescription() -> String {
self.detailedDescriptionLines().joined(separator: "\n")
}
}
extension FileSystemError {
/// A high level indication of the kind of error being thrown.
public struct Code: Hashable, Sendable, CustomStringConvertible {
private enum Wrapped: Hashable, Sendable, CustomStringConvertible {
case closed
case invalidArgument
case io
case permissionDenied
case notEmpty
case notFound
case resourceExhausted
case unavailable
case unknown
case unsupported
case fileAlreadyExists
var description: String {
switch self {
case .closed:
return "Closed"
case .invalidArgument:
return "Invalid argument"
case .io:
return "I/O error"
case .permissionDenied:
return "Permission denied"
case .resourceExhausted:
return "Resource exhausted"
case .notEmpty:
return "Not empty"
case .notFound:
return "Not found"
case .unavailable:
return "Unavailable"
case .unknown:
return "Unknown"
case .unsupported:
return "Unsupported"
case .fileAlreadyExists:
return "File already exists"
}
}
}
public var description: String {
String(describing: self.code)
}
private var code: Wrapped
private init(_ code: Wrapped) {
self.code = code
}
/// An operation on the file could not be performed because the file is closed
/// (or detached).
public static var closed: Self {
Self(.closed)
}
/// A provided argument was not valid for the operation.
public static var invalidArgument: Self {
Self(.invalidArgument)
}
/// An I/O error occurred.
public static var io: Self {
Self(.io)
}
/// The caller did not have sufficient permission to perform the operation.
public static var permissionDenied: Self {
Self(.permissionDenied)
}
/// A required resource was exhausted.
public static var resourceExhausted: Self {
Self(.resourceExhausted)
}
/// The directory wasn't empty.
public static var notEmpty: Self {
Self(.notEmpty)
}
/// The file could not be found.
public static var notFound: Self {
Self(.notFound)
}
/// The file system is not currently available, for example if the executor
/// is not running.
public static var unavailable: Self {
Self(.unavailable)
}
/// The error is not known or may not have an appropriate classification. See
/// ``FileSystemError/cause`` for more information about the error.
public static var unknown: Self {
Self(.unknown)
}
/// The operation is not supported or is not enabled.
public static var unsupported: Self {
Self(.unsupported)
}
/// The file already exists.
public static var fileAlreadyExists: Self {
Self(.fileAlreadyExists)
}
}
/// A location within source code.
public struct SourceLocation: Sendable, Hashable {
/// The function in which the error was thrown.
public var function: String
/// The file in which the error was thrown.
public var file: String
/// The line on which the error was thrown.
public var line: Int
public init(function: String, file: String, line: Int) {
self.function = function
self.file = file
self.line = line
}
internal static func here(
function: String = #function,
file: String = #fileID,
line: Int = #line
) -> Self {
SourceLocation(function: function, file: file, line: line)
}
}
}
extension FileSystemError {
/// An error resulting from a system call.
public struct SystemCallError: Error, Hashable, CustomStringConvertible {
/// The name of the system call which produced the error.
public var systemCall: String
/// The errno set by the system call.
public var errno: Errno
public init(systemCall: String, errno: Errno) {
self.systemCall = systemCall
self.errno = errno
}
public var description: String {
"'\(self.systemCall)' system call failed with '(\(self.errno.rawValue)) \(self.errno)'."
}
}
}