1414// limitations under the License.
1515//===----------------------------------------------------------------------===//
1616
17+ import ContainerizationError
1718import Foundation
1819import SystemPackage
1920
2021/// Represents a socket that should be published from container to host.
22+ ///
23+ /// - Deprecated: New for 1.0.0, path types changed from `URL` to `FilePath`.
24+ /// - Note: Decoder handles `FilePath` and `URL` for persistent data compatibility;
25+ /// this compatibility will be removed in a later release.
2126public struct PublishSocket : Sendable , Codable {
22- /// The path to the socket in the container.
27+ /// Absolute path to the socket inside the container.
2328 public var containerPath : FilePath
2429
25- /// The path where the socket should appear on the host.
30+ /// Absolute path where the socket appears on the host.
2631 public var hostPath : FilePath
2732
2833 /// File permissions for the socket on the host.
2934 public var permissions : FilePermissions ?
3035
36+ /// Creates a `PublishSocket` with validated absolute paths.
37+ ///
38+ /// - Parameters:
39+ /// - containerPath: Absolute path to the socket inside the container.
40+ /// Must begin with `/`.
41+ /// - hostPath: Absolute path where the socket appears on the host.
42+ /// Must begin with `/`.
43+ /// - permissions: File permissions applied to the socket on the host.
44+ /// - Throws: `ContainerizationError` with code `.invalidArgument` if
45+ /// either path is not absolute.
3146 public init (
3247 containerPath: FilePath ,
3348 hostPath: FilePath ,
3449 permissions: FilePermissions ? = nil
35- ) {
50+ ) throws {
51+ guard containerPath. isAbsolute else {
52+ throw ContainerizationError (
53+ . invalidArgument,
54+ message: " containerPath must be absolute: \( containerPath) "
55+ )
56+ }
57+ guard hostPath. isAbsolute else {
58+ throw ContainerizationError (
59+ . invalidArgument,
60+ message: " hostPath must be absolute: \( hostPath) "
61+ )
62+ }
3663 self . containerPath = containerPath
3764 self . hostPath = hostPath
3865 self . permissions = permissions
@@ -44,41 +71,45 @@ public struct PublishSocket: Sendable, Codable {
4471 case permissions
4572 }
4673
47- /// Encode paths as file-URL absolute strings (e.g. `"file:// /var/run/docker.sock"`).
74+ /// Encodes each path as its plain absolute string (e.g. `"/var/run/docker.sock"`).
4875 ///
49- /// These fields were previously typed `URL`; `JSONEncoder` special-cases
50- /// `URL` to emit `absoluteString`. `FilePath`'s synthesized `Codable`
51- /// would instead emit a keyed container (`{"_storage": "..."}`), changing
52- /// the on-disk and XPC wire format. We therefore encode each path as the
53- /// equivalent `URL.absoluteString` so the byte form remains compatible
54- /// with persisted bundles and any readers (e.g. an older service binary)
55- /// that still decode these fields as `URL`.
76+ /// Pre-1.0 wire-format change from the prior `URL`-typed encoding which
77+ /// emitted `URL.absoluteString` (`"file:///var/run/docker.sock"`). The
78+ /// decoder accepts both forms for compatibility with persisted bundles
79+ /// from earlier releases; that compatibility will be removed in a later
80+ /// release.
5681 public func encode( to encoder: any Encoder ) throws {
5782 var container = encoder. container ( keyedBy: CodingKeys . self)
58- try container. encode ( Self . encodePath ( containerPath) , forKey: . containerPath)
59- try container. encode ( Self . encodePath ( hostPath) , forKey: . hostPath)
83+ try container. encode ( containerPath. string , forKey: . containerPath)
84+ try container. encode ( hostPath. string , forKey: . hostPath)
6085 try container. encodeIfPresent ( permissions, forKey: . permissions)
6186 }
6287
6388 public init ( from decoder: any Decoder ) throws {
6489 let container = try decoder. container ( keyedBy: CodingKeys . self)
65- self . containerPath = try Self . decodePath ( from: container, forKey: . containerPath)
66- self . hostPath = try Self . decodePath ( from: container, forKey: . hostPath)
67- self . permissions = try container. decodeIfPresent ( FilePermissions . self, forKey: . permissions)
68- }
69-
70- /// Encode a `FilePath` as a file-URL `absoluteString` to match the prior
71- /// `URL`-typed wire format byte-for-byte.
72- private static func encodePath( _ path: FilePath ) -> String {
73- URL ( filePath: path. string) . absoluteString
90+ let containerPath = try Self . decodePath ( from: container, forKey: . containerPath)
91+ let hostPath = try Self . decodePath ( from: container, forKey: . hostPath)
92+ let permissions = try container. decodeIfPresent ( FilePermissions . self, forKey: . permissions)
93+ do {
94+ try self . init (
95+ containerPath: containerPath,
96+ hostPath: hostPath,
97+ permissions: permissions
98+ )
99+ } catch let error as ContainerizationError {
100+ throw DecodingError . dataCorruptedError (
101+ forKey: . containerPath,
102+ in: container,
103+ debugDescription: String ( describing: error)
104+ )
105+ }
74106 }
75107
76- /// Decode a `FilePath` from either the canonical file-URL form
77- /// (e.g. `"file:///foo"`) emitted by `encodePath(_:)` and the legacy
78- /// `URL`-typed wire format, or a plain absolute path string. Throws
79- /// `DecodingError.dataCorrupted` on malformed, empty, or non-absolute
80- /// inputs so corrupt persisted state fails loudly rather than silently
81- /// producing an invalid socket path.
108+ /// Decodes a `FilePath` accepting either the new plain-path form
109+ /// (`"/var/run/docker.sock"`) or the legacy file-URL form emitted by
110+ /// older releases (`"file:///var/run/docker.sock"`). Throws
111+ /// `DecodingError.dataCorrupted` on a malformed file URL or empty input.
112+ /// Absoluteness is enforced in `init(containerPath:hostPath:permissions:)`.
82113 private static func decodePath(
83114 from container: KeyedDecodingContainer < CodingKeys > ,
84115 forKey key: CodingKeys
@@ -106,11 +137,11 @@ public struct PublishSocket: Sendable, Codable {
106137 path = raw
107138 }
108139
109- guard path. hasPrefix ( " / " ) else {
140+ guard ! path. isEmpty else {
110141 throw DecodingError . dataCorruptedError (
111142 forKey: key,
112143 in: container,
113- debugDescription: " socket path must be absolute : \( raw) "
144+ debugDescription: " decoded socket path is empty : \( raw) "
114145 )
115146 }
116147
0 commit comments