diff --git a/src/html/page/block.rs b/src/html/page/block.rs
index 1cf23b8..cfce7d0 100644
--- a/src/html/page/block.rs
+++ b/src/html/page/block.rs
@@ -1,7 +1,7 @@
use crate::html::component::header::HeaderHtml;
use crate::html::page::not_found::not_found_html_response;
use crate::model::app_state::AppState;
-use crate::model::path_block_selector::PathBlockSelector;
+use crate::model::block_selector_extended::BlockSelectorExtended;
use axum::extract::rejection::PathRejection;
use axum::extract::Path;
use axum::extract::State;
@@ -13,20 +13,9 @@ use neptune_core::models::blockchain::block::block_info::BlockInfo;
use std::sync::Arc;
use tarpc::context;
-pub async fn block_page(
- user_input_maybe: Result, PathRejection>,
- state: State>,
-) -> Result, Response> {
- let Path(path_block_selector) = user_input_maybe
- .map_err(|e| not_found_html_response(state.clone(), Some(e.to_string())))?;
-
- let value_path: Path<(PathBlockSelector, String)> = Path((path_block_selector, "".to_string()));
- block_page_with_value(Ok(value_path), state).await
-}
-
#[axum::debug_handler]
-pub async fn block_page_with_value(
- user_input_maybe: Result, PathRejection>,
+pub async fn block_page(
+ user_input_maybe: Result, PathRejection>,
State(state): State>,
) -> Result, Response> {
#[derive(boilerplate::Boilerplate)]
@@ -36,21 +25,17 @@ pub async fn block_page_with_value(
block_info: BlockInfo,
}
- let Path((path_block_selector, value)) = user_input_maybe
+ let Path(block_selector) = user_input_maybe
.map_err(|e| not_found_html_response(State(state.clone()), Some(e.to_string())))?;
let header = HeaderHtml {
state: state.clone(),
};
- let block_selector = path_block_selector
- .as_block_selector(&value)
- .map_err(|e| not_found_html_response(State(state.clone()), Some(e.to_string())))?;
-
let block_info = match state
.clone()
.rpc_client
- .block_info(context::current(), block_selector)
+ .block_info(context::current(), block_selector.into())
.await
.map_err(|e| not_found_html_response(State(state.clone()), Some(e.to_string())))?
{
diff --git a/src/main.rs b/src/main.rs
index da3bde3..6e49700 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,7 +4,6 @@ use clap::Parser;
use neptune_core::models::blockchain::block::block_selector::BlockSelector;
use neptune_core::rpc_server::RPCClient;
use neptune_explorer::html::page::block::block_page;
-use neptune_explorer::html::page::block::block_page_with_value;
use neptune_explorer::html::page::not_found::not_found_html_fallback;
use neptune_explorer::html::page::redirect_qs_to_path::redirect_query_string_to_path;
use neptune_explorer::html::page::root::root;
@@ -12,9 +11,7 @@ use neptune_explorer::html::page::utxo::utxo_page;
use neptune_explorer::model::app_state::AppState;
use neptune_explorer::model::config::Config;
use neptune_explorer::rpc::block_digest::block_digest;
-use neptune_explorer::rpc::block_digest::block_digest_with_value;
use neptune_explorer::rpc::block_info::block_info;
-use neptune_explorer::rpc::block_info::block_info_with_value;
use neptune_explorer::rpc::utxo_digest::utxo_digest;
use std::net::Ipv4Addr;
use std::net::SocketAddr;
@@ -43,21 +40,12 @@ async fn main() -> Result<(), RpcError> {
let app = Router::new()
// -- RPC calls --
- .route("/rpc/block_info/:selector", get(block_info))
- .route(
- "/rpc/block_info/:selector/:value",
- get(block_info_with_value),
- )
- .route(
- "/rpc/block_digest/:selector/:value",
- get(block_digest_with_value),
- )
- .route("/rpc/block_digest/:selector", get(block_digest))
+ .route("/rpc/block_info/*selector", get(block_info))
+ .route("/rpc/block_digest/*selector", get(block_digest))
.route("/rpc/utxo_digest/:index", get(utxo_digest))
// -- Dynamic HTML pages --
.route("/", get(root))
- .route("/block/:selector", get(block_page))
- .route("/block/:selector/:value", get(block_page_with_value))
+ .route("/block/*selector", get(block_page))
.route("/utxo/:value", get(utxo_page))
// -- Rewrite query-strings to path --
.route("/rqs", get(redirect_query_string_to_path))
diff --git a/src/model/block_selector_extended.rs b/src/model/block_selector_extended.rs
new file mode 100644
index 0000000..4f79dfe
--- /dev/null
+++ b/src/model/block_selector_extended.rs
@@ -0,0 +1,71 @@
+use super::height_or_digest::HeightOrDigest;
+use neptune_core::models::blockchain::block::block_selector::BlockSelector;
+use neptune_core::models::blockchain::block::block_selector::BlockSelectorParseError;
+use serde::de::Error;
+use serde::Deserialize;
+use serde::Deserializer;
+use std::str::FromStr;
+
+/// newtype for `BlockSelector` that provides ability to parse `height_or_digest/value`.
+///
+/// This is useful for HTML form(s) that allow user to enter either height or
+/// digest into the same text input field.
+///
+/// In particular it is necessary to support javascript-free website with such
+/// an html form.
+#[derive(Debug, Clone)]
+pub struct BlockSelectorExtended(BlockSelector);
+
+impl std::fmt::Display for BlockSelectorExtended {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl FromStr for BlockSelectorExtended {
+ type Err = BlockSelectorParseError;
+
+ // note: this parses BlockSelector, plus height_or_digest/
+ fn from_str(s: &str) -> Result {
+ match BlockSelector::from_str(s) {
+ Ok(bs) => Ok(Self::from(bs)),
+ Err(e) => {
+ let parts: Vec<_> = s.split('/').collect();
+ if parts.len() == 2 && parts[0] == "height_or_digest" {
+ Ok(Self::from(HeightOrDigest::from_str(parts[1])?))
+ } else {
+ Err(e)
+ }
+ }
+ }
+ }
+}
+
+// note: axum uses serde Deserialize for Path elements.
+impl<'de> Deserialize<'de> for BlockSelectorExtended {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: Deserializer<'de>,
+ {
+ let s = String::deserialize(deserializer)?;
+ Self::from_str(&s).map_err(D::Error::custom)
+ }
+}
+
+impl From for BlockSelectorExtended {
+ fn from(hd: HeightOrDigest) -> Self {
+ Self(hd.into())
+ }
+}
+
+impl From for BlockSelectorExtended {
+ fn from(v: BlockSelector) -> Self {
+ Self(v)
+ }
+}
+
+impl From for BlockSelector {
+ fn from(v: BlockSelectorExtended) -> Self {
+ v.0
+ }
+}
diff --git a/src/model/height_or_digest.rs b/src/model/height_or_digest.rs
new file mode 100644
index 0000000..92bc9d5
--- /dev/null
+++ b/src/model/height_or_digest.rs
@@ -0,0 +1,47 @@
+use neptune_core::models::blockchain::block::block_height::BlockHeight;
+use neptune_core::models::blockchain::block::block_selector::BlockSelector;
+use neptune_core::models::blockchain::block::block_selector::BlockSelectorParseError;
+use neptune_core::prelude::tasm_lib::Digest;
+use serde::{Deserialize, Serialize};
+use std::str::FromStr;
+
+/// represents either a block-height or a block digest
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub enum HeightOrDigest {
+ /// Identifies block by Digest (hash)
+ Digest(Digest),
+ /// Identifies block by Height (count from genesis)
+ Height(BlockHeight),
+}
+
+impl std::fmt::Display for HeightOrDigest {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Digest(d) => write!(f, "{}", d),
+ Self::Height(h) => write!(f, "{}", h),
+ }
+ }
+}
+
+impl FromStr for HeightOrDigest {
+ type Err = BlockSelectorParseError;
+
+ // note: this parses the output of impl Display for HeightOrDigest
+ // note: this is used by clap parser in neptune-cli for block-info command
+ // and probably future commands as well.
+ fn from_str(s: &str) -> Result {
+ Ok(match s.parse::() {
+ Ok(h) => Self::Height(h.into()),
+ Err(_) => Self::Digest(Digest::try_from_hex(s)?),
+ })
+ }
+}
+
+impl From for BlockSelector {
+ fn from(hd: HeightOrDigest) -> Self {
+ match hd {
+ HeightOrDigest::Height(h) => Self::Height(h),
+ HeightOrDigest::Digest(d) => Self::Digest(d),
+ }
+ }
+}
diff --git a/src/model/mod.rs b/src/model/mod.rs
index ab761ae..a3c8ed6 100644
--- a/src/model/mod.rs
+++ b/src/model/mod.rs
@@ -1,3 +1,4 @@
pub mod app_state;
+pub mod block_selector_extended;
pub mod config;
-pub mod path_block_selector;
+pub mod height_or_digest;
diff --git a/src/model/path_block_selector.rs b/src/model/path_block_selector.rs
deleted file mode 100644
index 12159ba..0000000
--- a/src/model/path_block_selector.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-use axum::http::StatusCode;
-use axum::response::IntoResponse;
-use axum::response::Response;
-use neptune_core::models::blockchain::block::block_height::BlockHeight;
-use neptune_core::models::blockchain::block::block_selector::BlockSelector;
-use neptune_core::prelude::tasm_lib::Digest;
-use neptune_core::prelude::twenty_first::error::TryFromHexDigestError;
-use serde::{Deserialize, Serialize};
-use std::str::FromStr;
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
-pub enum PathBlockSelector {
- #[serde(rename = "genesis")]
- Genesis,
- #[serde(rename = "tip")]
- Tip,
- #[serde(rename = "digest")]
- Digest,
- #[serde(rename = "height")]
- Height,
- #[serde(rename = "height_or_digest")]
- HeightOrDigest,
-}
-
-#[derive(thiserror::Error, Debug)]
-pub enum PathBlockSelectorError {
- #[error("Genesis does not accept an argument")]
- GenesisNoArg,
-
- #[error("Tip does not accept an argument")]
- TipNoArg,
-
- #[error("Digest could not be parsed")]
- DigestNotParsed(#[from] TryFromHexDigestError),
-
- #[error("Height could not be parsed")]
- HeightNotParsed(#[from] std::num::ParseIntError),
-}
-impl PathBlockSelectorError {
- fn as_response_tuple(&self) -> (StatusCode, String) {
- (StatusCode::NOT_FOUND, self.to_string())
- }
-}
-impl IntoResponse for PathBlockSelectorError {
- fn into_response(self) -> Response {
- self.as_response_tuple().into_response()
- }
-}
-impl From for Response {
- fn from(e: PathBlockSelectorError) -> Response {
- e.as_response_tuple().into_response()
- }
-}
-impl PathBlockSelector {
- pub fn as_block_selector(&self, value: &str) -> Result {
- match self {
- PathBlockSelector::Genesis if !value.is_empty() => {
- Err(PathBlockSelectorError::GenesisNoArg)
- }
- PathBlockSelector::Genesis => Ok(BlockSelector::Genesis),
- PathBlockSelector::Tip if !value.is_empty() => Err(PathBlockSelectorError::TipNoArg),
- PathBlockSelector::Tip => Ok(BlockSelector::Tip),
- PathBlockSelector::Digest => Ok(BlockSelector::Digest(Digest::try_from_hex(value)?)),
- PathBlockSelector::Height => Ok(BlockSelector::Height(BlockHeight::from(
- u64::from_str(value)?,
- ))),
- PathBlockSelector::HeightOrDigest => Ok(match u64::from_str(value) {
- Ok(height) => BlockSelector::Height(BlockHeight::from(height)),
- Err(_) => BlockSelector::Digest(Digest::try_from_hex(value)?),
- }),
- }
- }
-}
diff --git a/src/rpc/block_digest.rs b/src/rpc/block_digest.rs
index a9b6c13..07f8bad 100644
--- a/src/rpc/block_digest.rs
+++ b/src/rpc/block_digest.rs
@@ -1,3 +1,7 @@
+use crate::http_util::not_found_err;
+use crate::http_util::rpc_err;
+use crate::model::app_state::AppState;
+use crate::model::block_selector_extended::BlockSelectorExtended;
use axum::extract::Path;
use axum::extract::State;
use axum::response::IntoResponse;
@@ -6,37 +10,14 @@ use neptune_core::prelude::twenty_first::math::digest::Digest;
use std::sync::Arc;
use tarpc::context;
-use crate::{
- http_util::{not_found_err, rpc_err},
- model::{app_state::AppState, path_block_selector::PathBlockSelector},
-};
-
#[axum::debug_handler]
pub async fn block_digest(
- Path(path_block_selector): Path,
+ Path(selector): Path,
State(state): State>,
) -> Result, impl IntoResponse> {
- block_digest_with_value_worker(state, path_block_selector, "").await
-}
-
-#[axum::debug_handler]
-pub async fn block_digest_with_value(
- Path((path_block_selector, value)): Path<(PathBlockSelector, String)>,
- State(state): State>,
-) -> Result, impl IntoResponse> {
- block_digest_with_value_worker(state, path_block_selector, &value).await
-}
-
-async fn block_digest_with_value_worker(
- state: Arc,
- path_block_selector: PathBlockSelector,
- value: &str,
-) -> Result, impl IntoResponse> {
- let block_selector = path_block_selector.as_block_selector(value)?;
-
match state
.rpc_client
- .block_digest(context::current(), block_selector)
+ .block_digest(context::current(), selector.into())
.await
.map_err(rpc_err)?
{
diff --git a/src/rpc/block_info.rs b/src/rpc/block_info.rs
index d8cdbc5..02dd181 100644
--- a/src/rpc/block_info.rs
+++ b/src/rpc/block_info.rs
@@ -1,3 +1,7 @@
+use crate::http_util::not_found_err;
+use crate::http_util::rpc_err;
+use crate::model::app_state::AppState;
+use crate::model::block_selector_extended::BlockSelectorExtended;
use axum::extract::Path;
use axum::extract::State;
use axum::response::Json;
@@ -6,43 +10,17 @@ use neptune_core::models::blockchain::block::block_info::BlockInfo;
use std::sync::Arc;
use tarpc::context;
-use crate::{
- http_util::{not_found_err, rpc_err},
- model::{app_state::AppState, path_block_selector::PathBlockSelector},
-};
-
#[axum::debug_handler]
pub async fn block_info(
- Path(path_block_selector): Path,
+ Path(selector): Path,
State(state): State>,
) -> Result, Response> {
- let block_info = block_info_with_value_worker(state, path_block_selector, "").await?;
- Ok(Json(block_info))
-}
-
-#[axum::debug_handler]
-pub async fn block_info_with_value(
- Path((path_block_selector, value)): Path<(PathBlockSelector, String)>,
- State(state): State>,
-) -> Result, Response> {
- let block_info = block_info_with_value_worker(state, path_block_selector, &value).await?;
- Ok(Json(block_info))
-}
-
-pub(crate) async fn block_info_with_value_worker(
- state: Arc,
- path_block_selector: PathBlockSelector,
- value: &str,
-) -> Result {
- let block_selector = path_block_selector.as_block_selector(value)?;
-
- match state
+ let block_info = state
.rpc_client
- .block_info(context::current(), block_selector)
+ .block_info(context::current(), selector.into())
.await
.map_err(rpc_err)?
- {
- Some(info) => Ok(info),
- None => Err(not_found_err()),
- }
+ .ok_or_else(not_found_err)?;
+
+ Ok(Json(block_info))
}