-
Notifications
You must be signed in to change notification settings - Fork 145
Expand file tree
/
Copy pathABI.EncodedAttachment.swift
More file actions
133 lines (114 loc) · 4.16 KB
/
ABI.EncodedAttachment.swift
File metadata and controls
133 lines (114 loc) · 4.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//
#if canImport(Foundation)
private import Foundation
#endif
extension ABI {
/// A type implementing the JSON encoding of ``Attachment`` for the ABI entry
/// point and event stream output.
///
/// This type is not part of the public interface of the testing library. It
/// assists in converting values to JSON; clients that consume this JSON are
/// expected to write their own decoders.
struct EncodedAttachment<V>: Sendable where V: ABI.Version {
/// The path where the attachment was written.
var path: String?
/// The preferred name of the attachment.
///
/// - Warning: Attachments' preferred names are not yet part of the JSON
/// schema.
var _preferredName: String?
/// The raw content of the attachment, if available.
///
/// The value of this property is set if the attachment was not first saved
/// to a file. It may also be `nil` if an error occurred while trying to get
/// the original attachment's serialized representation.
///
/// - Warning: Inline attachment content is not yet part of the JSON schema.
var _bytes: Bytes?
init(encoding attachment: borrowing Attachment<AnyAttachable>, in eventContext: borrowing Event.Context) {
path = attachment.fileSystemPath
if V.includesExperimentalFields {
_preferredName = attachment.preferredName
if path == nil {
_bytes = try? attachment.withUnsafeBytes { bytes in
return Bytes(rawValue: [UInt8](bytes))
}
}
}
}
/// A structure representing the bytes of an attachment.
struct Bytes: Sendable, RawRepresentable {
var rawValue: [UInt8]
}
}
}
// MARK: - Codable
extension ABI.EncodedAttachment: Codable {}
extension ABI.EncodedAttachment.Bytes: Codable {
func encode(to encoder: any Encoder) throws {
#if canImport(Foundation)
// If possible, encode this structure as Base64 data.
try rawValue.withUnsafeBytes { rawValue in
let data = Data(bytesNoCopy: .init(mutating: rawValue.baseAddress!), count: rawValue.count, deallocator: .none)
var container = encoder.singleValueContainer()
try container.encode(data)
}
#else
// Otherwise, it's an array of integers.
var container = encoder.singleValueContainer()
try container.encode(rawValue)
#endif
}
init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
#if canImport(Foundation)
// If possible, decode a whole Foundation Data object.
if let data = try? container.decode(Data.self) {
self.init(rawValue: [UInt8](data))
return
}
#endif
// Fall back to trying to decode an array of integers.
let bytes = try container.decode([UInt8].self)
self.init(rawValue: bytes)
}
}
// MARK: - Attachable
extension ABI.EncodedAttachment: Attachable {
var estimatedAttachmentByteCount: Int? {
_bytes?.rawValue.count
}
borrowing func withUnsafeBytes<R>(for attachment: borrowing Attachment<Self>, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R {
if let bytes = _bytes?.rawValue {
return try bytes.withUnsafeBytes(body)
}
#if !SWT_NO_FILE_IO
guard let path else {
throw BytesUnavailableError()
}
#if canImport(Foundation)
// Leverage Foundation's file-mapping logic since we're using Data anyway.
let url = URL(fileURLWithPath: path, isDirectory: false)
let bytes = try Data(contentsOf: url, options: [.mappedIfSafe])
#else
let fileHandle = try FileHandle(forReadingAtPath: path)
let bytes = try fileHandle.readToEnd()
#endif
return try bytes.withUnsafeBytes(body)
#else
// Cannot read the attachment from disk on this platform.
throw BytesUnavailableError()
#endif
}
borrowing func preferredName(for attachment: borrowing Attachment<Self>, basedOn suggestedName: String) -> String {
_preferredName ?? suggestedName
}
}