mirror of
https://github.com/apple/swift-nio.git
synced 2026-05-20 20:30:36 +00:00
a18bddb0ac
Motivation: We accidentally removed the 'NIOFileSystem' module from the '_NIOFileSystem' product in the last release. Modifications: - Rename 'NIOFileSystem' and 'NIOFileSystemFoundationCompat' to 'NIOFS' and 'NIOFSFoundationCompat' - Add back 'NIOFileSystem' which re-exports '_NIOFileSystem' (there was no publicly available 'NIOFileSystemFoundationCompat' module to remove, only '_NIOFileSystemFoundationCompat'). Result: Fewer breaks
281 lines
9.3 KiB
Swift
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 underlying 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 underlying 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 underlying 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)'."
|
|
}
|
|
}
|
|
}
|