Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
64 changes: 59 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,48 @@ impl NameOrAddress {
}
}

/// Resolve this [`NameOrAddress`].
///
/// If `self` is a [`NameOrAddress::Address`], the inner [`Address`] is
/// returned in a [`ResolvedAddress`] without a [`DomainName`].
///
/// Otherwise, if `self` is a [`NameOrAddress::Name`], the [`Address`] is
/// looked up by that name in the SpacetimeDB DNS and returned in a
/// [`ResolvedAddress`] alongside `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 +146,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
}
}