pub use exonum_api::{Deprecated, EndpointMutability, Error, FutureResult, HttpStatusCode, Result};
use exonum::{
blockchain::{Blockchain, Schema as CoreSchema},
crypto::PublicKey,
merkledb::{access::Prefixed, Snapshot},
runtime::{BlockchainData, InstanceDescriptor, InstanceId},
};
use exonum_api::{backends::actix, ApiBuilder, ApiScope, MovedPermanentlyError};
use futures::{Future, IntoFuture};
use serde::{de::DeserializeOwned, Serialize};
use super::Broadcaster;
#[derive(Debug)]
pub struct ServiceApiState<'a> {
broadcaster: Broadcaster<'a>,
snapshot: Box<dyn Snapshot>,
endpoint: String,
}
impl<'a> ServiceApiState<'a> {
pub fn from_api_context<S: Into<String>>(
blockchain: &'a Blockchain,
instance: InstanceDescriptor,
endpoint: S,
) -> Self {
Self {
broadcaster: Broadcaster::new(
instance,
blockchain.service_keypair(),
blockchain.sender(),
),
snapshot: blockchain.snapshot(),
endpoint: endpoint.into(),
}
}
pub fn data(&'a self) -> BlockchainData<&dyn Snapshot> {
BlockchainData::new(&self.snapshot, &self.instance().name)
}
pub fn service_data(&'a self) -> Prefixed<&dyn Snapshot> {
self.data().for_executing_service()
}
pub fn snapshot(&self) -> &dyn Snapshot {
&self.snapshot
}
pub fn service_key(&self) -> PublicKey {
self.broadcaster.keypair().public_key()
}
pub fn instance(&self) -> &InstanceDescriptor {
&self.broadcaster.instance()
}
pub fn broadcaster(&self) -> Option<Broadcaster<'a>> {
CoreSchema::new(&self.snapshot).validator_id(self.broadcaster.keypair().public_key())?;
Some(self.broadcaster.clone())
}
pub fn generic_broadcaster(&self) -> Broadcaster<'a> {
self.broadcaster.clone()
}
pub fn moved_permanently(&self, new_endpoint: &str) -> MovedPermanentlyError {
let new_url = Self::relative_to(&self.endpoint, new_endpoint);
MovedPermanentlyError::new(new_url)
}
fn relative_to(old_endpoint: &str, new_endpoint: &str) -> String {
let endpoint_without_end_slash = old_endpoint.trim_end_matches('/');
let mut nesting_level = endpoint_without_end_slash
.chars()
.filter(|&c| c == '/')
.count();
nesting_level += 1;
let path_to_service_root = "../".repeat(nesting_level);
format!("{}{}", path_to_service_root, new_endpoint)
}
}
#[derive(Debug, Clone)]
pub struct ServiceApiScope {
inner: ApiScope,
blockchain: Blockchain,
descriptor: (InstanceId, String),
}
impl ServiceApiScope {
pub fn new(blockchain: Blockchain, instance: &InstanceDescriptor) -> Self {
Self {
inner: ApiScope::new(),
blockchain,
descriptor: (instance.id, instance.name.clone()),
}
}
pub fn endpoint<Q, I, F, R>(&mut self, name: &'static str, handler: F) -> &mut Self
where
Q: DeserializeOwned + 'static,
I: Serialize + 'static,
F: Fn(&ServiceApiState<'_>, Q) -> R + 'static + Clone + Send + Sync,
R: IntoFuture<Item = I, Error = Error> + 'static,
{
let blockchain = self.blockchain.clone();
let (instance_id, instance_name) = self.descriptor.clone();
self.inner
.endpoint(name, move |query: Q| -> FutureResult<I> {
let descriptor = InstanceDescriptor::new(instance_id, &instance_name);
let state = ServiceApiState::from_api_context(&blockchain, descriptor, name);
let result = handler(&state, query);
let instance_name = instance_name.clone();
let future = result
.into_future()
.map_err(move |err| err.source(format!("{}:{}", instance_id, instance_name)));
Box::new(future)
});
self
}
pub fn endpoint_mut<Q, I, F, R>(&mut self, name: &'static str, handler: F) -> &mut Self
where
Q: DeserializeOwned + 'static,
I: Serialize + 'static,
F: Fn(&ServiceApiState<'_>, Q) -> R + 'static + Clone + Send + Sync,
R: IntoFuture<Item = I, Error = Error> + 'static,
{
let blockchain = self.blockchain.clone();
let (instance_id, instance_name) = self.descriptor.clone();
self.inner
.endpoint_mut(name, move |query: Q| -> FutureResult<I> {
let descriptor = InstanceDescriptor::new(instance_id, &instance_name);
let state = ServiceApiState::from_api_context(&blockchain, descriptor, name);
let result = handler(&state, query);
let instance_name = instance_name.clone();
let future = result
.into_future()
.map_err(move |err| err.source(format!("{}:{}", instance_id, instance_name)));
Box::new(future)
});
self
}
pub fn deprecated_endpoint<Q, I, F, R>(
&mut self,
name: &'static str,
deprecated: Deprecated<Q, I, R, F>,
) -> &mut Self
where
Q: DeserializeOwned + 'static,
I: Serialize + 'static,
F: Fn(&ServiceApiState<'_>, Q) -> R + 'static + Clone + Send + Sync,
R: IntoFuture<Item = I, Error = Error> + 'static,
{
let blockchain = self.blockchain.clone();
let (instance_id, instance_name) = self.descriptor.clone();
let inner = deprecated.handler.clone();
let handler = move |query: Q| -> FutureResult<I> {
let descriptor = InstanceDescriptor::new(instance_id, &instance_name);
let state = ServiceApiState::from_api_context(&blockchain, descriptor, name);
let result = inner(&state, query);
let instance_name = instance_name.clone();
let future = result
.into_future()
.map_err(move |err| err.source(format!("{}:{}", instance_id, instance_name)));
Box::new(future)
};
let handler = deprecated.with_different_handler(handler);
self.inner.endpoint(name, handler);
self
}
pub fn deprecated_endpoint_mut<Q, I, F, R>(
&mut self,
name: &'static str,
deprecated: Deprecated<Q, I, R, F>,
) -> &mut Self
where
Q: DeserializeOwned + 'static,
I: Serialize + 'static,
F: Fn(&ServiceApiState<'_>, Q) -> R + 'static + Clone + Send + Sync,
R: IntoFuture<Item = I, Error = Error> + 'static,
{
let blockchain = self.blockchain.clone();
let (instance_id, instance_name) = self.descriptor.clone();
let inner = deprecated.handler.clone();
let handler = move |query: Q| -> FutureResult<I> {
let descriptor = InstanceDescriptor::new(instance_id, &instance_name);
let state = ServiceApiState::from_api_context(&blockchain, descriptor, name);
let result = inner(&state, query);
let instance_name = instance_name.clone();
let future = result
.into_future()
.map_err(move |err| err.source(format!("{}:{}", instance_id, instance_name)));
Box::new(future)
};
let handler = deprecated.with_different_handler(handler);
self.inner.endpoint_mut(name, handler);
self
}
pub fn web_backend(&mut self) -> &mut actix::ApiBuilder {
self.inner.web_backend()
}
}
#[derive(Debug)]
pub struct ServiceApiBuilder {
blockchain: Blockchain,
public_scope: ServiceApiScope,
private_scope: ServiceApiScope,
root_path: Option<String>,
}
impl ServiceApiBuilder {
#[doc(hidden)]
pub fn new(blockchain: Blockchain, instance: InstanceDescriptor) -> Self {
Self {
blockchain: blockchain.clone(),
public_scope: ServiceApiScope::new(blockchain.clone(), &instance),
private_scope: ServiceApiScope::new(blockchain, &instance),
root_path: None,
}
}
pub fn public_scope(&mut self) -> &mut ServiceApiScope {
&mut self.public_scope
}
pub fn private_scope(&mut self) -> &mut ServiceApiScope {
&mut self.private_scope
}
pub fn blockchain(&self) -> &Blockchain {
&self.blockchain
}
#[doc(hidden)]
pub fn with_root_path(&mut self, root_path: impl Into<String>) -> &mut Self {
let root_path = root_path.into();
self.root_path = Some(root_path);
self
}
pub(super) fn take_root_path(&mut self) -> Option<String> {
self.root_path.take()
}
}
impl From<ServiceApiBuilder> for ApiBuilder {
fn from(inner: ServiceApiBuilder) -> Self {
Self {
public_scope: inner.public_scope.inner,
private_scope: inner.private_scope.inner,
}
}
}