Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
tags:
- 'v*'
- '[0-9]*'
workflow_dispatch:

jobs:

Expand All @@ -13,4 +14,4 @@ jobs:
permissions:
contents: read
pages: write
id-token: write
id-token: write
31 changes: 31 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Pull request

on:
pull_request:
branches:
- main

jobs:

soundness:
name: Soundness
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@0.0.15
with:
license_header_check_project_name: "project"
format_check_enabled : true
broken_symlink_check_enabled : true
unacceptable_language_check_enabled : true
shell_check_enabled : true
docs_check_enabled : false
api_breakage_check_enabled : false
license_header_check_enabled : false
yamllint_check_enabled : false
python_lint_check_enabled : false

tests:
name: Tests
uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@0.0.15
with:
enable_windows_checks : false
linux_build_command: "swift test --parallel --enable-code-coverage"
linux_swift_versions: '["6.3", "6.4"]'
39 changes: 0 additions & 39 deletions .github/workflows/testing.yml

This file was deleted.

18 changes: 9 additions & 9 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 15 additions & 20 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
// swift-tools-version:6.1
// swift-tools-version:6.3
import PackageDescription

// NOTE: https://github.com/swift-server/swift-http-server/blob/main/Package.swift
var defaultSwiftSettings: [SwiftSetting] =
[
// https://github.com/swiftlang/swift-evolution/blob/main/proposals/0441-formalize-language-mode-terminology.md
let swiftSettings: [SwiftSetting] = [
.swiftLanguageMode(.v6),
// https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md
.strictMemorySafety(),
.treatAllWarnings(as: .error),
.enableUpcomingFeature("InternalImportsByDefault"),
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("MemberImportVisibility"),
// https://forums.swift.org/t/experimental-support-for-lifetime-dependencies-in-swift-6-2-and-beyond/78638
.enableUpcomingFeature("InferIsolatedConformances"),
.enableUpcomingFeature("NonisolatedNonsendingByDefault"),
.enableUpcomingFeature("ImmutableWeakCaptures"),
.enableExperimentalFeature("SuppressedAssociatedTypes"),
.enableExperimentalFeature("LifetimeDependence"),
.enableExperimentalFeature("Lifetimes"),
// https://github.com/swiftlang/swift/pull/65218
.enableExperimentalFeature("AvailabilityMacro=featherStorage 1.0:macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0"),
.enableUpcomingFeature("StrictConcurrency"),
]

#if compiler(>=6.2)
defaultSwiftSettings.append(
// https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md
.enableUpcomingFeature("NonisolatedNonsendingByDefault")
)
#endif

let package = Package(
name: "feather-storage",
platforms: [
Expand All @@ -34,24 +30,23 @@ let package = Package(
.library(name: "FeatherStorage", targets: ["FeatherStorage"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio", from: "2.9.0"),
// [docc-plugin-placeholder]
.package(url: "https://github.com/apple/swift-nio", from: "2.100.0"),
],
targets: [
.target(
name: "FeatherStorage",
dependencies: [
.product(name: "NIOCore", package: "swift-nio"),
],
swiftSettings: defaultSwiftSettings
swiftSettings: swiftSettings
),
.testTarget(
name: "FeatherStorageTests",
dependencies: [
.target(name: "FeatherStorage"),
.product(name: "NIOFoundationCompat", package: "swift-nio"),
],
swiftSettings: defaultSwiftSettings
swiftSettings: swiftSettings
),
]
)
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
Abstract storage component, providing a shared API surface for file storage drivers written in Swift.

[
![Release: 1.0.0-beta.2](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E2-F05138)
![Release: 1.0.0-rc.1](https://img.shields.io/badge/Release-1%2E0%2E0--rc%2E1-F05138)
](
https://github.com/feather-framework/feather-storage/releases/tag/1.0.0-beta.2
https://github.com/feather-framework/feather-storage/releases/tag/1.0.0-rc.1
)

## Features
Expand All @@ -17,10 +17,10 @@ Abstract storage component, providing a shared API surface for file storage driv

## Requirements

![Swift 6.1+](https://img.shields.io/badge/Swift-6%2E1%2B-F05138)
![Swift 6.3+](https://img.shields.io/badge/Swift-6%2E3%2B-F05138)
![Platforms: Linux, macOS, iOS, tvOS, watchOS, visionOS](https://img.shields.io/badge/Platforms-Linux_%7C_macOS_%7C_iOS_%7C_tvOS_%7C_watchOS_%7C_visionOS-F05138)

- Swift 6.1+
- Swift 6.3+

- Platforms:
- Linux
Expand All @@ -35,7 +35,7 @@ Abstract storage component, providing a shared API surface for file storage driv
Use Swift Package Manager; add the dependency to your `Package.swift` file:

```swift
.package(url: "https://github.com/feather-framework/feather-storage", exact: "1.0.0-beta.2"),
.package(url: "https://github.com/feather-framework/feather-storage", exact: "1.0.0-rc.1"),
```

Then add `FeatherStorage` to your target dependencies:
Expand Down
2 changes: 1 addition & 1 deletion Sources/FeatherStorage/ByteBufferSequence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//
// Created by Tibor Bödecs on 2023. 01. 16.

import NIOCore
public import NIOCore

/// An async sequence that streams a `ByteBuffer` in fixed-size chunks.
public struct ByteBufferSequence: AsyncSequence, Sendable {
Expand Down
19 changes: 18 additions & 1 deletion Sources/FeatherStorage/StorageClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ public protocol StorageClient: Sendable {
/// - Parameters:
/// - key: The destination object key.
/// - sequence: The source byte stream to upload.
/// - contentType: Optional MIME type stored with the object metadata.
/// - Throws: `StorageClientError` if the upload fails.
func upload(
key: String,
sequence: StorageSequence
sequence: StorageSequence,
contentType: String?
) async throws(StorageClientError)

/// Downloads an object as an asynchronous byte stream.
Expand Down Expand Up @@ -136,6 +138,21 @@ public protocol StorageClient: Sendable {

extension StorageClient {

/// Uploads an object without additional metadata.
///
/// This convenience preserves the original storage API for callers that
/// do not need to provide object metadata.
public func upload(
key: String,
sequence: StorageSequence
) async throws(StorageClientError) {
try await upload(
key: key,
sequence: sequence,
contentType: nil
)
}

/// Moves an object from one key to another by copying then deleting the source.
///
/// - Parameters:
Expand Down
2 changes: 1 addition & 1 deletion Sources/FeatherStorage/StorageClientError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ public enum StorageClientError: Error {
/// A multipart chunk descriptor is invalid.
case invalidMultipartChunk
/// Driver-specific underlying failure.
case unknown(Error)
case unknown(any Error)
}
2 changes: 1 addition & 1 deletion Sources/FeatherStorage/StorageSequence.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//
// Created by Tibor Bodecs on 2023. 01. 16.

import NIOCore
public import NIOCore

/// A type-erased async sequence of storage byte buffers.
public struct StorageSequence: Sendable, AsyncSequence {
Expand Down
17 changes: 17 additions & 0 deletions Tests/FeatherStorageTests/FeatherStorageTestSuite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@ import Testing
@Suite
struct FeatherStorageTestSuite {

@Test
func storageClientUploadPreservesContentType() async throws {
let client = MockStorageClient()
let sequence = StorageSequence(
asyncSequence: [1, 2, 3].byteBufferAsync,
length: 3
)

try await client.upload(
key: "image.webp",
sequence: sequence,
contentType: "image/webp"
)

#expect(await client.contentType(for: "image.webp") == "image/webp")
}

@Test
func storageClientMoveDeletesSourceOnSuccess() async throws {
let client = MockStorageClient()
Expand Down
28 changes: 23 additions & 5 deletions Tests/FeatherStorageTests/Mock/MockStorageClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,18 @@ struct MockStorageClient: StorageClient {

func setObject(
key: String,
values: [Int]
values: [Int],
contentType: String? = nil
) async {
await state.setObject(key, values: values)
await state.setObject(
key,
values: values,
contentType: contentType
)
}

func contentType(for key: String) async -> String? {
await state.objectContentType(for: key)
}

func multipartExists(
Expand All @@ -53,7 +62,8 @@ struct MockStorageClient: StorageClient {

func upload(
key: String,
sequence: StorageSequence
sequence: StorageSequence,
contentType: String?
) async throws(StorageClientError) {
let values: [Int]
do {
Expand All @@ -62,7 +72,11 @@ struct MockStorageClient: StorageClient {
catch {
throw .unknown(error)
}
await state.setObject(key, values: values)
await state.setObject(
key,
values: values,
contentType: contentType
)
}

func download(
Expand Down Expand Up @@ -104,7 +118,11 @@ struct MockStorageClient: StorageClient {
guard let values = await state.object(for: source) else {
throw .invalidKey
}
await state.setObject(destination, values: values)
await state.setObject(
destination,
values: values,
contentType: await state.objectContentType(for: source)
)
}

func list(
Expand Down
Loading
Loading