|  | 
|  | 1 | +# Consolidate Swift Testing's image attachments API across platforms | 
|  | 2 | + | 
|  | 3 | +* Proposal: [ST-0017](0017-image-attachment-consolidation.md) | 
|  | 4 | +* Authors: [Jonathan Grynspan](https://github.com/grynspan) | 
|  | 5 | +* Review Manager: [Rachel Brindle](https://github.com/younata) | 
|  | 6 | +* Status: **Active Review (October 22-30, 2025)** | 
|  | 7 | +* Implementation: [swiftlang/swift-testing#1359](https://github.com/swiftlang/swift-testing/pull/1359) | 
|  | 8 | +* Review: ([pitch](https://forums.swift.org/t/pitch-adjustments-to-image-attachments-in-swift-testing/82581), Review TBA) | 
|  | 9 | + | 
|  | 10 | +## Introduction | 
|  | 11 | + | 
|  | 12 | +This proposal includes a small number of adjustments to the API surface of Swift | 
|  | 13 | +Testing's image attachments feature introduced in [ST-0014](0014-image-attachments-in-swift-testing-apple-platforms.md) | 
|  | 14 | +and [ST-0015](0015-image-attachments-in-swift-testing-windows.md). | 
|  | 15 | + | 
|  | 16 | +## Motivation | 
|  | 17 | + | 
|  | 18 | +These changes will help to align the platform-specific interfaces of the feature | 
|  | 19 | +more closely. | 
|  | 20 | + | 
|  | 21 | +## Proposed solution | 
|  | 22 | + | 
|  | 23 | +The `AttachableAsCGImage` and `AttachableAsIWICBitmapSource` protocols are | 
|  | 24 | +combined into a single protocol, `AttachableAsImage` with adjusted protocol | 
|  | 25 | +requirements; a change is made to `AttachableImageFormat` to more closely | 
|  | 26 | +align its interface between Darwin and Windows; `AttachableImageFormat` is made | 
|  | 27 | +to conform to `Equatable` and `Hashable`; and an additional property is added to | 
|  | 28 | +`Attachment` to query its image format. | 
|  | 29 | + | 
|  | 30 | +## Detailed design | 
|  | 31 | + | 
|  | 32 | +The following changes are proposed: | 
|  | 33 | + | 
|  | 34 | +### Combining AttachableAsCGImage and AttachableAsIWICBitmapSource | 
|  | 35 | + | 
|  | 36 | +The `AttachableAsCGImage` and `AttachableAsIWICBitmapSource` protocols are | 
|  | 37 | +combined into a single protocol, `AttachableAsImage`. | 
|  | 38 | + | 
|  | 39 | +These platform-specific requirements are removed: | 
|  | 40 | + | 
|  | 41 | +```diff | 
|  | 42 | +- var attachableCGImage: CGImage { get throws } | 
|  | 43 | +- func copyAttachableIWICBitmapSource() throws -> UnsafeMutablePointer<IWICBitmapSource> | 
|  | 44 | +``` | 
|  | 45 | + | 
|  | 46 | +They are replaced with a new requirement that encapsulates the image encoding | 
|  | 47 | +operation. This requirement is implemented by the CoreGraphics and WinSDK | 
|  | 48 | +overlays and is made publicly available for test authors who wish to declare | 
|  | 49 | +additional conformances to this protocol for types that are not based on | 
|  | 50 | +`CGImage` or `IWICBitmapSource`: | 
|  | 51 | + | 
|  | 52 | +```swift | 
|  | 53 | +public protocol AttachableAsImage { | 
|  | 54 | +  // ... | 
|  | 55 | +   | 
|  | 56 | +  /// Encode a representation of this image in a given image format. | 
|  | 57 | +  /// | 
|  | 58 | +  /// - Parameters: | 
|  | 59 | +  ///   - imageFormat: The image format to use when encoding this image. | 
|  | 60 | +  ///   - body: A function to call. A temporary buffer containing a data | 
|  | 61 | +  ///     representation of this instance is passed to it. | 
|  | 62 | +  /// | 
|  | 63 | +  /// - Returns: Whatever is returned by `body`. | 
|  | 64 | +  /// | 
|  | 65 | +  /// - Throws: Whatever is thrown by `body`, or any error that prevented the | 
|  | 66 | +  ///   creation of the buffer. | 
|  | 67 | +  /// | 
|  | 68 | +  /// The testing library uses this function when saving an image as an | 
|  | 69 | +  /// attachment. The implementation should use `imageFormat` to determine what | 
|  | 70 | +  /// encoder to use. | 
|  | 71 | +  borrowing func withUnsafeBytes<R>(as imageFormat: AttachableImageFormat, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R | 
|  | 72 | +} | 
|  | 73 | +``` | 
|  | 74 | + | 
|  | 75 | +If a developer has an image type that should conform to `AttachableAsImage` and | 
|  | 76 | +wraps an instance of `CGImage` or `IWICBitmapSource`, it is straightforward for | 
|  | 77 | +them to delegate to that object. For example: | 
|  | 78 | + | 
|  | 79 | +```swift | 
|  | 80 | +import Testing | 
|  | 81 | +import CoreGraphics | 
|  | 82 | + | 
|  | 83 | +struct MyImage { | 
|  | 84 | +  var cgImage: CGImage | 
|  | 85 | +  // ... | 
|  | 86 | +} | 
|  | 87 | + | 
|  | 88 | +extension MyImage: AttachableAsImage { | 
|  | 89 | +  func withUnsafeBytes<R>(as imageFormat: AttachableImageFormat, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { | 
|  | 90 | +    try cgImage.withUnsafeBytes(as: imageFormat, body) | 
|  | 91 | +  } | 
|  | 92 | +} | 
|  | 93 | +``` | 
|  | 94 | + | 
|  | 95 | +### Adjusting AttachableImageFormat | 
|  | 96 | + | 
|  | 97 | +The following Apple-specific `AttachableImageFormat` initializer is renamed so | 
|  | 98 | +that its first argument has an explicit label: | 
|  | 99 | + | 
|  | 100 | +```diff | 
|  | 101 | + public struct AttachableImageFormat { | 
|  | 102 | +   // ... | 
|  | 103 | +-  public init(_ contentType: UTType, encodingQuality: Float = 1.0) | 
|  | 104 | ++  public init(contentType: UTType, encodingQuality: Float = 1.0) | 
|  | 105 | + } | 
|  | 106 | +``` | 
|  | 107 | + | 
|  | 108 | +This change makes the type's interface more consistent between Darwin and | 
|  | 109 | +Windows (where it has an `init(encoderCLSID:encodingQuality:)` initializer.) | 
|  | 110 | + | 
|  | 111 | +As well, conformances to `Equatable`, `Hashable`, `CustomStringConvertible`, and | 
|  | 112 | +`CustomDebugStringConvertible` are added: | 
|  | 113 | + | 
|  | 114 | +```swift | 
|  | 115 | +extension AttachableImageFormat: Equatable, Hashable {} | 
|  | 116 | +extension AttachableImageFormat: CustomStringConvertible, CustomDebugStringConvertible {} | 
|  | 117 | +``` | 
|  | 118 | + | 
|  | 119 | +Conformance to `Equatable` is necessary to correctly implement the | 
|  | 120 | +`withUnsafeBytes(as:_:)` protocol requirement mentioned above, and conformance | 
|  | 121 | +to `Hashable` is generally useful and straightforward to implement. Conformance | 
|  | 122 | +to `CustomStringConvertible` and `CustomDebugStringConvertible` allows for | 
|  | 123 | +better diagnostic output (especially if an encoding failure occurs.) | 
|  | 124 | + | 
|  | 125 | +### Adding an imageFormat property to Attachment | 
|  | 126 | + | 
|  | 127 | +The following property is added to `Attachment` when the attachable value is an | 
|  | 128 | +image: | 
|  | 129 | + | 
|  | 130 | +```swift | 
|  | 131 | +extension Attachment where AttachableValue: AttachableWrapper, | 
|  | 132 | +                           AttachableValue.Wrapped: AttachableAsImage { | 
|  | 133 | +  /// The image format to use when encoding the represented image. | 
|  | 134 | +  public var imageFormat: AttachableImageFormat? { get } | 
|  | 135 | +} | 
|  | 136 | +``` | 
|  | 137 | + | 
|  | 138 | +## Source compatibility | 
|  | 139 | + | 
|  | 140 | +These changes are breaking for anyone who has created a type that conforms to | 
|  | 141 | +either `AttachableAsCGImage` or `AttachableAsIWICBitmapSource`, or anyone who | 
|  | 142 | +has adopted `AttachableImageFormat.init(_:encodingQuality:)`. | 
|  | 143 | + | 
|  | 144 | +This feature is new in Swift 6.3 and has not shipped to developers outside of | 
|  | 145 | +nightly toolchain builds. As such, we feel confident that any real-world impact | 
|  | 146 | +to developers will be both minimal and manageable. | 
|  | 147 | + | 
|  | 148 | +## Integration with supporting tools | 
|  | 149 | + | 
|  | 150 | +No changes. | 
|  | 151 | + | 
|  | 152 | +## Future directions | 
|  | 153 | + | 
|  | 154 | +N/A | 
|  | 155 | + | 
|  | 156 | +## Alternatives considered | 
|  | 157 | + | 
|  | 158 | +- Leaving the two protocols separate. Combining them allows us to lower more | 
|  | 159 | +  code into the main Swift Testing library and improves our ability to generate | 
|  | 160 | +  DocC documentation, while also simplifying the story for developers who want | 
|  | 161 | +  to use this feature across platforms. | 
|  | 162 | + | 
|  | 163 | +## Acknowledgments | 
|  | 164 | + | 
|  | 165 | +Thanks to my colleagues for their feedback on the image attachments feature and | 
|  | 166 | +to the Swift community for putting up with the churn! | 
0 commit comments