---
# System prepended metadata

title: bee-api
tags: [' draft', RFC]

---

---
title: bee-api
tags: RFC, draft
---

# bee-api

+ Feature name: `api`
+ Start date: 2020-07-14
+ RFC PR: [iotaledger/bee-rfcs#0000](https://github.com/iotaledger/bee-rfcs/pull/0000)
+ Bee issue: [iotaledger/bee#0000](https://github.com/iotaledger/bee/issues/0000)

# Summary

This RFC proposes a crate that provides interfaces and conversions to support presentation layers such as web, mobile, or UI.

# Motivation

To be able to program applications that interface with the Bee node, an Application Programming Interface (API) is needed. This RFC does focus on a high-level interaction with the Bee node.

# Detailed design

The design describes an layered approach that separates the `Service` from the presentation layer.


## Service

The `Service` trait defines all the services that a Bee node exposes. It hides internal implementation and changes, furthermore governs external access to data and functions. It can support multiple presentation layers such as web, mobile, UI, etc. 
Since it hides internal logic and governs access, it helps maintain the layers above.
The `Service` trait is defined as follows:

```
pub trait Service {
    fn node_info() -> NodeInfoResponse;
    fn transactions_by_bundle(params: TransactionsByBundleParams) -> Result<TransactionsByBundleResponse, ServiceError>;
    fn transaction_by_hash(params: TransactionByHashParams) -> TransactionByHashResponse;
    ...
}
```

With function parameters and return-values like:

```
pub struct NodeInfoResponse {
    pub is_synced: bool,
    pub last_milestone_index: MilestoneIndex,
    pub last_milestone_hash: Option<Hash>,
    ...
}

pub struct TransactionsByBundleParams {
    pub entry: Hash,
    pub bundle: Hash,
}

pub struct TransactionsByBundleResponse {
    pub tx_refs: HashMap<Hash, TransactionRef>,
}

pub struct TransactionByHashParams {
    pub hash: Hash,
}

pub struct TransactionByHashResponse {
    pub tx_ref: Option<TransactionRef>,
}
...
```

Since some service functions might return an error, e.g. when illegal parameters are provided or when an underlying function call returns in an error, some functions might return `Result<Response, ServiceError>`.

## Format

Service functions might be called in different contexts. For example, a REST endpoint might want to call a specific service function but is provided with JSON data. In contrast, an XML-based API is supplied with XML-formatted input data.
To be able to call the functions of the`Service` trait, input data needs to be converted.
A conversion from `JSON` to `TransactionsByBundleParams` could be defined as follows:
```
impl TryFrom<&JsonValue> for TransactionsByBundleParams {
    type Error = &'static str;
    fn try_from(value: &JsonValue) -> Result<Self, Self::Error> {
        let entry = match value["entry"].as_str() {
            Some(str) => HashItem::try_from(str)?.0,
            None => return Err("can not find entry hash"),
        };
        let bundle = match value["bundle"].as_str() {
            Some(str) => HashItem::try_from(str)?.0,
            None => return Err("can not find bundle hash"),
        };
        Ok(TransactionsByBundleParams { entry, bundle })
    }
}
```
The same applies to responses - a conversion back from `TransactionsByBundleResponse` to `JSON` could be defined like this:
```
impl From<TransactionsByBundleResponse> for JsonValue {
    fn from(res: TransactionsByBundleResponse) -> Self {
        let mut json_obj = Map::new();
        for (hash, tx_ref) in res.tx_refs.iter() {
            json_obj.insert(
                String::from(&HashItem(hash.clone())),
                JsonValue::from(&TransactionRefItem(tx_ref.clone())),
            );
        }
        JsonValue::Object(json_obj)
    }
}
```

Notable is the use of `HashItem` or `TransactionRefItem` in the conversions. These wrapper types are used to be conform with Rust's orphan rules. Since multiple services use the `Hash` type within parameters/response, conversions should be reusable.
For this reason, conversions should be defined as `TryFrom`/`From`s.
```
pub struct HashItem(pub Hash);

impl TryFrom<&str> for HashItem {
    type Error = &'static str;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        ...
    }
}

impl From<&HashItem> for String {
    fn from(value: &HashItem) -> Self {
        ...
    }
}

impl From<&HashItem> for JsonValue {
    fn from(value: &HashItem) -> Self {
        JsonValue::String(String::from(value))
    }
}
```

Some types require more conversion work than others. For consistency - if desired - `TryFrom`/`From`s could be defined for all inner types of a request/response. An example that requires less conversion work, but still might get its `From` implementation:
```
pub struct MilestoneIndexItem(pub MilestoneIndex);

impl From<&MilestoneIndexItem> for JsonValue {
    fn from(value: &MilestoneIndexItem) -> Self {
        JsonValue::from(*value.0)
    }
}
```

## WebService trait

The `WebService` trait specifies which endpoints a Web service should implement. This also means, not all functions of the `Service` trait need to be exposed.

```
#[async_trait]
pub trait WebService {
    type Input;
    type Output;

    async fn node_info() -> Self::Output;
    async fn transactions_by_bundle(input: Self::Input) -> Self::Output;
    async fn transaction_by_hash(input: Self::Input) -> Self::Output;
    async fn transactions_by_hashes(input: Self::Input) -> Self::Output;
}
```

The associated types are remarkable. Since some Web services might require a certain function signature, this flexibility is required.
In the case of `warp` the `WebService` d implemented as follows:
```
#[async_trait]
impl WebService for Rest {
    type Input = JsonValue;
    type Output = Result<warp::reply::Json, warp::Rejection>;

    async fn node_info() -> Self::Output {
        Ok(warp::reply::json(&JsonValue::from(ServiceImpl::node_info())))
    }
    ...
}
```

These implemented functions then can be easily injected into the Web service.
In the case of `warp` it would look like this:
```
warp::get()
    .and(warp::path("v1"))
    .and(warp::path("node-info"))
    .and(warp::path::end())
    .and_then(routes::Rest::node_info);
```

# Drawbacks

No drawbacks known to the author.

# Rationale and alternatives

No viable alternatives known to the author.

# Unresolved questions

- Should function params be created with a `new` constructor instead? And eventually return an error already there when illegal parameters where passed, instead in the service?
- For consistency, should there be a `TryFrom`/`From` for every type in a request/response? Or should only conversions that require more work be reused and therefore get a `TryFrom`/`From` implementation?