Skip to content

Commit

Permalink
Return parsed DomainName alongside Address when resolving `NameOr…
Browse files Browse the repository at this point in the history
…Address`

Helps to avoid repeatedly parsing `DomainName`, and to simplify the
`/database/publish` handler (not included in this patch).
  • Loading branch information
kim committed Aug 9, 2023
1 parent 537b3c7 commit 381e1c6
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 15 deletions.
17 changes: 8 additions & 9 deletions crates/client-api/src/routes/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub async fn call(

let args = ReducerArgs::Json(body);

let address = name_or_address.resolve(&*worker_ctx).await?;
let address = name_or_address.resolve(&*worker_ctx).await?.into();
let database = worker_ctx_find_database(&*worker_ctx, &address).await?.ok_or_else(|| {
log::error!("Could not find database: {}", address.to_hex());
(StatusCode::NOT_FOUND, "No such database.")
Expand Down Expand Up @@ -254,7 +254,7 @@ pub async fn describe(
Query(DescribeQueryParams { expand }): Query<DescribeQueryParams>,
auth: SpacetimeAuthHeader,
) -> axum::response::Result<impl IntoResponse> {
let address = name_or_address.resolve(&*worker_ctx).await?;
let address = name_or_address.resolve(&*worker_ctx).await?.into();
let database = worker_ctx_find_database(&*worker_ctx, &address)
.await?
.ok_or((StatusCode::NOT_FOUND, "No such database."))?;
Expand Down Expand Up @@ -308,7 +308,7 @@ pub async fn catalog(
Query(DescribeQueryParams { expand }): Query<DescribeQueryParams>,
auth: SpacetimeAuthHeader,
) -> axum::response::Result<impl IntoResponse> {
let address = name_or_address.resolve(&*worker_ctx).await?;
let address = name_or_address.resolve(&*worker_ctx).await?.into();
let database = worker_ctx_find_database(&*worker_ctx, &address)
.await?
.ok_or((StatusCode::NOT_FOUND, "No such database."))?;
Expand Down Expand Up @@ -354,7 +354,7 @@ pub async fn info(
State(worker_ctx): State<Arc<dyn WorkerCtx>>,
Path(InfoParams { name_or_address }): Path<InfoParams>,
) -> axum::response::Result<impl IntoResponse> {
let address = name_or_address.resolve(&*worker_ctx).await?;
let address = name_or_address.resolve(&*worker_ctx).await?.into();
let database = worker_ctx_find_database(&*worker_ctx, &address)
.await?
.ok_or((StatusCode::NOT_FOUND, "No such database."))?;
Expand Down Expand Up @@ -403,7 +403,7 @@ pub async fn logs(
// Should all the others change?
let auth = auth_or_unauth(auth)?;

let address = name_or_address.resolve(&*worker_ctx).await?;
let address = name_or_address.resolve(&*worker_ctx).await?.into();
let database = worker_ctx_find_database(&*worker_ctx, &address)
.await?
.ok_or((StatusCode::NOT_FOUND, "No such database."))?;
Expand Down Expand Up @@ -503,7 +503,7 @@ pub async fn sql(
// which queries this identity is allowed to execute against the database.
let auth = auth.get_or_create(&*worker_ctx).await?;

let address = name_or_address.resolve(&*worker_ctx).await?;
let address = name_or_address.resolve(&*worker_ctx).await?.into();
let database = worker_ctx_find_database(&*worker_ctx, &address)
.await?
.ok_or((StatusCode::NOT_FOUND, "No such database."))?;
Expand Down Expand Up @@ -800,9 +800,8 @@ pub async fn publish(
// Parse the address or convert the name to a usable address
let db_address = if let Some(name_or_address) = name_or_address.clone() {
match name_or_address.try_resolve(&*ctx).await? {
Ok(address) => address,
Err(name) => {
let domain = name.parse().map_err(DomainParsingRejection)?;
Ok(resolved) => resolved.into(),
Err(domain) => {
// Client specified a name which doesn't yet exist
// Create a new DNS record and a new address to assign to it
let address = ctx.control_db().alloc_spacetime_address().await.map_err(log_and_500)?;
Expand Down
2 changes: 1 addition & 1 deletion crates/client-api/src/routes/subscribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub async fn handle_websocket(
) -> axum::response::Result<impl IntoResponse> {
let auth = auth.get_or_create(&*worker_ctx).await?;

let address = name_or_address.resolve(&*worker_ctx).await?;
let address = name_or_address.resolve(&*worker_ctx).await?.into();

let (res, ws_upgrade, protocol) =
ws.select_protocol([(BIN_PROTOCOL, Protocol::Binary), (TEXT_PROTOCOL, Protocol::Text)]);
Expand Down
66 changes: 61 additions & 5 deletions crates/client-api/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ use axum::headers;
use axum::response::IntoResponse;
use bytestring::ByteString;
use http::{HeaderName, HeaderValue, Request, StatusCode};

use spacetimedb::address::Address;
use spacetimedb_lib::name::DomainName;

use crate::routes::database::DomainParsingRejection;
use crate::{log_and_500, ControlNodeDelegate};
Expand Down Expand Up @@ -75,20 +77,50 @@ impl NameOrAddress {
}
}

/// Resolve this [`NameOrAddress`].
///
/// If `self` is a [`NameOrAddress::Address`], the returned
/// [`ResolvedAddress`] contains only an [`Address`] and a `None`
/// [`DomainName`].
///
/// Otherwise, if `self` is a [`NameOrAddress::Name`], the [`Address`] is
/// looked up by that name in the SpacetimeDB DNS and the returned
/// [`ResolvedAddress`] contains both an [`Address`] and `Some`
/// [`DomainName`].
///
/// Errors are returned if [`NameOrAddress::Name`] cannot be parsed into a
/// [`DomainName`], or the DNS lookup fails.
///
/// An `Ok` result is itself a [`Result`], which is `Err(DomainName)` if the
/// given [`NameOrAddress::Name`] is not registered in the SpacetimeDB DNS,
/// i.e. no corresponding [`Address`] exists.
pub async fn try_resolve(
&self,
ctx: &(impl ControlNodeDelegate + ?Sized),
) -> axum::response::Result<Result<Address, &str>> {
) -> axum::response::Result<Result<ResolvedAddress, DomainName>> {
Ok(match self {
NameOrAddress::Address(addr) => Ok(*addr),
NameOrAddress::Name(name) => {
Self::Address(addr) => Ok(ResolvedAddress {
address: *addr,
domain: None,
}),
Self::Name(name) => {
let domain = name.parse().map_err(DomainParsingRejection)?;
ctx.spacetime_dns(&domain).await.map_err(log_and_500)?.ok_or(name)
let address = ctx.spacetime_dns(&domain).await.map_err(log_and_500)?;
match address {
Some(address) => Ok(ResolvedAddress {
address,
domain: Some(domain),
}),
None => Err(domain),
}
}
})
}

pub async fn resolve(&self, ctx: &(impl ControlNodeDelegate + ?Sized)) -> axum::response::Result<Address> {
/// A variant of [`Self::try_resolve()`] which maps to a 400 (Bad Request)
/// response if `self` is a [`NameOrAddress::Name`] for which no
/// corresponding [`Address`] is found in the SpacetimeDB DNS.
pub async fn resolve(&self, ctx: &(impl ControlNodeDelegate + ?Sized)) -> axum::response::Result<ResolvedAddress> {
self.try_resolve(ctx).await?.map_err(|_| StatusCode::BAD_REQUEST.into())
}
}
Expand Down Expand Up @@ -116,3 +148,27 @@ impl fmt::Display for NameOrAddress {
}
}
}

/// A resolved [`NameOrAddress`].
///
/// Constructed by [`NameOrAddress::try_resolve()`].
pub struct ResolvedAddress {
address: Address,
domain: Option<DomainName>,
}

impl ResolvedAddress {
pub fn address(&self) -> &Address {
&self.address
}

pub fn domain(&self) -> Option<&DomainName> {
self.domain.as_ref()
}
}

impl From<ResolvedAddress> for Address {
fn from(value: ResolvedAddress) -> Self {
value.address
}
}

0 comments on commit 381e1c6

Please sign in to comment.