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, mona
≍ MONA
)
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)