@@ -115,33 +115,152 @@ public struct RegistryClient {
115115 let urlsession = URLSession ( configuration: . ephemeral)
116116 try await self . init ( registry: registryURL, client: urlsession, auth: auth)
117117 }
118+ }
119+
120+ extension RegistryClient {
121+ /// Represents an operation to be executed on the registry.
122+ struct RegistryOperation {
123+ enum Destination {
124+ case subpath( String ) // Repository subpath on the registry
125+ case url( URL ) // Full destination URL, for example from a Location header returned by the registry
126+ }
127+
128+ var method : HTTPRequest . Method // HTTP method
129+ var repository : String // Repository path on the registry
130+ var destination : Destination // Destination of the operation: can be a subpath or remote URL
131+ var accepting : [ String ] = [ ] // Acceptable response types
132+ var contentType : String ? = nil // Request data type
118133
119- func registryURLForPath( _ path: String ) throws -> URL {
120- var components = URLComponents ( )
121- components. path = path
122- guard let url = components. url ( relativeTo: registryURL) else {
123- throw RegistryClientError . invalidRegistryPath ( path)
134+ func url( relativeTo registry: URL ) -> URL {
135+ switch destination {
136+ case . url( let url) : return url
137+ case . subpath( let path) :
138+ let subpath = registry. appendingPathComponent ( " /v2/ \( repository) / \( path) " )
139+ return subpath
140+ }
141+ }
142+
143+ // Convenience constructors
144+ static func get(
145+ _ repository: String ,
146+ path: String ,
147+ actions: [ String ] ? = nil ,
148+ accepting: [ String ] = [ ] ,
149+ contentType: String ? = nil
150+ ) -> RegistryOperation {
151+ . init(
152+ method: . get,
153+ repository: repository,
154+ destination: . subpath( path) ,
155+ accepting: accepting,
156+ contentType: contentType
157+ )
158+ }
159+
160+ static func get(
161+ _ repository: String ,
162+ url: URL ,
163+ actions: [ String ] ? = nil ,
164+ accepting: [ String ] = [ ] ,
165+ contentType: String ? = nil
166+ ) -> RegistryOperation {
167+ . init(
168+ method: . get,
169+ repository: repository,
170+ destination: . url( url) ,
171+ accepting: accepting,
172+ contentType: contentType
173+ )
174+ }
175+
176+ static func head(
177+ _ repository: String ,
178+ path: String ,
179+ actions: [ String ] ? = nil ,
180+ accepting: [ String ] = [ ] ,
181+ contentType: String ? = nil
182+ ) -> RegistryOperation {
183+ . init(
184+ method: . head,
185+ repository: repository,
186+ destination: . subpath( path) ,
187+ accepting: accepting,
188+ contentType: contentType
189+ )
190+ }
191+
192+ /// This handles the 'put' case where the registry gives us a location URL which we must not alter, aside from adding the digest to it
193+ static func put(
194+ _ repository: String ,
195+ url: URL ,
196+ actions: [ String ] ? = nil ,
197+ accepting: [ String ] = [ ] ,
198+ contentType: String ? = nil
199+ ) -> RegistryOperation {
200+ . init(
201+ method: . put,
202+ repository: repository,
203+ destination: . url( url) ,
204+ accepting: accepting,
205+ contentType: contentType
206+ )
207+ }
208+
209+ static func put(
210+ _ repository: String ,
211+ path: String ,
212+ actions: [ String ] ? = nil ,
213+ accepting: [ String ] = [ ] ,
214+ contentType: String ? = nil
215+ ) -> RegistryOperation {
216+ . init(
217+ method: . put,
218+ repository: repository,
219+ destination: . subpath( path) ,
220+ accepting: accepting,
221+ contentType: contentType
222+ )
223+ }
224+
225+ static func post(
226+ _ repository: String ,
227+ path: String ,
228+ actions: [ String ] ? = nil ,
229+ accepting: [ String ] = [ ] ,
230+ contentType: String ? = nil
231+ ) -> RegistryOperation {
232+ . init(
233+ method: . post,
234+ repository: repository,
235+ destination: . subpath( path) ,
236+ accepting: accepting,
237+ contentType: contentType
238+ )
124239 }
125- return url
126240 }
127- }
128241
129- extension RegistryClient {
130242 /// Execute an HTTP request with no request body.
131243 /// - Parameters:
132- /// - request : The HTTP request to execute.
244+ /// - operation : The Registry operation to execute.
133245 /// - success: The HTTP status code expected if the request is successful.
134246 /// - errors: Expected error codes for which the registry sends structured error messages.
135247 /// - Returns: An asynchronously-delivered tuple that contains the raw response body as a Data instance, and a HTTPURLResponse.
136248 /// - Throws: If the server response is unexpected or indicates that an error occurred.
137249 ///
138250 /// A plain Data version of this function is required because Data is Decodable and decodes from base64.
139251 /// Plain blobs are not encoded in the registry, so trying to decode them will fail.
140- public func executeRequestThrowing(
141- _ request : HTTPRequest ,
252+ func executeRequestThrowing(
253+ _ operation : RegistryOperation ,
142254 expectingStatus success: HTTPResponse . Status = . ok,
143255 decodingErrors errors: [ HTTPResponse . Status ]
144256 ) async throws -> ( data: Data , response: HTTPResponse ) {
257+ let request = HTTPRequest (
258+ method: operation. method,
259+ url: operation. url ( relativeTo: registryURL) ,
260+ accepting: operation. accepting,
261+ contentType: operation. contentType
262+ )
263+
145264 do {
146265 let authenticatedRequest = auth? . auth ( for: request) ?? request
147266 return try await client. executeRequestThrowing ( authenticatedRequest, expectingStatus: success)
@@ -166,8 +285,8 @@ extension RegistryClient {
166285 /// - errors: Expected error codes for which the registry sends structured error messages.
167286 /// - Returns: An asynchronously-delivered tuple that contains the raw response body as a Data instance, and a HTTPURLResponse.
168287 /// - Throws: If the server response is unexpected or indicates that an error occurred.
169- public func executeRequestThrowing< Response: Decodable > (
170- _ request: HTTPRequest ,
288+ func executeRequestThrowing< Response: Decodable > (
289+ _ request: RegistryOperation ,
171290 expectingStatus success: HTTPResponse . Status = . ok,
172291 decodingErrors errors: [ HTTPResponse . Status ]
173292 ) async throws -> ( data: Response , response: HTTPResponse ) {
@@ -182,7 +301,7 @@ extension RegistryClient {
182301
183302 /// Execute an HTTP request uploading a request body.
184303 /// - Parameters:
185- /// - request : The HTTP request to execute.
304+ /// - operation : The Registry operation to execute.
186305 /// - payload: The request body to upload.
187306 /// - success: The HTTP status code expected if the request is successful.
188307 /// - errors: Expected error codes for which the registry sends structured error messages.
@@ -191,12 +310,19 @@ extension RegistryClient {
191310 ///
192311 /// A plain Data version of this function is required because Data is Encodable and encodes to base64.
193312 /// Accidentally encoding data blobs will cause digests to fail and runtimes to be unable to run the images.
194- public func executeRequestThrowing(
195- _ request : HTTPRequest ,
313+ func executeRequestThrowing(
314+ _ operation : RegistryOperation ,
196315 uploading payload: Data ,
197316 expectingStatus success: HTTPResponse . Status ,
198317 decodingErrors errors: [ HTTPResponse . Status ]
199318 ) async throws -> ( data: Data , response: HTTPResponse ) {
319+ let request = HTTPRequest (
320+ method: operation. method,
321+ url: operation. url ( relativeTo: registryURL) ,
322+ accepting: operation. accepting,
323+ contentType: operation. contentType
324+ )
325+
200326 do {
201327 let authenticatedRequest = auth? . auth ( for: request) ?? request
202328 return try await client. executeRequestThrowing (
@@ -224,20 +350,20 @@ extension RegistryClient {
224350
225351 /// Execute an HTTP request uploading a Codable request body.
226352 /// - Parameters:
227- /// - request : The HTTP request to execute.
353+ /// - operation : The Registry operation to execute.
228354 /// - payload: The request body to upload.
229355 /// - success: The HTTP status code expected if the request is successful.
230356 /// - errors: Expected error codes for which the registry sends structured error messages.
231357 /// - Returns: An asynchronously-delivered tuple that contains the raw response body as a Data instance, and a HTTPURLResponse.
232358 /// - Throws: If the server response is unexpected or indicates that an error occurred.
233- public func executeRequestThrowing< Body: Encodable > (
234- _ request : HTTPRequest ,
359+ func executeRequestThrowing< Body: Encodable > (
360+ _ operation : RegistryOperation ,
235361 uploading payload: Body ,
236362 expectingStatus success: HTTPResponse . Status ,
237363 decodingErrors errors: [ HTTPResponse . Status ]
238364 ) async throws -> ( data: Data , response: HTTPResponse ) {
239365 try await executeRequestThrowing (
240- request ,
366+ operation ,
241367 uploading: try encoder. encode ( payload) ,
242368 expectingStatus: success,
243369 decodingErrors: errors
0 commit comments