diff --git a/Sources/NIOFileSystem/String+FileSystem.swift b/Sources/NIOFileSystem/String+FileSystem.swift new file mode 100644 index 00000000000..57e2d51c0b8 --- /dev/null +++ b/Sources/NIOFileSystem/String+FileSystem.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2025 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 NIOCore + +@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) +extension String { + /// Reads the contents of the file at the path into a String. + /// + /// - Parameters: + /// - path: The path of the file to read. + /// - maximumSizeAllowed: The maximum size of file which can be read, in bytes, as a ``ByteCount``. If the file is larger than this, an error is thrown. + /// - fileSystem: The ``FileSystemProtocol`` instance to use to read the file. + /// + /// - Throws: If the file is larger than `maximumSizeAllowed`, an ``FileSystemError/Code-swift.struct/resourceExhausted`` error will be thrown. + public init( + contentsOf path: FilePath, + maximumSizeAllowed: ByteCount, + fileSystem: some FileSystemProtocol + ) async throws { + let byteBuffer = try await fileSystem.withFileHandle(forReadingAt: path) { handle in + try await handle.readToEnd(maximumSizeAllowed: maximumSizeAllowed) + } + + self = Self(buffer: byteBuffer) + } + + /// Reads the contents of the file at the path using ``FileSystem``. + /// + /// - Parameters: + /// - path: The path of the file to read. + /// - maximumSizeAllowed: The maximum size of file which can be read, in bytes, as a ``ByteCount``. If the file is larger than this, an error is thrown. + /// + /// - Throws: If the file is larger than `maximumSizeAllowed`, an ``FileSystemError/Code-swift.struct/resourceExhausted`` error will be thrown. + public init( + contentsOf path: FilePath, + maximumSizeAllowed: ByteCount + ) async throws { + self = try await Self( + contentsOf: path, + maximumSizeAllowed: maximumSizeAllowed, + fileSystem: .shared + ) + } +} diff --git a/Tests/NIOFileSystemIntegrationTests/ConvenienceTests.swift b/Tests/NIOFileSystemIntegrationTests/ConvenienceTests.swift index 85a09505df4..de9a6094859 100644 --- a/Tests/NIOFileSystemIntegrationTests/ConvenienceTests.swift +++ b/Tests/NIOFileSystemIntegrationTests/ConvenienceTests.swift @@ -70,4 +70,31 @@ final class ConvenienceTests: XCTestCase { let bytes = try await ByteBuffer(contentsOf: path, maximumSizeAllowed: .bytes(1024)) XCTAssertEqual(bytes, ByteBuffer(bytes: Array(0..<64))) } + + // MARK: - String + FileSystem + + func testStringFromFullFile() async throws { + let path = try await Self.fs.temporaryFilePath() + try await "some text".write(toFileAt: path) + + let string = try await String(contentsOf: path, maximumSizeAllowed: .bytes(1024)) + XCTAssertEqual(string, "some text") + } + + func testStringFromPartOfAFile() async throws { + let path = try await Self.fs.temporaryFilePath() + try await "some text".write(toFileAt: path) + + await XCTAssertThrowsFileSystemErrorAsync { + try await String(contentsOf: path, maximumSizeAllowed: .bytes(4)) + } + } + + // MARK: - Array + FileSystem + func testArrayFromFullFile() async throws { + let path = try await Self.fs.temporaryFilePath() + try await Array("some text".utf8).write(toFileAt: path) + let array = try await Array(contentsOf: path, maximumSizeAllowed: .bytes(1024)) + XCTAssertEqual(array, Array("some text".utf8)) + } }