diff --git a/Sources/GoogleCloud/Storage/Bucket.swift b/Sources/GoogleCloud/Storage/Bucket.swift index aa68ee2..6283396 100644 --- a/Sources/GoogleCloud/Storage/Bucket.swift +++ b/Sources/GoogleCloud/Storage/Bucket.swift @@ -12,7 +12,7 @@ public struct Bucket: Sendable { self.authorizedClient = authorizedClient } - // INFO: https://cloud.google.com/storage/docs/json_api/v1/objects/list + // INFO: https://docs.cloud.google.com/storage/docs/json_api/v1/objects/list public func files( prefix: String, logger: Logger? = nil @@ -30,7 +30,24 @@ public struct Bucket: Sendable { ).items ?? [] } - // INFO: https://cloud.google.com/storage/docs/json_api/v1/objects/delete + // INFO: https://docs.cloud.google.com/storage/docs/json_api/v1/objects/move + @discardableResult + public func move( + src: String, + dst: String, + logger: Logger? = nil + ) async throws -> StorageFile { + let src = Self.nameOnPath(name: src) + let dst = Self.nameOnPath(name: dst) + return try await authorizedClient.execute( + method: .POST, + path: "storage/v1/b/\(bucketName)/o/\(src)/moveTo/o/\(dst)", + logger: logger, + responseType: StorageFile.self + ) + } + + // INFO: https://docs.cloud.google.com/storage/docs/json_api/v1/objects/delete public func delete( name: String, logger: Logger? = nil @@ -38,7 +55,7 @@ public struct Bucket: Sendable { let name = Self.nameOnPath(name: name) _ = try await authorizedClient.execute( method: .DELETE, - path: "storage/v1/b/\(bucketName)/o" + (name.hasPrefix("/") ? name : "/" + name), + path: "storage/v1/b/\(bucketName)/o/\(name)", logger: logger ) } @@ -75,7 +92,9 @@ public struct Bucket: Sendable { // INFO: https://cloud.google.com/storage/docs/request-endpoints#encoding static func nameOnPath(name: String) -> String { let targets = CharacterSet(charactersIn: " !#$&'()*+,/:;=?@[]") - return name.addingPercentEncoding(withAllowedCharacters: targets.inverted)! + return name + .choppingSlashPrefix + .addingPercentEncoding(withAllowedCharacters: targets.inverted)! } } diff --git a/Sources/GoogleCloud/Storage/Entity/StorageFile.swift b/Sources/GoogleCloud/Storage/Entity/StorageFile.swift index 749efdb..1a79271 100644 --- a/Sources/GoogleCloud/Storage/Entity/StorageFile.swift +++ b/Sources/GoogleCloud/Storage/Entity/StorageFile.swift @@ -1,6 +1,6 @@ import Foundation -/// INFO: https://cloud.google.com/storage/docs/json_api/v1/objects +/// INFO: https://docs.cloud.google.com/storage/docs/json_api/v1/objects#resource /// varとletは↑のwritableかどうかに対応してるけど、意味はわかっていない public struct StorageFile: Decodable { public init(name: String, bucket: String) { diff --git a/Sources/GoogleCloudBase/AuthorizedClient.swift b/Sources/GoogleCloudBase/AuthorizedClient.swift index 099658c..0b5714b 100644 --- a/Sources/GoogleCloudBase/AuthorizedClient.swift +++ b/Sources/GoogleCloudBase/AuthorizedClient.swift @@ -77,9 +77,10 @@ package struct AuthorizedClient: Sendable { if let data = resBody.getData(at: resBody.readerIndex, length: resBody.readableBytes), let string = String(data: data, encoding: .utf8) { // 404のときなど、JSONでないレスポンスが返ることがある - throw ErrorResponse(error: .init(code: Int(res.status.code), message: string)) + errorResponse = ErrorResponse(error: .init(code: Int(res.status.code), message: string)) + } else { + throw error } - throw error } throw errorResponse } diff --git a/Sources/GoogleCloudBase/GCPClient.swift b/Sources/GoogleCloudBase/GCPClient.swift index adb647d..fb9afe8 100644 --- a/Sources/GoogleCloudBase/GCPClient.swift +++ b/Sources/GoogleCloudBase/GCPClient.swift @@ -56,7 +56,7 @@ public struct GCPClient: Sendable { public init( requestLogLevel: Logger.Level = .debug, - errorLogLevel: Logger.Level = .debug + errorLogLevel: Logger.Level = .error ) { self.requestLogLevel = requestLogLevel self.errorLogLevel = errorLogLevel diff --git a/Tests/SharedTests/Storage/BucketTest.swift b/Tests/SharedTests/Storage/BucketTest.swift index 774eed9..0fa7ec0 100644 --- a/Tests/SharedTests/Storage/BucketTest.swift +++ b/Tests/SharedTests/Storage/BucketTest.swift @@ -19,6 +19,26 @@ import Testing #expect(Set(response.map(\.name)) == ["testFiles/bar.png", "testFiles/baz.jpg"]) } + @Test(.disabled("fake-gcs-server doesn't support 'move'.")) + func move() async throws { + let bucket = makeBucket() + + let srcName = "testMove/src/Foo.txt" + let dstName = "testMove/dst/Foo_renamed.txt" + _ = try await bucket.uploadSimple(name: srcName, media: Data(#function.utf8)) + + let filesBefore = try await bucket.files(prefix: srcName) + #expect(!filesBefore.isEmpty) + + let movedFile = try await bucket.move(src: srcName, dst: dstName) + #expect(movedFile.name == dstName) + + let filesAfterSrc = try await bucket.files(prefix: srcName) + #expect(filesAfterSrc.isEmpty) + let filesAfterDst = try await bucket.files(prefix: dstName) + #expect(!filesAfterDst.isEmpty) + } + @Test func upload() async throws { let bucket = makeBucket()