Radish alpha
r
Radicle Improvement Proposals (RIPs)
Radicle
Git (anonymous pull)
Log in to clone via SSH
R
Richard Levitte
RIP 4: URI Scheme 16 days ago 43301b47e5643de823fc14c51e4826dde54db3fb History
rips 0004-general-uri-scheme general-uri-scheme.adoc
= RIP 4: General `rad:` URI Scheme
Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>; Richard Levitte <richard@levitte.org>
:doctype: article
:toc: preamble
:toclevels: 3
:status: Draft
:copyright: CC0-1.0
:lang: en

--
In <<RIP2>>, a `rad:` URN is briefly discussed as a simple Radicle
identifier. In <<RIP3>>, the `rad://` URL is introduced, made to allow
`git-remote-rad` to be a fruitful interface between Git and Radicle.

In this RIP, we discuss some modifications that are needed to have a general
URI scheme that fits in the syntax defined by <<URI>>, as well as further
extensions that allow reference to more aspects of Radicle as well as Git on a
Radicle network.

All extensions and modifications are intended not to introduce impossible
clashes with the existing schemes.
--

== Status Quo

References to resources on Radicle (both traditional Git objects as well as
collaborative objects, such as patches and issues) are communicated between
individuals mostly by relying on installations of <<radicle-explorer>>,
a web application that provides an HTTPS interface to the Radicle network.

When Alice wants to direct Bob's attention to a specific issue that she filed
on Radicle, you will likely see her send a URI of the form
`https://app.example.com/nodes/seed.example.com/rad:…/issues/…` to Bob.
URIs using the `rad:` schema are exchanged more rarely, and almost exclusively
to refer to repositories as a whole, e.g. in order to invoke `rad clone`,
`git clone`, etc.

As of 2024-04-23, the Radicle team also opted to have `rad:…`
URIs in Zulip messages automatically replaced by links to
`https://app.radicle.xyz/nodes/garden.radicle.xyz/rad:…`, limiting the
usefulness of exchange of `rad:` URIs.<<zulip-mangle>>

== Terminology

Node ID:: a Node ID, or NID for short, is an identifier for a Radicle node and a Radicle user, at the time of writing.
It is a <<multibase>> (<<base58btc>>) and <<multicodec>> encoded Ed25519 public key.

Namespace:: a namespace, in the Radicle ecosystem, commonly refers to a Git namespace, i.e. `refs/namespaces`,
that uses a *Node ID* as the namespace, e.g., `refs/namespaces/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi`.

Repository ID:: a Repository ID, or RID for short, is an identifier for a Radicle repository.
It is a <<multibase>> (<<base58btc>>) encoded Git object identifier, at the time of writing.

== Challenges

:url-cgit: https://git.zx2c4.com/cgit/
:url-sourcegraph: https://sourcegraph.com/

Every user should be able to individually control how they want to interact
with the network. Users may prefer to interact with Radicle via…

* a command line interface in their shell.
* a specific client program, such as:
** VSCode with a suitable extension, <<radicle-vscode>>
** a JetBrains IDE with a suitable plugin, <<radicle-jetbrains>>.
* <<radicle-explorer>>, hosted locally or by relying on a specific hosting
  provider.
* another custom program or handler, e.g. redirection to an instance of
  {url-cgit}[cgit] or {url-sourcegraph}[Sourcegraph].

Exchanging `https:` URIs that point at a specific hosting provider of
<<radicle-explorer>> is not portable. Individuals that read/receive such a URI
might prefer to access the repository with another method and then must modify
the URI (extract repository address, etc.) before access.

For the case of a reference to a repository, this is rather simple, but as
soon as references to resources _within_ the repositories are exchanged, there
are currently no standardized options. The two most common ones are referring
to an instance of `radicle-explorer` by means of a `https:`-URI, or
communicating in natural language, often in a context where the repository ID
is clear to all communicating parties. They will simply say "patch c863fe".

Automatic rewriting of `rad:` URIs to a specific hosting provider of
`radicle-explorer` contributes to additional centralization of access to the
network. In case the specific hosting provider goes offline, is censored, or
censors content, the impact is multiplied.

== Intention

The intention with this RIP is to define a `rad:` URI scheme with which one
can refer to:

. a Git repository in the Radicle network, which also includes the namespace
  for the contributions of a particular user (currently represented with a NID).
. optionally, a repository resource of some sort; to properly identify
  the resource:
.. the resource's type
.. a resource identity, specific to the resource type

****
We recognise that the concept of namespace and how that relates to a user is
a bit ambiguous.  A more detailed explanation should appear in another RIP.

For the moment, the user and their local node are identified with the same
identity, the NID.  Since the namespace for a user uses its identity, it may
appear confusing.  However, the fact remains that a namespace stores a user's
contribution to the repository it lives within.

In the future, users may be identified differently, and when that happens, the
namespace is expected to change accordingly, so that it is clear that it is the
user's contributions.
****

This can be seen as a hierarchy, expressed like this:

 repo-id [ "/" namespace ] [ "/" resource-type "/" resource-id ]

The `rad:` URI scheme is intended to be used to refer to / represent
repositories and resources within repositories, in such a way that it
can be used by applications, but also be understood by humans wishing
to get to the resource being referred to with what knowledge tools
they have available (such as the commands `git` and `rad`).

It does not imply any particular network protocol, or even that all
the parts of the scheme must be implemeted by all applications, and
applications may also make some parts of the scheme more mandatory
than what this RIP implies.

As a very concrete example of an application that handles `rad:` URIs,
`git-remote-rad` can probably never be expected to handle more than a
repository and namespace, a `rad:` URI with more than that should be
considered an error.  On the other hand, `git-remote-rad` requires the
presence of a namespace, i.e. makes it mandatory.

== Formalization and General Extension of the `rad:` URI Scheme

In this RIP, we denote the `rad:` scheme as a _URI_ scheme and try to
generally follow the nomenclature from <<URI>>.

=== The `web+rad:` URI Scheme

<<HTML>> defines that unless a URL scheme is known and on their safelist, it
may be registered as a custom URL handler if prefixed with `web+`.  See their
section on <<HTML-registerhandler,registering handlers>>.

For this RIP, it means that `web+rad:` must be compatible with `rad:` in every
way, i.e prepending the string `"web+"` to a valid `rad:` URI must result in a
valid `web+rad:`-URI, and removing the first four symbols from a `web+rad:`
URI must result in a valid `rad:` URI.

This should make it as easy as possible for users to register URI handlers for
both `web+rad:` and `rad:` on their system, according to their preferences.

=== The Old `rad:` URI Scheme

The scheme as defined in <<RIP2>> and <<RIP3>> was really pretty simple, and could
be summarized with just a few lines of <<ABNF>>:

// [source,abnf]
[%nowrap]
----
rad             = rad-scheme ":" rad-rid                       ; (RIP #2)
                / rad-scheme ":" "//" rad-rid [ "/" rad-nid ]  ; (RIP #3)
rad-scheme      = "rad"
rad-rid         = "z" 27*28(ALPHA / DIGIT)
rad-nid         = "z6Mk" 44(ALPHA / DIGIT)
----

This is workable in practice, but the URL form is arguably not aligning with
the ideas behind the 'authority' as described in <<URI-Authority>>.  The
'authority' carries with it the idea of a host, possibly with some user
information.  In a Radicle network, it can be argued that the NID represents
that much more than the RID does, but the NID used in the old `rad:` URI
scheme does not represent that either; it represents a namespace within a
repository.

=== The New `rad:` URI Scheme: General Approach

To align with <<URI, RFC 3986>>, the `rad:` scheme is renewed to include a
possible authority, which is the particular node / host that the data should
be found on.  In most cases, (much like in `file:` URIs), the authority is
expected to be empty or not there at all.

Other than that, the new URI scheme is extended with additional syntax to
meet the <<Challenges, challenges>> given above.

// [source,abnf]
[%nowrap]
----
include::data/rad-uri.abnf[tag=overview]
----

==== The Authority

The NID is conceptually ambiguous from an <<URI, RFC 3986>> syntax perspective:

// [source,abnf]
[%nowrap]
----
authority   = [ userinfo "@" ] host [ ":" port ]
----

This does not quite align semantically with how Radicle nodes are identified.
Radicle nodes are identified with their NID, or with `{NID}@{hostname}:{port}`.
This does align with <<URI, RFC 3986>> syntax well enough, with just a little
bit of redefinition of the authority for use in Radicle context.

****
The authority is most closely related to the address given to the Radicle CLI
command `rad node connect`.
****

// [source,abnf]
[%nowrap]
----
include::data/rad-uri.abnf[tag=authority]
----

==== The Repository

After the authority (if there is one) comes the repository to refer to.

With Radicle, the main anchoring resource is the repository, so its ID must
always be present, and specifying it alone refers to the synthesis of all
participants' contributions.

To refer to individual participants' separate contribution, their namespace
must also be included in the URI, separate from the repository ID with a slash
("/").  It shall be noted, though, that the namespace is only meaningful in
some specific circumstances, to be defined later.

****
Radicle repositories are _currently_ Git repositories.  We expect that other
version control (VCS) implementations may be used as well, and in that case,
we expect the VCS implementation type to be reflected in the repository
identity.
****

// [source,abnf]
[%nowrap]
----
include::data/rad-uri.abnf[tag=resource]
----

Here are a few examples of what a reference to a whole repository could look
like:

:tc00a: pass:c,q[footnote:tc00a[nearley-test result in link:data/test/tc00a-expected.json[]]]
:tc00b: pass:c,q[footnote:tc00b[nearley-test result in link:data/test/tc00b-expected.json[]]]
:tc00c: pass:c,q[footnote:tc00c[nearley-test result in link:data/test/tc00c-expected.json[]]]
:tc01a: pass:c,q[footnote:tc01a[nearley-test result in link:data/test/tc01a-expected.json[]]]
:tc01b: pass:c,q[footnote:tc01b[nearley-test result in link:data/test/tc01b-expected.json[]]]
:tc01c: pass:c,q[footnote:tc01c[nearley-test result in link:data/test/tc01c-expected.json[]]]
:tc02: pass:c,q[footnote:tc02[nearley-test result in link:data/test/tc02-expected.json[]]]
:tc03: pass:c,q[footnote:tc03[nearley-test result in link:data/test/tc03-expected.json[]]]
:tc04: pass:c,q[footnote:tc04[nearley-test result in link:data/test/tc04-expected.json[]]]
:tc05: pass:c,q[footnote:tc05[nearley-test result in link:data/test/tc05-expected.json[]]]

[options="header",cols="2a,1"]
|===
|URI
|Refers to

|
....
include::data/test/tc00a[]
....
{tc00a}
|Specific repo (RIP #2 URN form, still valid)

|
....
include::data/test/tc00b[]
....
{tc00b}
|Specific repo (RIP #3 URL form, considered legacy)

|
....
include::data/test/tc00c[]
....
{tc00c}
|Specific repo (new URI form with empty authority)

|
....
include::data/test/tc01a[]
....
{tc01a}
|Specific repo and namespace (new URI form, relative)

|
....
include::data/test/tc01b[]
....
{tc01b}
|Specific repo and namespace (RIP #3 URL form, considered legacy)

|
....
include::data/test/tc01c[]
....
{tc01c}
|Specific repo and namespace (new URI form with empty authority)

|
....
include::data/test/tc02[]
....
{tc02}
|Specific repo, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`

|
....
include::data/test/tc03[]
....
{tc03}
|Specific repo and namespace, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`

|
....
include::data/test/tc04[]
....
{tc04}
|Specific repo, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`, available on the host `seed.example.com:8776`

|
....
include::data/test/tc05[]
....
{tc05}
|Specific repo and namespace, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`, available on the host `seed.example.com:8776`
|===

(to test these URIs against a grammar, see <<Testing>> below)

==== The Resource Type and Identity

While <<The repository,the repository>> allows you to refer to a Git repository,
it may also be desirable to refer to one or more specific resources within
that repository.  To do this, <<The repository,the repository>> may be followed
by a "/", a resource type and identity.

By default, the resource type and identity is nothing, left to be added
to further on in this RIP, or in future RIPs:

// [source,abnf]
[%nowrap]
----
include::data/rad-uri.abnf[tags=resource-type-and-identity;!*]
----

==== The Query

The query part of the `rad:` URI inherits the syntax from <<URI>>.
The semantics of the query are to be defined with each resource type.

==== The Fragment

The fragment part of the URI inherits the syntax and semantics from <<URI>>.

=== Relative URIs

<<URI-Reference-Resolution>> discusses the resolution of relative (absolute
or rootless alike) URIs, based on a base URI.

This RIP has nothing specific to say about this, apart from supporting it.

== Resource Types and Identities

These are the resource types and associated identities defined in this
RIP.  Future RIPs may amend them, as well as define new resource types
with associated identities.

=== Resource Types Specific to Git

There are four Git specific resource types:

* `commit`
* `tree`
* `blob`
* `tag`

In a `rad:` URI, these resource types are used to refer to Git object types
with the same name in a repository.  These resource types must be followed by
an identifier that refers to a specific Git object of that type.

Some of these resource types may also be accompanied by <<The Query,query>>
parameters to further refine what the `rad:` URI refers to.  This RIP defines
the following <<The Query,query>> parameters, all of which may be given
multiple times:

* `tree=dir1/dir2/…`, to "dig out" a tree given by the path.
* `blob=dir1/dir2/…`, to "dig out" a blob given by the path.
* `path=dir1/dir2/…`, to "dig out" a tree or a blob given by the path.

****
Applications are by and large allowed to define other <<The Query,query>>
parameters than what is defined here.  We hope that future RIPs will help
standardise at least the most common ones.

A few possible examples that may be useful:

* `rad:{rid}/commit/{git-obj-or-ref}?history`, to view the history of a commit.
* `rad:{rid}/blob/{git-obj}?raw`, to view/download the raw contents of a blob,
  where one might otherwise reasonably expected it to be wrapped in some
  markup, such as HTML.
****

`commit`::
+
--
The identifier that follows this resource type may be:

* a Git object ID
* a Git reference (typically, a branch or a tag name)

In all cases, this resource type refers to a single commit object.

With this resource type, it makes sense to use the <<The Query,query>>
parameters `tree`, `blob` and `path` to refine the `rad:` URI.
--

`tree`::
+
--
The identifier that follows this type may be must be a Git object identifier.

With this resource type, it makes sense to use the <<The Query,query>>
parameters `tree`, `blob` and `path` to refine the `rad:` URI.
--

`blob`::
+
--
The identifier that follows this type must be a Git object identifier.

With this resource type, it makes no sense to use any of the <<The
Query,query>> defined above.
--

`tag`::
+
--
The identifier that follows this type may be:

* a Git object ID
* a Git reference (i.e. a tag name)

In all cases, this resource type refers to a single tag object.

With this resource type, it makes sense to use the <<The Query,query>>
parameters `tree`, `blob` and `path` to refine the `rad:` URI.
--

In <<ABNF>> terms, the Git specific resource types are defined like this:

// [source,abnf]
[%nowrap]
----
include::data/rad-uri.abnf[tag=git-type-and-identity]
----

Here are a few examples of what a reference to a Git specific resource could
look like:

:tc10a: pass:c,q[footnote:tc10a[nearley-test result in link:data/test/tc10a-expected.json[]]]
:tc10b: pass:c,q[footnote:tc10b[nearley-test result in link:data/test/tc10b-expected.json[]]]
:tc12: pass:c,q[footnote:tc12[nearley-test result in link:data/test/tc12-expected.json[]]]
:tc13: pass:c,q[footnote:tc13[nearley-test result in link:data/test/tc13-expected.json[]]]

[options="header",cols="2a,1"]
|===
|URI
|Refers to

|
....
include::data/test/tc10a[]
....
{tc10a}
|Specific commit in repo

|
....
include::data/test/tc10b[]
....
{tc10b}
|Specific commit in repo

|
....
include::data/test/tc12[]
....
{tc12}
|Specific commit on a specific node, using the explicit name for the commit object

|
....
include::data/test/tc13[]
....
{tc13}
|Specific commit in a specific repo and namespace, which, if not available locally, should be fetched from the node `z6MksF…S9wzpT`, available on the host `seed.example.com:8776`
|===

(to test these URIs against a grammar, see <<Testing>> below)

And with queries:

:tc40a: pass:c,q[footnote:tc40a[nearley-test result in link:data/test/tc40a-expected.json[]]]
:tc40b: pass:c,q[footnote:tc40b[nearley-test result in link:data/test/tc40b-expected.json[]]]
:tc41a: pass:c,q[footnote:tc41a[nearley-test result in link:data/test/tc41a-expected.json[]]]
:tc41b: pass:c,q[footnote:tc41b[nearley-test result in link:data/test/tc41b-expected.json[]]]
:tc42a: pass:c,q[footnote:tc42a[nearley-test result in link:data/test/tc42a-expected.json[]]]
:tc42b: pass:c,q[footnote:tc42b[nearley-test result in link:data/test/tc42b-expected.json[]]]
:tc43a: pass:c,q[footnote:tc43a[nearley-test result in link:data/test/tc43a-expected.json[]]]
:tc43b: pass:c,q[footnote:tc43b[nearley-test result in link:data/test/tc43b-expected.json[]]]
:tc44: pass:c,q[footnote:tc44[nearley-test result in link:data/test/tc44-expected.json[]]]

[options="header",cols="2a,1"]
|===
|URI
|Refers to

|
....
include::data/test/tc40a[]
....
{tc40a}
|The `src` directory of the repo at tree object `3eb47e9`

|
....
include::data/test/tc40b[]
....
{tc40b}
|The `src` directory of the repo at tree object `3eb47e9`

|
....
include::data/test/tc41a[]
....
{tc41a}
|The `README.md` file of the repo at the "master" reference

|
....
include::data/test/tc41b[]
....
{tc41b}
|The `README.md` file of the repo at the "master" reference

|
....
include::data/test/tc42a[]
....
{tc42a}
|The `foo/doc` directory of the repo at the "baz" reference

|
....
include::data/test/tc42b[]
....
{tc42b}
|The `foo/doc` directory of the repo at the "baz" reference

|
....
include::data/test/tc43a[]
....
{tc43a}
|The `doc` and `src` directories of the repo at the "baz/foo" reference.

|
....
include::data/test/tc43b[]
....
{tc43b}
|The `doc` and `src` directories directory of the repo at the "baz/foo" reference.

|
....
include::data/test/tc44[]
....
{tc44}
|The commit notes for commit `e9cf5263…`.
|===

(to test these URIs against a grammar, see <<Testing>> below)

****
// Git version that is current at the time of writing.
:gitversion: 2.54.0

// Base URL of Git documentation.
:gitdocs: https://git-scm.com/docs

// Allows for convenient reference to definitions in the Git glossary.
// "gitgloss" is just short for "a definition in the Git glossary".
:gitgloss: {gitdocs}/gitglossary/{gitversion}#Documentation/gitglossary.txt-

:gitrevparse: {gitdocs}/git-rev-parse/{gitversion}#Documentation/git-rev-parse.txt-

[discrete]
==== Addressing Git Resources by Reference Names

[discrete]
===== Types of Resources That May Be Addressed

Resources of the types "commit" and "tag", may, in Git jargon, be
"committish". There are at least two definitions in the Git documentation
that differ slightly:

[quote,'{gitgloss}commit-ishalsocommittish[Git glossary entry for "committish"]']
A {gitgloss}commit_object[commit object] or an {gitgloss}object[object]
that can be recursively {gitgloss}dereference[dereferenced] to a commit
object.  The following are all commit-ishes:
a commit object,
a {gitgloss}tag_object[tag object] that points to a commit object,
a tag object that points to a tag object that points to a commit object,
etc.

[quote,'{gitrevparse}--verify[Git documentation of `git rev-parse --verify`]']
a commit-ish (i.e. a commit, or an annotated tag that points at a commit)

We thus restrict ourselves to only refer to commits and tags via references,
since no tree or blob object may ever be committish.

[discrete]
===== Ambiguity in Addressing by Reference Name

Note that Git references are ambiguous, even within one repository (or reference
namespace).

It is possible to create the following set of references in one namespace:

* `refs/heads/main`
* `refs/tags/main`
* `refs/heads/refs/heads/main`

We do not define how such ambiguity is to be resolved, but note that applications
will likely want to implement some mechanism for disambiguation.

See also {gitrevparse}refnameegmasterheadsmasterrefsheadsmaster[the section
_Specifying Revisions_ in the documentation of `git rev-parse`].

****

=== Resources Types Specific to Radicle Collaborative Objects

Alongside the usual Git objects, Radicle also implements a generalized
collaborative object (usually referred to as COB), identified with a COB type
and a COB object.

For general COB references in a `rad:` URI, these resource types are defined
to refer to a Radicle COB / multiple COBs:

`cob`::
+
--
In a `rad:` URI, this must be followed a slash (`/`) and the COB type.  The
COB type _may_ be followed by another slash (`/`) and the COB object.

If the `rad:` URI includes the COB object, then it refers to a single COB.

If the `rad:` URI does not include the COB object, then it refers to the set
of COB objects of the given COB type.
--

`cob` resources may also be accompanied by a set of <<The Query,query>>
parameters to further refine what the `rad:` URI refers to.

****
Because we realise that we cannot know all future COB types and how they
function, we cannot do too much in terms of definitions.
We leave it to the COB owners as well as applications to work out what query params they
will handle, and hopefully, some of them can be standardised in future RIPs.

A hypothetical example: `rad:{RID}/cob/example.radicle.sparkle?q=glitter`.
****

In <<ABNF>> terms, the COB resource types are defined like this:

// [source, abnf]
[%nowrap]
----
include::data/rad-uri.abnf[tag=rad-cob-type-and-identity]
----

Here are a few examples of what a reference to other collaborative objects
could look like:

:tc30: pass:c,q[footnote:tc30[nearley-test result in link:data/test/tc30-expected.json[]]]
:tc31: pass:c,q[footnote:tc31[nearley-test result in link:data/test/tc31-expected.json[]]]
:tc32: pass:c,q[footnote:tc32[nearley-test result in link:data/test/tc32-expected.json[]]]

[options="header",cols="2a,1"]
|===
|URI
|Refers to

|
....
include::data/test/tc30[]
....
{tc30}
|Specific COB of the type `org.example`

|
....
include::data/test/tc31[]
....
{tc31}
|Specific COB of the type `org.example` on specific node

|
....
include::data/test/tc32[]
....
{tc32}
|The set of COBs of the type `org.example`
|===

== Implementation Requirements and Recommendations

* Implementations may implement only part of this specification, depending on
what makes sense in the context of what they do.  It is advisable, however, if
they can parse enough of a valid `rad:` URI to allow for <<Future work,future work>>.
For example, it is hard to imagine that `git-remote-rad` would ever support
more elaborate URLs than those involving an RID and an NID:

** `rad:{RID}`
** `rad:///{RID}`
** `rad:{RID}/{NID}`
** `rad:///{RID}/{NID}`
** `rad://{RID}/{NID}` (legacy)

* Implementations that receive a URI they cannot handle - because of
unrecognised resource type, identifier, or query params - should signal an
error.
+
For fragments, there is no recommendation, that is left entirely to the
authors of those softwares.

* Software for human interaction on the web should be able to automatically
convert between `rad:` and `web+rad:` to create links.  For example, in a
markdown context, it would be quite viable for this:
+
`rad:z3gqcJU…CSGazv5/commit/72db6d…`
+
to be converted to:
+
`[rad:z3gqcJU…CSGazv5/commit/72db6d…](web+rad:z3gqcJU…CSGazv5/commit/72db6d…)`

* Implementations that are aware of the context may support relative URIs as
they see fit.  For example, a program that handles Radicle patches and issues
could assume the base URI `rad:///{RID}` (given that `{RID}` is the ID of
the repository that is being handled), allowing comments to refer to issue and
other patches in the same repository with `patch/{PATCH-ID}` or `issue/{ISSUE-ID}`.

== Future Work

Future work may include:

* amending <<The authority,the authority>> with Radicle network
  specification syntax (pending future formalization of that idea).
* amending <<The authority,the authority>> with multi-device IDs (also
  pending formalization of that idea).
* adding more resource types, and their respective syntaxes
* extending existing resource types.
* adding other query types
* viewing the history/graph of the underlying Git repository (in analogy to `git log`/`git log --graph`).
* viewing revision ranges of the underlying Git repository (in analogy to `git diff`, `git log`).

== Closing Thoughts

This RIP started with a status quo and challenges.  It is the hope of the
authors that the challenges are sufficiently addressed, while also leaving
doors open for more extensions in the future.

== Copyright

This document is licensed under the Creative Commons CC0 1.0 Universal license.

[bibliography]
== References

// https://docs.asciidoctor.org/asciidoc/latest/syntax-quick-reference/#bibliography
// says that anchors like [[[ABNF]]] should render to [ANBF], but experience
// says that this is not quite true.  However, [[ABNF]][ABNF] seems to render
// what I hoped for.  /RL

[[ABNF]][ABNF]:: https://datatracker.ietf.org/doc/html/rfc5234[RFC 5234 - Augmented BNF for Syntax Specifications: ABNF]
[[HTML]][HTML]:: Web Hypertext Application Technology Working Group: _HTML Living Standard_ (<https://html.spec.whatwg.org>)
[[HTML-registerhandler]][HTML-registerhandler]:: Sec. 8.9.1.4, _Custom scheme handlers: the `registerProtocolHandler()` method_ (<https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers>)
[[Nearley]][Nearley]:: Kartik Chandra's Nearley grammar (<https://nearley.js.org/>)
[[radicle-explorer]][radicle-explorer]:: Radicle Web Interface - link:web+rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5[rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5] +
  (<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5>)
[[radicle-jetbrains]][radicle-jetbrains]:: Radicle JetBrains plugin - link:web+rad:z3WHS4GSf8hChLjGYfPkJY7vCxsBK[rad:z3WHS4GSf8hChLjGYfPkJY7vCxsBK] +
  (<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3WHS4GSf8hChLjGYfPkJY7vCxsBK>)
[[radicle-vscode]][radicle-vscode]:: Radicle VSCode extension - link:web+rad:z3Makm6fsQQXmpSFE43DZqwupaEhk[rad:z3Makm6fsQQXmpSFE43DZqwupaEhk] +
  (<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3Makm6fsQQXmpSFE43DZqwupaEhk>), +
  link:web+vscode:extension/radicle-ide-plugins-team.radicle[vscode:extension/radicle-ide-plugins-team.radicle] +
  (<https://marketplace.visualstudio.com/items?itemName=radicle-ide-plugins-team.radicle>)
[[RIP2]][RIP2]:: @cloudhead: Identity - link:web+rad:z3trNYnLWS11cJWC6BbxDs5niGo82/blob/1c402116983be19e754fb14aa7ce38145f0a4b09?path=0002-identity.md[rad:z3trNYnLWS11cJWC6BbxDs5niGo82/blob/1c402116983be19e754fb14aa7ce38145f0a4b09?path=0002-identity.md] +
  (<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/tree/1c402116983be19e754fb14aa7ce38145f0a4b09/0002-identity.md>)
[[RIP3]][RIP3]:: @fintohaps: Radicle Improvement Proposal #3: Storage Layout - link:web+rad:z3trNYnLWS11cJWC6BbxDs5niGo82/blob/329dee9a4b65169ea3889a7da239892b705d0d68?path=0003-storage-layout.md#url[rad:z3trNYnLWS11cJWC6BbxDs5niGo82/blob/329dee9a4b65169ea3889a7da239892b705d0d68?path=0003-storage-layout.md#url] +
  (<https://app.radicle.xyz/nodes/iris.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/tree/329dee9a4b65169ea3889a7da239892b705d0d68/0003-storage-layout.md#url>)
[[URI]][URI]:: https://datatracker.ietf.org/doc/html/rfc3986[RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax]
[[URI-Authority]][URI-Authority]:: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2[RFC 3986 - 3.2. Authority]
[[URI-Reference-Resolution]][URI-Reference-Resolution]:: https://datatracker.ietf.org/doc/html/rfc3986#section-5[RFC 3986 - 5. Reference Resolution]
[[zulip-mangle]][zulip-mangle]:: Zulip Topic: _`rad:` gets mangled by Zulip_ (#general, 2024-04-23) (<https://radicle.zulipchat.com/#narrow/stream/369274-general/topic/.60rad.3A.60.20gets.20mangled.20by.20Zulip>)
[[base58btc]][base58btc]:: https://datatracker.ietf.org/doc/html/draft-msporny-base58-02[Base58 Specification]
[[multibase]][multibase]:: https://www.ietf.org/archive/id/draft-multiformats-multibase-07.html[Multibase Specification]
[[multicodec]][multicodec]:: https://github.com/multiformats/multicodec/[Multiformats: multicodec]

[appendix]
== Full <<ABNF>> grammar

[%nowrap]
----
include::data/rad-uri.abnf[tag=!rfc5234]
----

[appendix]
== Full <<Nearley>> grammar

[%nowrap]
----
include::data/rad-uri.ne[]
----

[appendix]
== Testing

All the examples sprinkled in this RIP are tested against the <<Nearley>>
grammar.  Test inputs and expected parse results are in `data/test/`, where
each `tc__` file contains a valid URI and the corresponding
`tc__-expected.json` contains the expected parse result as JSON.

The test suite can be run with:

[,shell]
----
nix flake check
----

This compiles the grammar with `nearleyc`, runs `nearley-test` on each test
input, and compares the JSON output against the expected result.

To test a single URI manually:

[,shell]
[%nowrap]
----
# [one time] Compile the grammar into javascript
nearleyc -o rad-uri.js data/rad-uri.ne

# Test the chosen URI string ($URI)
nearley-test -q -i "$URI" rad-uri.js
----

`nearley-test` will return all possible results for the URI, which means that
where there are syntactic ambiguities, there will be more than one result.
For testing purposes, it is enough if one of the results match the expected
results linked from the footnotes associated with the examples in the tables
above.