Swift Package Registry Service

Detailed design

Notations

The following terminology and conventions are used in this document.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

API endpoints that accept parameters in their path are expressed by Uniform Resource Identifier (URI) templates, as described in RFC 6570.

Definitions

The following terms, as used in this document, have the meanings indicated.

  • Package: A named collection of Swift source code that is organized into one or more modules according to a Package.swift manifest file.
  • Release: The state of a package after applying a particular set of changes that is uniquely identified by an assigned version number.
  • Version Number: An identifier for a package release in accordance with the Semantic Versioning Specification (SemVer).

Conventions

This document uses the following conventions in its description of client-server interactions.

Application layer protocols

A client and server MUST communicate over a secured connection using Transport Layer Security (TLS) with the https URI scheme.

The use of HTTP 1.1 in examples is non-normative. A client and server MAY communicate according to this specification using any version of the HTTP protocol.

Authentication

A server SHOULD require authentication for client requests to publish or unpublish a package release. A server MAY require authentication for client requests to access information about packages and package releases.

A server SHOULD respond with a status code of 401 (Unauthorized) if a client sends a request to an endpoint that requires authentication without providing credentials. A server MAY respond with a status code of 404 (Not Found) or 403 (Forbidden) when a client provides valid credentials but isn't authorized to access the requested resource.

A server MAY use any authentication model of its choosing. However, use of a scoped, revocable authorization framework like OAuth 2.0 is RECOMMENDED.

Error handling

A server SHOULD communicate any errors to the client using "problem details" objects, as described by RFC 7807. For example, a client sends a request to create a package release with an invalid tag parameter and receives the following response:

HTTP/1.1 404
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "tag '2.0.0' not found"
}

Rate limiting

A server MAY limit the number of requests made by a client by responding with a status code of 429 (Too Many Requests).

HTTP/1.1 429
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en
Retry-After: 60

{
   "detail": "try again in 60 seconds"
}

A client SHOULD follow the guidance of any Retry-After header values provided in responses to prevent overwhelming a server with retry requests. It is RECOMMENDED for clients to introduce randomness in their retry logic to avoid a thundering herd effect.

API versioning

Package registry APIs are versioned.

API version numbers are designated by decimal integers. The accepted version of this proposal constitutes the initial version, 1. Subsequent revisions SHOULD be numbered sequentially (2, 3, and so on).

API version numbers SHOULD follow Semantic Versioning conventions for major releases. Non-breaking changes, such as adding new endpoints, adding new optional parameters to existing endpoints, or adding new information to existing endpoints in a backward-compatible way, SHOULD NOT require a new version. Breaking changes, such as removing or changing an existing endpoint in a backward-incompatible way, MUST correspond to a new version.

A client SHOULD set the Accept-Version header field to specify the API version of a request.

GET /mona/LinkedList/list HTTP/1.1
Accept: application/json
Accept-Version: 1

A server SHALL set the Content-Version header field with the API version number of the response.

HTTP/1.1 200 OK
Content-Type: application/json
Content-Version: 1

If a client sends a request without an Accept-Version header, a server MAY either return 400 Bad Request or process the request using an API version that it chooses, making sure to set the Content-Version header field accordingly.

If a client sends a request with an Accept-Version header that specifies an unknown or invalid API version, a server SHOULD respond with a status code of 400 (Bad Request).

HTTP/1.1 400 Bad Request
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "invalid API version"
}

If a client sends a request with an Accept-Version header that specifies a valid but unsupported API version, a server SHOULD respond with a status code of 415 (Unsupported Media Type).

HTTP/1.1 415 Unsupported Media Type
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "unsupported API version"
}

Namespace and package name resolution

A namespace consists of zero or more path components preceding a package name and is used to distinguish between packages with the same name.

For instance:

  • A registry that qualifies package names with the username of the person or organization responsible for the package (for example, mona/LinkedList)
  • A registry that qualifies package names with a username and domain of the source host (for example, github.com/mona/LinkedList)
  • A registry that doesn't qualify package names, creating an implicit, global namespace for the domain (for example, LinkedList)

Namespaces and package names are both case-insensitive (for example, monaMONA) and normalization-insensitive (for example, n + ◌̃ñ).

A server MAY normalize or disallow characters in a namespace or package name according to the security considerations of Internationalized Resource Identifiers (IRIs) discussed in RFC 3987 and Unicode Technical Report #36.

Endpoints

A server MUST respond to the following endpoints:

Link Method Path Description
[1] GET {/namespace*}/{package} List package releases
[2] GET {/namespace*}/{package}/{version} Fetch a package release
[3] PUT {/namespace*}/{package}/{version} Publish a package release
[4] DELETE {/namespace*}/{package}/{version} Unpublish a package release

List package releases

A client MAY send a GET request for a URI matching the expression {/namespace*}/{package} to retrieve a list of the available releases for a particular package. A client SHOULD set the Accept header with the application/json content type and MAY append the .json extension to the requested URI.

GET /mona/LinkedList HTTP/1.1
Accept-Version: 1
Accept: application/json

A server SHOULD respond with a JSON document containing all of the releases for the requested package.

HTTP/1.1 200 OK
Content-Type: application/json
Content-Version: 1
Link: <https://swift.pkg.github.com/mona/LinkedList/1.1.1>; rel="latest-version",
      <https://github.com/sponsors/mona>; rel="payment"

{
    "releases": {
        "1.1.1": {
            "url": "https://swift.pkg.github.com/mona/LinkedList/1.1.1"
        },
        "1.1.0": {
            "url": "https://swift.pkg.github.com/mona/LinkedList/1.1.0",
            "problem": {
                "status": 410,
                "title": "Gone",
                "detail": "this release was removed from the registry"
            }
        },
        "1.0.0": {
            "url": "https://swift.pkg.github.com/mona/LinkedList/1.0.0"
        }
    }
}

The response body SHALL contain a JSON object nested at a top-level releases key, whose keys are version numbers for releases and values are objects containing the following fields:

Key Type Description Requirement Level
url String A URI for the release REQUIRED
problem Object A problem details object. OPTIONAL

A server SHOULD communicate the unavailability of a package release using a "problem details" object. A client SHOULD consider any releases with an associated problem to be unavailable for the purposes of package resolution.

A server SHOULD list releases in order of precedence, starting with the latest version. However, a client SHOULD NOT assume any specific ordering of version numbers in the response.

A server SHOULD respond with a link to the latest published release of the package if one exists, using a Link header field with a latest-version relation. A server MAY respond with additional Link entries, such as one with a payment relation for sponsoring a package maintainer.

Fetch a package release

A client MAY send a GET request for a URI matching the expression {/namespace*}/{package}/{version} to fetch a package release.

Source archive

A client MAY send a GET request for a URI matching the expression {/namespace*}/{package}/{version} to retrieve a release's source archive. A client SHOULD set the Accept header and SHOULD append the corresponding extension to the requested URI to indicate the desired archive format. The following table lists the supported archive formats along with the requirement level for a server.

Media Type Extension Kind of Document Requirement Level
application/zip .zip ZIP archive REQUIRED
application/gzip .tar.gz GZip Compressed Tape Archive RECOMMENDED
GET /mona/LinkedList/1.1.1.zip HTTP/1.1
Accept: application/zip
Accept-Version: 1
Want-Digest: sha-256

If a release is found for the requested URI, a server SHOULD respond with a status code of 200 (OK) and a Content-Type header corresponding to the archive format used (for example, application/zip for a Zip archive). Otherwise, a server SHOULD respond with a status code of 404 (NOT FOUND).

HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: public, immutable
Content-Type: application/zip
Content-Disposition: attachment; filename="LinkedList-1.1.1.zip"
Content-Length: 2048
Content-Version: 1
Digest: sha-256=a2ac54cf25fbc1ad0028f03f0aa4b96833b83bb05a14e510892bb27dea4dc812
ETag: e61befdd5056d4b8bafa71c5bbb41d71
Link: <https://swift.pkg.github.com/mona/LinkedList/1.1.1.zip.asc>; rel=describedby; type="application/pgp-signature", <https://mirror-japanwest.github.com/mona-LinkedList-1.1.1.zip>; rel=duplicate; geo=jp; pri=10; type="application/zip"

A server SHOULD respond with a Content-Length header set to the size of the archive in bytes.

A server SHOULD respond with a Content-Disposition header set to attachment with a filename parameter equal to the name of the package followed by a hyphen (-), the version number, and file extension (for example, "LinkedList-1.1.1.zip").

A server SHOULD respond with a link to a digital signature for the archive using a Link header field entry with a describedby relation and an application/pgp-signature type.

It is RECOMMENDED for clients and servers to support range requests as described by RFC 7233, caching as described by RFC 7234, and instance digests as described by RFC 3230 and RFC 5843.

A server MAY specify mirrors or multiple download locations using Link header fields with a duplicate relation, as described by RFC 6249. A client MAY use this information to determine its preferred strategy for downloading.

A server that indexes but doesn't host packages SHOULD respond with a status code of 303 (See Other) and redirect to a hosted package archive if one is available. Otherwise, a server MAY respond with a status code of 501 (Not Implemented).

HTTP/1.1 303 See Other
Content-Version: 1
Location: https://swift.pkg.github.com/mona/LinkedList/1.1.1.zip
Digital signature of source archive

A client MAY send a GET request for a URI matching the expression {/namespace*}/{package}/{version} to retrieve a detached digital signature that can be used to verify the release's source archive according to RFC 4880. A client SHOULD set the Accept header to application/pgp-signature and SHOULD append .asc to the originally requested URI (for example .zip.asc for the signature of the Zip archive).

GET /mona/LinkedList/1.1.1.zip.asc HTTP/1.1
Accept: application/pgp-signature
Accept-Version: 1

If a release is found for the requested URI, a server SHOULD respond with a status code of 200 (OK) and a Content-Type header set to application/pgp-signature. Otherwise, a server SHOULD respond with a status code of 404 (NOT FOUND).

HTTP/1.1 200 OK
Cache-Control: public
Content-Type: application/pgp-signature
Content-Disposition: attachment; filename="LinkedList-1.1.1.zip.asc"
Content-Length: 833
Content-Version: 1

A client SHOULD verify the contents of a downloaded source archive using the provided signature (for example, using the command gpg --verify LinkedList-1.1.1.zip.asc LinkedList-1.1.1.zip).

A server that indexes but doesn't host packages SHOULD respond with a status code of 303 (See Other) and redirect to the digital signature for the archive if one is available. Otherwise, a server MAY respond with a status code of 501 (Not Implemented).

HTTP/1.1 303 See Other
Content-Version: 1
Location: https://swift.pkg.github.com/mona/LinkedList/1.1.1.zip.asc
Metadata

A client MAY send a GET request for a URI matching the expression {/namespace*}/{package}/{version} to retrieve metadata about a release. A client SHOULD set the Accept header with an application/json content type, and MAY append the .json extension to the requested URI.

GET /mona/LinkedList/1.1.1 HTTP/1.1
Accept: application/json
Accept-Version: 1

If a release is found for the requested URI, a server SHOULD respond with a status code of 200 (OK). Otherwise, a server SHOULD respond with a status code of 404 (NOT FOUND).

HTTP/1.1 200 OK
Content-Type: application/json
Content-Version: 1
Link: <https://example.com/mona/LinkedList/1.1.1>; rel="latest-version",
      <https://example.com/mona/LinkedList/1.0.0>; rel="predecessor-version"

A server SHOULD respond with a Link header containing the following entries:

Relation Description
latest-version The latest published release of the package
successor-version The next published release of the package, if one exists
predecessor-version The previous published release of the package, if one exists

A link with the latest-version relation MAY correspond to the requested release.

A server MAY include additional fields in its response. It is RECOMMENDED that package metadata be represented in JSON-LD according to a structured data standard. For example, this response using the Schema.org SoftwareSourceCode vocabulary:

{
  "@context": ["http://schema.org"],
  "@type": "SoftwareSourceCode",
  "identifier": "@mona/LinkedList",
  "name": "LinkedList",
  "description": "One thing links to another.",
  "keywords": ["data-structure", "collection"],
  "version": "1.1.1",
  "codeRepository": "https://github.com/mona/LinkedList",
  "license": "https://www.apache.org/licenses/LICENSE-2.0",
  "readme": "https://github.com/mona/LinkedList/blob/master/README.md",
  "issueTracker": "https://github.com/mona/LinkedList/issues",
  "programmingLanguage": {
    "@type": "ComputerLanguage",
    "name": "Swift",
    "url": "https://swift.org"
  },
  "author": {
      "@type": "Person",
      "@id": "https://github.com/mona",
      "givenName": "Mona",
      "middleName": "Lisa",
      "familyName": "Octocat",
  }
}

Publish a new package release

A client MAY send a PUT request for a URI matching the expression {/namespace*}/{package}/{version}{?commit,branch,tag,path,url} to publish a release of a package.

PUT /mona/LinkedList/1.1.1 HTTP/1.1
Accept-Version: 1

A client MAY publish releases in any order. For example, if a package has existing 1.0.0 and 2.0.0 releases, a client MAY publish a new 1.0.1 or 1.1.0 release.

A client MAY include additional information in its request body encoded as a JSON document with the Content-Type header set to application/json. It is RECOMMENDED that package metadata be represented in JSON-LD according to a structured data standard, as discussed in "Fetch a package release". A client MAY send subsequent requests to update metadata for existing releases. A server MAY refuse to update the metadata for a package release by responding with a status code of 403 (Forbidden).

If no release currently exists for the requested version, a server SHOULD respond quickly with a status code of 202 (Accepted) to acknowledge that the request is being processed. This response SHOULD also contain a Location header with a URL that the client can poll for progress updates and a Retry-After header with an estimate of when processing is expected to finish. A server MAY locate the status resource endpoint at a URI of its choosing. However, the use of a non-sequential, randomly-generated identifier is RECOMMENDED.

HTTP/1.1 202 Accepted
Content-Version: 1
Location: https://swift.pkg.github.com/submissions/90D8CC77-A576-47AE-A531-D6402C4E33BC
Retry-After: 120

A server SHOULD attempt to access the source of the package release before sending a response, so that it may communicate any errors to the client immediately. A server MAY set a timeout to guarantee a timely response to each request.

If a request to publish a new package release were to fail, a server MUST communicate that failure in the same way if sending an immediate response as it would if responding to a client polling for status.

Source repository location

A server SHOULD determine the location of a package's source repository from the requested URI, if possible. A client MAY specify an explicit location with a url query item parameter.

PUT /mona/LinkedList/1.1.1?url=https://github.com/mona/LinkedList.git HTTP/1.1
Accept-Version: 1

A server MAY refuse the specified source repository location by responding with a status code of 403 (Forbidden).

HTTP/1.1 403 Forbidden
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
     "detail": "invalid parameter, 'url'"
}

If a server is unable to access the repository, it SHOULD respond with 502 (Bad Gateway) with additional details about the failure in the response body.

HTTP/1.1 502 Bad Gateway
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "unable to connect to repository"
}
Source revision

The version path parameter in a client's request MUST be a valid version number for the release and SHOULD correspond to a tag in the package's source repository. A client MAY specify a different commit reference with any combination of commit, tag, and branch query item parameters.

PUT /mona/LinkedList/1.1.1?tag=v1.1.1 HTTP/1.1
Accept-Version: 1

A server SHOULD respond with a status code of 404 (Not Found) if it's unable to resolve the requested version in the repository with the provided commit, tag, or branch parameters.

HTTP/1.1 404 Not Found
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "tag '2.0.0' not found"
}

A server SHOULD respond with a status code of 409 (Conflict) if any provided commit, tag, or branch parameters cannot be resolved to a single revision.

HTTP/1.1 409 Conflict
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "tag '2.0.0' doesn't refer to commit '1F34E'"
}

A server MAY refuse to publish a package if the specified tag or commit isn't signed by responding with a status code of 403 (Forbidden).

HTTP/1.1 403 Forbidden
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "tag '2.0.0' must be signed"
}

A server SHOULD support Git repositories. A server MAY support other source control systems, such as Mercurial or Subversion.

Package location

A client MAY use path and url parameters together to publish a single package located in a subdirectory of a repository or to publish multiple packages from different paths in a single repository.

If a client specifies a path parameter, a server SHOULD look for a Package.swift file at that location. Otherwise, it SHOULD look at the root directory. If a Package.swift file doesn't exist at that location, a server SHOULD respond with a status code of 404 (Not Found).

Unpublish a package release

A client MAY send a DELETE request for a URI matching the expression {/namespace*}/{package}/{version} to remove an existing release from a package registry.

DELETE /mona/LinkedList/1.1.1 HTTP/1.1
Content-Version: 1

Support for this endpoint is OPTIONAL. A server that chooses not to process requests for unpublishing a package release SHALL respond with a status code of 405 (Method Not Allowed).

HTTP/1.1 405 Method Not Allowed
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
   "detail": "packages cannot be unpublished through the API"
}

If a server accepts a request for removal, it SHOULD respond quickly with a status code of 202 (Accepted) to acknowledge that the request is being processed. This response SHOULD also contain a Location header with a URL that the client can poll for progress updates and a Retry-After header with an estimate of when processing is expected to finish. A server MAY locate the status resource endpoint at a URI of its choosing. However, the use of a non-sequential, randomly-generated identifier is RECOMMENDED.

HTTP/1.1 202 Accepted
Content-Version: 1
Location: https://swift.pkg.github.com/submissions/0CF1FC65-D928-41E5-A825-3BEB9D69C004
Retry-After: 120

Once a package release is removed, a server SHOULD NOT publish any new releases with the same combination of namespace, package name, and version number.

If a client attempts to unpublish a package that has already been removed from the registry, a server SHOULD return 204 (No Content).

HTTP/1.1 204 No Content
Content-Version: 1

If a client fetches a package that has been unpublished from the registry, a server SHOULD respond with a status code of 410 (Gone), or MAY instead redirect to an alternate release by responding with a status code of 301 (Moved Permanently).

HTTP/1.1 410 Gone
Content-Version: 1
Content-Type: application/problem+json
Content-Language: en

{
    "detail": "this release was removed from the registry"
}

(Continued in next post)

24 Likes