Check documentation intra-link (#1432)

* Fix broken links in rustdoc

This fixes all of the rustdoc warnings on nightly.

* Check documentation intra-link

* Fix config

* Fix bad indent

* Make nightly explicit

* More links fixes

* Fix link broken after master merge

Co-authored-by: Demi Obenour <48690212+DemiMarie-parity@users.noreply.github.com>
This commit is contained in:
Pierre Krieger 2020-02-10 15:17:08 +01:00 committed by GitHub
parent 1eff4b9823
commit fc4dec581e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 72 additions and 49 deletions

View File

@ -6,6 +6,7 @@ workflows:
jobs: jobs:
- test - test
- test-wasm - test-wasm
- check-rustdoc-links
- integration-test - integration-test
jobs: jobs:
@ -40,6 +41,31 @@ jobs:
paths: paths:
- "/cache" - "/cache"
check-rustdoc-links:
docker:
- image: rust:latest
steps:
- checkout
- restore_cache:
key: test-rustdoc-cache-{{ epoch }}
- run:
name: Install nightly Rust
# TODO: intra-doc links are available on nightly only
# see https://doc.rust-lang.org/nightly/rustdoc/lints.html#intra_doc_link_resolution_failure
command: rustup default nightly
- run:
name: Print Rust version
command: |
rustc --version
- run:
name: Check Rustdoc links
command: RUSTDOCFLAGS="--deny intra_doc_link_resolution_failure" cargo +nightly doc --verbose --workspace --no-deps --document-private-items
- save_cache:
key: test-rustdoc-cache-{{ epoch }}
paths:
- ./target
- /usr/local/cargo
test-wasm: test-wasm:
docker: docker:
- image: parity/rust-builder:latest - image: parity/rust-builder:latest

View File

@ -84,7 +84,7 @@ impl PublicKey {
/// Encode the RSA public key in DER as a X.509 SubjectPublicKeyInfo structure, /// Encode the RSA public key in DER as a X.509 SubjectPublicKeyInfo structure,
/// as defined in [RFC5280]. /// as defined in [RFC5280].
/// ///
/// [RFC5280] https://tools.ietf.org/html/rfc5280#section-4.1 /// [RFC5280]: https://tools.ietf.org/html/rfc5280#section-4.1
pub fn encode_x509(&self) -> Vec<u8> { pub fn encode_x509(&self) -> Vec<u8> {
let spki = Asn1SubjectPublicKeyInfo { let spki = Asn1SubjectPublicKeyInfo {
algorithmIdentifier: Asn1RsaEncryption { algorithmIdentifier: Asn1RsaEncryption {

View File

@ -22,7 +22,7 @@
//! //!
//! The core type is a [`task::Task`], which implements [`futures::Future`] //! The core type is a [`task::Task`], which implements [`futures::Future`]
//! and connects and handles a node. A task receives and sends messages //! and connects and handles a node. A task receives and sends messages
//! ([`tasks::FromTaskMessage`], [`tasks::ToTaskMessage`]) to the outside. //! ([`task::FromTaskMessage`], [`task::ToTaskMessage`]) to the outside.
//! //!
//! A set of tasks is managed by a [`Manager`] which creates tasks when a //! A set of tasks is managed by a [`Manager`] which creates tasks when a
//! node should be connected to (cf. [`Manager::add_reach_attempt`]) or //! node should be connected to (cf. [`Manager::add_reach_attempt`]) or

View File

@ -209,7 +209,7 @@ pub trait Transport {
} }
/// Begins a series of protocol upgrades via an /// Begins a series of protocol upgrades via an
/// [`upgrade::Builder`](core::transport::upgrade::Builder). /// [`upgrade::Builder`](upgrade::Builder).
fn upgrade(self, version: upgrade::Version) -> upgrade::Builder<Self> fn upgrade(self, version: upgrade::Version) -> upgrade::Builder<Self>
where where
Self: Sized, Self: Sized,

View File

@ -239,7 +239,7 @@ type EitherUpgrade<C, U> = future::Either<InboundUpgradeApply<C, U>, OutboundUpg
/// An upgrade on an authenticated, non-multiplexed [`Transport`]. /// An upgrade on an authenticated, non-multiplexed [`Transport`].
/// ///
/// See [`Builder::upgrade`](Builder::upgrade). /// See [`Transport::upgrade`]
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Upgrade<T, U> { inner: T, upgrade: U } pub struct Upgrade<T, U> { inner: T, upgrade: U }

View File

@ -105,7 +105,7 @@ impl Multiaddr {
Some(protocol) Some(protocol)
} }
/// Like [`push`] but consumes `self`. /// Like [`Multiaddr::push`] but consumes `self`.
pub fn with(mut self, p: Protocol<'_>) -> Self { pub fn with(mut self, p: Protocol<'_>) -> Self {
let mut w = io::Cursor::<&mut Vec<u8>>::new(Arc::make_mut(&mut self.bytes)); let mut w = io::Cursor::<&mut Vec<u8>>::new(Arc::make_mut(&mut self.bytes));
w.set_position(w.get_ref().len() as u64); w.set_position(w.get_ref().len() as u64);

View File

@ -284,10 +284,10 @@ impl<R> LengthDelimitedReader<R> {
/// # Panic /// # Panic
/// ///
/// Will panic if called while there is data in the read or write buffer. /// Will panic if called while there is data in the read or write buffer.
/// The read buffer is guaranteed to be empty whenever `Stream::poll` yields /// The read buffer is guaranteed to be empty whenever [`Stream::poll`] yields
/// a new `Message`. The write buffer is guaranteed to be empty whenever /// a new `Message`. The write buffer is guaranteed to be empty whenever
/// [`poll_write_buffer`] yields `Async::Ready` or after the `Sink` has been /// [`LengthDelimited::poll_write_buffer`] yields [`Async::Ready`] or after
/// completely flushed via [`Sink::poll_complete`]. /// the [`Sink`] has been completely flushed via [`Sink::poll_complete`].
pub fn into_inner(self) -> (R, BytesMut) { pub fn into_inner(self) -> (R, BytesMut) {
self.inner.into_inner() self.inner.into_inner()
} }

View File

@ -62,7 +62,7 @@ impl<TInner: AsyncRead + AsyncWrite> Future for NegotiatedComplete<TInner> {
} }
impl<TInner> Negotiated<TInner> { impl<TInner> Negotiated<TInner> {
/// Creates a `Negotiated` in state [`State::Complete`], possibly /// Creates a `Negotiated` in state [`State::Completed`], possibly
/// with `remaining` data to be sent. /// with `remaining` data to be sent.
pub(crate) fn completed(io: TInner, remaining: BytesMut) -> Self { pub(crate) fn completed(io: TInner, remaining: BytesMut) -> Self {
Negotiated { state: State::Completed { io, remaining } } Negotiated { state: State::Completed { io, remaining } }

View File

@ -62,8 +62,8 @@ const MSG_LS: &[u8] = b"ls\n";
pub enum Version { pub enum Version {
/// Version 1 of the multistream-select protocol. See [1] and [2]. /// Version 1 of the multistream-select protocol. See [1] and [2].
/// ///
/// [1] https://github.com/libp2p/specs/blob/master/connections/README.md#protocol-negotiation /// [1]: https://github.com/libp2p/specs/blob/master/connections/README.md#protocol-negotiation
/// [2] https://github.com/multiformats/multistream-select /// [2]: https://github.com/multiformats/multistream-select
V1, V1,
/// A lazy variant of version 1 that is identical on the wire but delays /// A lazy variant of version 1 that is identical on the wire but delays
/// sending of protocol negotiation data as much as possible. /// sending of protocol negotiation data as much as possible.
@ -302,7 +302,7 @@ impl<R> MessageIO<R> {
/// ///
/// Panics if the read buffer is not empty, meaning that an incoming /// Panics if the read buffer is not empty, meaning that an incoming
/// protocol negotiation frame has been partially read. The read buffer /// protocol negotiation frame has been partially read. The read buffer
/// is guaranteed to be empty whenever [`MessageIO::poll`] returned /// is guaranteed to be empty whenever `MessageIO::poll` returned
/// a message. /// a message.
pub fn into_inner(self) -> (R, BytesMut) { pub fn into_inner(self) -> (R, BytesMut) {
self.inner.into_inner() self.inner.into_inner()
@ -368,7 +368,7 @@ impl<R> MessageReader<R> {
/// ///
/// Panics if the read buffer is not empty, meaning that an incoming /// Panics if the read buffer is not empty, meaning that an incoming
/// protocol negotiation frame has been partially read. The read buffer /// protocol negotiation frame has been partially read. The read buffer
/// is guaranteed to be empty whenever [`MessageReader::poll`] returned /// is guaranteed to be empty whenever `MessageReader::poll` returned
/// a message. /// a message.
pub fn into_inner(self) -> (R, BytesMut) { pub fn into_inner(self) -> (R, BytesMut) {
self.inner.into_inner() self.inner.into_inner()

View File

@ -23,7 +23,7 @@
//! [`AsyncRead`] and [`AsyncWrite`]. //! [`AsyncRead`] and [`AsyncWrite`].
//! //!
//! Each call to [`AsyncWrite::poll_write`] will send one packet to the sink. //! Each call to [`AsyncWrite::poll_write`] will send one packet to the sink.
//! Calls to [`AsyncRead::read`] will read from the stream's incoming packets. //! Calls to [`AsyncRead::poll_read`] will read from the stream's incoming packets.
use futures::{prelude::*, ready}; use futures::{prelude::*, ready};
use std::{io::{self, Read}, pin::Pin, task::{Context, Poll}}; use std::{io::{self, Read}, pin::Pin, task::{Context, Poll}};

View File

@ -37,7 +37,7 @@ impl<S> fmt::Debug for Yamux<S> {
} }
struct Inner<S> { struct Inner<S> {
/// The `futures::stream::Stream` of incoming substreams. /// The [`futures::stream::Stream`] of incoming substreams.
incoming: S, incoming: S,
/// Handle to control the connection. /// Handle to control the connection.
control: yamux::Control, control: yamux::Control,
@ -171,7 +171,7 @@ impl Config {
Config(cfg) Config(cfg)
} }
/// Turn this into a `LocalConfig` for use with upgrades of !Send resources. /// Turn this into a [`LocalConfig`] for use with upgrades of ![`Send`] resources.
pub fn local(self) -> LocalConfig { pub fn local(self) -> LocalConfig {
LocalConfig(self) LocalConfig(self)
} }
@ -253,7 +253,7 @@ where
} }
} }
/// The Yamux [`StreamMuxer`] error type. /// The Yamux [`libp2p_core::StreamMuxer`] error type.
#[derive(Debug, Error)] #[derive(Debug, Error)]
#[error("yamux error: {0}")] #[error("yamux error: {0}")]
pub struct YamuxError(#[from] pub yamux::ConnectionError); pub struct YamuxError(#[from] pub yamux::ConnectionError);
@ -316,4 +316,4 @@ impl<T> Stream for LocalIncoming<T> {
} }
impl<T> Unpin for LocalIncoming<T> { impl<T> Unpin for LocalIncoming<T> {
} }

View File

@ -383,7 +383,7 @@ impl Gossipsub {
debug!("Completed JOIN for topic: {:?}", topic_hash); debug!("Completed JOIN for topic: {:?}", topic_hash);
} }
/// Gossipsub LEAVE(topic) - Notifies mesh[topic] peers with PRUNE messages. /// Gossipsub LEAVE(topic) - Notifies mesh\[topic\] peers with PRUNE messages.
fn leave(&mut self, topic_hash: &TopicHash) { fn leave(&mut self, topic_hash: &TopicHash) {
debug!("Running LEAVE for topic {:?}", topic_hash); debug!("Running LEAVE for topic {:?}", topic_hash);
@ -880,7 +880,7 @@ impl Gossipsub {
} }
} }
/// Helper function which forwards a message to mesh[topic] peers. /// Helper function which forwards a message to mesh\[topic\] peers.
fn forward_msg(&mut self, message: GossipsubMessage, source: &PeerId) { fn forward_msg(&mut self, message: GossipsubMessage, source: &PeerId) {
let msg_id = (self.config.message_id_fn)(&message); let msg_id = (self.config.message_id_fn)(&message);
debug!("Forwarding message: {:?}", msg_id); debug!("Forwarding message: {:?}", msg_id);

View File

@ -471,7 +471,7 @@ where
/// of the libp2p Kademlia provider API. /// of the libp2p Kademlia provider API.
/// ///
/// The results of the (repeated) provider announcements sent by this node are /// The results of the (repeated) provider announcements sent by this node are
/// delivered in [`KademliaEvent::AddProviderResult`]. /// delivered in [`AddProviderResult`].
pub fn start_providing(&mut self, key: record::Key) { pub fn start_providing(&mut self, key: record::Key) {
let record = ProviderRecord::new(key.clone(), self.kbuckets.local_key().preimage().clone()); let record = ProviderRecord::new(key.clone(), self.kbuckets.local_key().preimage().clone());
if let Err(err) = self.store.add_provider(record) { if let Err(err) = self.store.add_provider(record) {
@ -1435,7 +1435,7 @@ impl Quorum {
/// The events produced by the `Kademlia` behaviour. /// The events produced by the `Kademlia` behaviour.
/// ///
/// See [`Kademlia::poll`]. /// See [`NetworkBehaviour::poll`].
#[derive(Debug)] #[derive(Debug)]
pub enum KademliaEvent { pub enum KademliaEvent {
/// The result of [`Kademlia::bootstrap`]. /// The result of [`Kademlia::bootstrap`].

View File

@ -41,10 +41,10 @@
//! //!
//! This module implements two periodic jobs: //! This module implements two periodic jobs:
//! //!
//! * [`jobs::PutRecordJob`]: For (re-)publication and (re-)replication of //! * [`PutRecordJob`]: For (re-)publication and (re-)replication of
//! regular (value-)records. //! regular (value-)records.
//! //!
//! * [`jobs::AddProviderJob`]: For (re-)publication of provider records. //! * [`AddProviderJob`]: For (re-)publication of provider records.
//! Provider records currently have no separate replication mechanism. //! Provider records currently have no separate replication mechanism.
//! //!
//! A periodic job is driven like a `Future` or `Stream` by `poll`ing it. //! A periodic job is driven like a `Future` or `Stream` by `poll`ing it.

View File

@ -32,12 +32,11 @@
//! an [`AppliedPending`] result which must be consumed by calling [`take_applied_pending`] //! an [`AppliedPending`] result which must be consumed by calling [`take_applied_pending`]
//! regularly and / or after performing lookup operations like [`entry`] and [`closest`]. //! regularly and / or after performing lookup operations like [`entry`] and [`closest`].
//! //!
//! [`entry`]: kbucket::KBucketsTable::entry //! [`entry`]: KBucketsTable::entry
//! [`closest`]: kbucket::KBucketsTable::closest //! [`closest`]: KBucketsTable::closest
//! [`AppliedPending`]: kbucket::AppliedPending //! [`AppliedPending`]: bucket::AppliedPending
//! [`KBucketsTable`]: kbucket::KBucketsTable //! [`take_applied_pending`]: KBucketsTable::take_applied_pending
//! [`take_applied_pending`]: kbucket::KBucketsTable::take_applied_pending //! [`PendingEntry`]: entry::PendingEntry
//! [`PendingEntry`]: kbucket::PendingEntry
// [Implementation Notes] // [Implementation Notes]
// //
@ -469,7 +468,7 @@ where
/// Together with a known key `a` (e.g. the local key), a random distance `d` for /// Together with a known key `a` (e.g. the local key), a random distance `d` for
/// this bucket w.r.t `k` gives rise to the corresponding (random) key `b` s.t. /// this bucket w.r.t `k` gives rise to the corresponding (random) key `b` s.t.
/// the XOR distance between `a` and `b` is `d`. In other words, it gives /// the XOR distance between `a` and `b` is `d`. In other words, it gives
/// rise to a random key falling into this bucket. See [`Key::from_distance`]. /// rise to a random key falling into this bucket. See [`key::Key::for_distance`].
pub fn rand_distance(&self, rng: &mut impl rand::Rng) -> Distance { pub fn rand_distance(&self, rng: &mut impl rand::Rng) -> Distance {
self.index.rand_distance(rng) self.index.rand_distance(rng)
} }

View File

@ -25,10 +25,6 @@
//! The upgrade's output is a `Sink + Stream` of messages. The `Stream` component is used //! The upgrade's output is a `Sink + Stream` of messages. The `Stream` component is used
//! to poll the underlying transport for incoming messages, and the `Sink` component //! to poll the underlying transport for incoming messages, and the `Sink` component
//! is used to send messages to remote peers. //! is used to send messages to remote peers.
//!
//! [`KademliaProtocolConfig`]: protocol::KademliaProtocolConfig
//! [`KadRequestMsg`]: protocol::KadRequestMsg
//! [`KadResponseMsg`]: protocol::KadResponseMsg
use bytes::BytesMut; use bytes::BytesMut;
use codec::UviBytes; use codec::UviBytes;

View File

@ -36,7 +36,7 @@
//! //!
//! A peer iterator can be finished prematurely at any time through `finish`. //! A peer iterator can be finished prematurely at any time through `finish`.
//! //!
//! [`Finished`]: peers::PeersIterState::Finished //! [`Finished`]: PeersIterState::Finished
pub mod closest; pub mod closest;
pub mod fixed; pub mod fixed;

View File

@ -297,7 +297,7 @@ where
/// ///
/// On success, the upgrade yields the [`PeerId`] obtained from the /// On success, the upgrade yields the [`PeerId`] obtained from the
/// `RemoteIdentity`. The output of this upgrade is thus directly suitable /// `RemoteIdentity`. The output of this upgrade is thus directly suitable
/// for creating an [`authenticated`](libp2p_core::TransportBuilder::authenticate) /// for creating an [`authenticated`](libp2p_core::transport::upgrade::Authenticate)
/// transport for use with a [`Network`](libp2p_core::nodes::Network). /// transport for use with a [`Network`](libp2p_core::nodes::Network).
#[derive(Clone)] #[derive(Clone)]
pub struct NoiseAuthenticated<P, C: Zeroize, R> { pub struct NoiseAuthenticated<P, C: Zeroize, R> {

View File

@ -102,8 +102,8 @@ impl PingConfig {
/// at any time, i.e. in the absence of ping failures the connection lifetime /// at any time, i.e. in the absence of ping failures the connection lifetime
/// is determined by other protocol handlers. /// is determined by other protocol handlers.
/// ///
/// If the maximum number of allowed ping failures is reached, the /// If the maximum number of allowed ping failures is reached, the
/// connection is always terminated as a result of [`PingHandler::poll`] /// connection is always terminated as a result of [`ProtocolsHandler::poll`]
/// returning an error, regardless of the keep-alive setting. /// returning an error, regardless of the keep-alive setting.
pub fn with_keep_alive(mut self, b: bool) -> Self { pub fn with_keep_alive(mut self, b: bool) -> Self {
self.keep_alive = b; self.keep_alive = b;

View File

@ -38,7 +38,7 @@
//! > it only adds an additional condition for terminating the connection, namely //! > it only adds an additional condition for terminating the connection, namely
//! > a certain number of failed ping requests. //! > a certain number of failed ping requests.
//! //!
//! [`Swarm`]: libp2p_core::Swarm //! [`Swarm`]: libp2p_swarm::Swarm
//! [`Transport`]: libp2p_core::Transport //! [`Transport`]: libp2p_core::Transport
pub mod protocol; pub mod protocol;

View File

@ -25,7 +25,7 @@
//! //!
//! The `SecioConfig` implements [`InboundUpgrade`] and [`OutboundUpgrade`] and thus //! The `SecioConfig` implements [`InboundUpgrade`] and [`OutboundUpgrade`] and thus
//! serves as a connection upgrade for authentication of a transport. //! serves as a connection upgrade for authentication of a transport.
//! See [`authenticate`](libp2p_core::transport::upgrade::builder::Builder::authenticate). //! See [`authenticate`](libp2p_core::transport::upgrade::Builder::authenticate).
//! //!
//! ```no_run //! ```no_run
//! # fn main() { //! # fn main() {

View File

@ -155,11 +155,11 @@ pub trait NetworkBehaviour: Send + 'static {
/// Parameters passed to `poll()`, that the `NetworkBehaviour` has access to. /// Parameters passed to `poll()`, that the `NetworkBehaviour` has access to.
pub trait PollParameters { pub trait PollParameters {
/// Iterator returned by [`supported_protocols`]. /// Iterator returned by [`supported_protocols`](PollParameters::supported_protocols).
type SupportedProtocolsIter: ExactSizeIterator<Item = Vec<u8>>; type SupportedProtocolsIter: ExactSizeIterator<Item = Vec<u8>>;
/// Iterator returned by [`listened_addresses`]. /// Iterator returned by [`listened_addresses`](PollParameters::listened_addresses).
type ListenedAddressesIter: ExactSizeIterator<Item = Multiaddr>; type ListenedAddressesIter: ExactSizeIterator<Item = Multiaddr>;
/// Iterator returned by [`external_addresses`]. /// Iterator returned by [`external_addresses`](PollParameters::external_addresses).
type ExternalAddressesIter: ExactSizeIterator<Item = Multiaddr>; type ExternalAddressesIter: ExactSizeIterator<Item = Multiaddr>;
/// Returns the list of protocol the behaviour supports when a remote negotiates a protocol on /// Returns the list of protocol the behaviour supports when a remote negotiates a protocol on
@ -190,6 +190,8 @@ pub trait NetworkBehaviourEventProcess<TEvent> {
/// An action that a [`NetworkBehaviour`] can trigger in the [`Swarm`] /// An action that a [`NetworkBehaviour`] can trigger in the [`Swarm`]
/// in whose context it is executing. /// in whose context it is executing.
///
/// [`Swarm`]: super::Swarm
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum NetworkBehaviourAction<TInEvent, TOutEvent> { pub enum NetworkBehaviourAction<TInEvent, TOutEvent> {
/// Instructs the `Swarm` to return an event when it is being polled. /// Instructs the `Swarm` to return an event when it is being polled.

View File

@ -75,14 +75,14 @@ pub use select::{IntoProtocolsHandlerSelect, ProtocolsHandlerSelect};
/// Communication with a remote over a set of protocols is initiated in one of two ways: /// Communication with a remote over a set of protocols is initiated in one of two ways:
/// ///
/// 1. Dialing by initiating a new outbound substream. In order to do so, /// 1. Dialing by initiating a new outbound substream. In order to do so,
/// [`ProtocolsHandler::poll()`] must return an [`OutboundSubstreamRequest`], providing an /// [`ProtocolsHandler::poll()`] must return an [`ProtocolsHandlerEvent::OutboundSubstreamRequest`],
/// instance of [`ProtocolsHandler::OutboundUpgrade`] that is used to negotiate the /// providing an instance of [`libp2p_core::upgrade::OutboundUpgrade`] that is used to negotiate the
/// protocol(s). Upon success, [`ProtocolsHandler::inject_fully_negotiated_outbound`] /// protocol(s). Upon success, [`ProtocolsHandler::inject_fully_negotiated_outbound`]
/// is called with the final output of the upgrade. /// is called with the final output of the upgrade.
/// ///
/// 2. Listening by accepting a new inbound substream. When a new inbound substream /// 2. Listening by accepting a new inbound substream. When a new inbound substream
/// is created on a connection, [`ProtocolsHandler::listen_protocol`] is called /// is created on a connection, [`ProtocolsHandler::listen_protocol`] is called
/// to obtain an instance of [`ProtocolsHandler::InboundUpgrade`] that is used to /// to obtain an instance of [`libp2p_core::upgrade::InboundUpgrade`] that is used to
/// negotiate the protocol(s). Upon success, /// negotiate the protocol(s). Upon success,
/// [`ProtocolsHandler::inject_fully_negotiated_inbound`] is called with the final /// [`ProtocolsHandler::inject_fully_negotiated_inbound`] is called with the final
/// output of the upgrade. /// output of the upgrade.
@ -111,8 +111,8 @@ pub trait ProtocolsHandler: Send + 'static {
/// The type of additional information passed to an `OutboundSubstreamRequest`. /// The type of additional information passed to an `OutboundSubstreamRequest`.
type OutboundOpenInfo: Send + 'static; type OutboundOpenInfo: Send + 'static;
/// The [`InboundUpgrade`] to apply on inbound substreams to negotiate the /// The [`InboundUpgrade`](libp2p_core::upgrade::InboundUpgrade) to apply on inbound
/// desired protocols. /// substreams to negotiate the desired protocols.
/// ///
/// > **Note**: The returned `InboundUpgrade` should always accept all the generally /// > **Note**: The returned `InboundUpgrade` should always accept all the generally
/// > supported protocols, even if in a specific context a particular one is /// > supported protocols, even if in a specific context a particular one is