Skip to content

Commit

Permalink
feat: add liveness endpoint (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
smrz2001 authored Sep 19, 2023
1 parent 61937db commit 8e07778
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 9 deletions.
4 changes: 3 additions & 1 deletion api-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ To see how to make this your own, look here:
[README]((https://openapi-generator.tech))

- API version: 0.1.1
- Build date: 2023-09-18T11:40:22.264-06:00[America/Denver]
- Build date: 2023-09-19T15:29:43.988-04:00[America/New_York]



Expand Down Expand Up @@ -62,6 +62,7 @@ cargo run --example server
To run a client, follow one of the following simple steps:

```
cargo run --example client LivenessGet
cargo run --example client SubscribeSortKeySortValueGet
cargo run --example client VersionPost
```
Expand Down Expand Up @@ -98,6 +99,7 @@ All URIs are relative to */ceramic*
Method | HTTP request | Description
------------- | ------------- | -------------
[****](docs/default_api.md#) | **POST** /events | Creates a new event
[****](docs/default_api.md#) | **GET** /liveness | Test the liveness of the Ceramic node
[****](docs/default_api.md#) | **GET** /subscribe/{sort_key}/{sort_value} | Get events for a stream
[****](docs/default_api.md#) | **POST** /version | Get the version of the Ceramic node

Expand Down
6 changes: 6 additions & 0 deletions api-server/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ info:
servers:
- url: /ceramic
paths:
/liveness:
get:
responses:
"200":
description: success
summary: Test the liveness of the Ceramic node
/version:
post:
responses:
Expand Down
23 changes: 23 additions & 0 deletions api-server/docs/default_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ All URIs are relative to */ceramic*
Method | HTTP request | Description
------------- | ------------- | -------------
****](default_api.md#) | **POST** /events | Creates a new event
****](default_api.md#) | **GET** /liveness | Test the liveness of the Ceramic node
****](default_api.md#) | **GET** /subscribe/{sort_key}/{sort_value} | Get events for a stream
****](default_api.md#) | **POST** /version | Get the version of the Ceramic node

Expand Down Expand Up @@ -34,6 +35,28 @@ No authorization required

[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

# ****
> ()
Test the liveness of the Ceramic node

### Required Parameters
This endpoint does not need any parameter.

### Return type

(empty response body)

### Authorization

No authorization required

### HTTP request headers

- **Content-Type**: Not defined
- **Accept**: Not defined

[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

# ****
> Vec<models::Event> (sort_key, sort_value, optional)
Get events for a stream
Expand Down
12 changes: 10 additions & 2 deletions api-server/examples/client/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#[allow(unused_imports)]
use ceramic_api_server::{
models, Api, ApiNoContext, Client, ContextWrapperExt, EventsPostResponse,
models, Api, ApiNoContext, Client, ContextWrapperExt, EventsPostResponse, LivenessGetResponse,
SubscribeSortKeySortValueGetResponse, VersionPostResponse,
};
use clap::{App, Arg};
Expand Down Expand Up @@ -32,7 +32,7 @@ fn main() {
.arg(
Arg::with_name("operation")
.help("Sets the operation to run")
.possible_values(&["SubscribeSortKeySortValueGet", "VersionPost"])
.possible_values(&["LivenessGet", "SubscribeSortKeySortValueGet", "VersionPost"])
.required(true)
.index(1),
)
Expand Down Expand Up @@ -95,6 +95,14 @@ fn main() {
info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has<XSpanIdString>).get().clone());
},
*/
Some("LivenessGet") => {
let result = rt.block_on(client.liveness_get());
info!(
"{:?} (X-Span-ID: {:?})",
result,
(client.context() as &dyn Has<XSpanIdString>).get().clone()
);
}
Some("SubscribeSortKeySortValueGet") => {
let result = rt.block_on(client.subscribe_sort_key_sort_value_get(
"sort_key_example".to_string(),
Expand Down
10 changes: 9 additions & 1 deletion api-server/examples/server/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ impl<C> Server<C> {

use ceramic_api_server::server::MakeService;
use ceramic_api_server::{
Api, EventsPostResponse, SubscribeSortKeySortValueGetResponse, VersionPostResponse,
Api, EventsPostResponse, LivenessGetResponse, SubscribeSortKeySortValueGetResponse,
VersionPostResponse,
};
use std::error::Error;
use swagger::ApiError;
Expand All @@ -126,6 +127,13 @@ where
Err(ApiError("Generic failure".into()))
}

/// Test the liveness of the Ceramic node
async fn liveness_get(&self, context: &C) -> Result<LivenessGetResponse, ApiError> {
let context = context.clone();
info!("liveness_get() - X-Span-ID: {:?}", context.get().0.clone());
Err(ApiError("Generic failure".into()))
}

/// Get events for a stream
async fn subscribe_sort_key_sort_value_get(
&self,
Expand Down
73 changes: 72 additions & 1 deletion api-server/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS
#[allow(dead_code)]
const ID_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'|');

use crate::{Api, EventsPostResponse, SubscribeSortKeySortValueGetResponse, VersionPostResponse};
use crate::{
Api, EventsPostResponse, LivenessGetResponse, SubscribeSortKeySortValueGetResponse,
VersionPostResponse,
};

/// Convert input into a base path, e.g. "http://example:123". Also checks the scheme as it goes.
fn into_base_path(
Expand Down Expand Up @@ -476,6 +479,74 @@ where
}
}

async fn liveness_get(&self, context: &C) -> Result<LivenessGetResponse, ApiError> {
let mut client_service = self.client_service.clone();
let mut uri = format!("{}/ceramic/liveness", self.base_path);

// Query parameters
let query_string = {
let mut query_string = form_urlencoded::Serializer::new("".to_owned());
query_string.finish()
};
if !query_string.is_empty() {
uri += "?";
uri += &query_string;
}

let uri = match Uri::from_str(&uri) {
Ok(uri) => uri,
Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))),
};

let mut request = match Request::builder()
.method("GET")
.uri(uri)
.body(Body::empty())
{
Ok(req) => req,
Err(e) => return Err(ApiError(format!("Unable to create request: {}", e))),
};

let header = HeaderValue::from_str(Has::<XSpanIdString>::get(context).0.as_str());
request.headers_mut().insert(
HeaderName::from_static("x-span-id"),
match header {
Ok(h) => h,
Err(e) => {
return Err(ApiError(format!(
"Unable to create X-Span ID header value: {}",
e
)))
}
},
);

let response = client_service
.call((request, context.clone()))
.map_err(|e| ApiError(format!("No response received: {}", e)))
.await?;

match response.status().as_u16() {
200 => Ok(LivenessGetResponse::Success),
code => {
let headers = response.headers().clone();
let body = response.into_body().take(100).into_raw().await;
Err(ApiError(format!(
"Unexpected response code {}:\n{:?}\n\n{}",
code,
headers,
match body {
Ok(body) => match String::from_utf8(body) {
Ok(body) => body,
Err(e) => format!("<Body was not UTF8: {:?}>", e),
},
Err(e) => format!("<Failed to read body: {}>", e),
}
)))
}
}
}

async fn subscribe_sort_key_sort_value_get(
&self,
param_sort_key: String,
Expand Down
18 changes: 18 additions & 0 deletions api-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ pub enum EventsPostResponse {
Success,
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum LivenessGetResponse {
/// success
Success,
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub enum SubscribeSortKeySortValueGetResponse {
/// success
Expand Down Expand Up @@ -58,6 +64,9 @@ pub trait Api<C: Send + Sync> {
context: &C,
) -> Result<EventsPostResponse, ApiError>;

/// Test the liveness of the Ceramic node
async fn liveness_get(&self, context: &C) -> Result<LivenessGetResponse, ApiError>;

/// Get events for a stream
async fn subscribe_sort_key_sort_value_get(
&self,
Expand Down Expand Up @@ -88,6 +97,9 @@ pub trait ApiNoContext<C: Send + Sync> {
/// Creates a new event
async fn events_post(&self, event: models::Event) -> Result<EventsPostResponse, ApiError>;

/// Test the liveness of the Ceramic node
async fn liveness_get(&self) -> Result<LivenessGetResponse, ApiError>;

/// Get events for a stream
async fn subscribe_sort_key_sort_value_get(
&self,
Expand Down Expand Up @@ -134,6 +146,12 @@ impl<T: Api<C> + Send + Sync, C: Clone + Send + Sync> ApiNoContext<C> for Contex
self.api().events_post(event, &context).await
}

/// Test the liveness of the Ceramic node
async fn liveness_get(&self) -> Result<LivenessGetResponse, ApiError> {
let context = self.context().clone();
self.api().liveness_get(&context).await
}

/// Get events for a stream
async fn subscribe_sort_key_sort_value_get(
&self,
Expand Down
48 changes: 45 additions & 3 deletions api-server/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,26 @@ pub use crate::context;

type ServiceFuture = BoxFuture<'static, Result<Response<Body>, crate::ServiceError>>;

use crate::{Api, EventsPostResponse, SubscribeSortKeySortValueGetResponse, VersionPostResponse};
use crate::{
Api, EventsPostResponse, LivenessGetResponse, SubscribeSortKeySortValueGetResponse,
VersionPostResponse,
};

mod paths {
use lazy_static::lazy_static;

lazy_static! {
pub static ref GLOBAL_REGEX_SET: regex::RegexSet = regex::RegexSet::new(vec![
r"^/ceramic/events$",
r"^/ceramic/liveness$",
r"^/ceramic/subscribe/(?P<sort_key>[^/?#]*)/(?P<sort_value>[^/?#]*)$",
r"^/ceramic/version$"
])
.expect("Unable to create global regex set");
}
pub(crate) static ID_EVENTS: usize = 0;
pub(crate) static ID_SUBSCRIBE_SORT_KEY_SORT_VALUE: usize = 1;
pub(crate) static ID_LIVENESS: usize = 1;
pub(crate) static ID_SUBSCRIBE_SORT_KEY_SORT_VALUE: usize = 2;
lazy_static! {
pub static ref REGEX_SUBSCRIBE_SORT_KEY_SORT_VALUE: regex::Regex =
#[allow(clippy::invalid_regex)]
Expand All @@ -44,7 +49,7 @@ mod paths {
)
.expect("Unable to create regex for SUBSCRIBE_SORT_KEY_SORT_VALUE");
}
pub(crate) static ID_VERSION: usize = 2;
pub(crate) static ID_VERSION: usize = 3;
}

pub struct MakeService<T, C>
Expand Down Expand Up @@ -230,6 +235,40 @@ where
}
}

// LivenessGet - GET /liveness
hyper::Method::GET if path.matched(paths::ID_LIVENESS) => {
let result = api_impl.liveness_get(&context).await;
let mut response = Response::new(Body::empty());
response.headers_mut().insert(
HeaderName::from_static("x-span-id"),
HeaderValue::from_str(
(&context as &dyn Has<XSpanIdString>)
.get()
.0
.clone()
.as_str(),
)
.expect("Unable to create X-Span-ID header value"),
);

match result {
Ok(rsp) => match rsp {
LivenessGetResponse::Success => {
*response.status_mut() = StatusCode::from_u16(200)
.expect("Unable to turn 200 into a StatusCode");
}
},
Err(_) => {
// Application code returned an error. This should not happen, as the implementation should
// return a valid response.
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
*response.body_mut() = Body::from("An internal error occurred");
}
}

Ok(response)
}

// SubscribeSortKeySortValueGet - GET /subscribe/{sort_key}/{sort_value}
hyper::Method::GET if path.matched(paths::ID_SUBSCRIBE_SORT_KEY_SORT_VALUE) => {
// Path parameters
Expand Down Expand Up @@ -439,6 +478,7 @@ where
}

_ if path.matched(paths::ID_EVENTS) => method_not_allowed(),
_ if path.matched(paths::ID_LIVENESS) => method_not_allowed(),
_ if path.matched(paths::ID_SUBSCRIBE_SORT_KEY_SORT_VALUE) => method_not_allowed(),
_ if path.matched(paths::ID_VERSION) => method_not_allowed(),
_ => Ok(Response::builder()
Expand All @@ -459,6 +499,8 @@ impl<T> RequestParser<T> for ApiRequestParser {
match *request.method() {
// EventsPost - POST /events
hyper::Method::POST if path.matched(paths::ID_EVENTS) => Some("EventsPost"),
// LivenessGet - GET /liveness
hyper::Method::GET if path.matched(paths::ID_LIVENESS) => Some("LivenessGet"),
// SubscribeSortKeySortValueGet - GET /subscribe/{sort_key}/{sort_value}
hyper::Method::GET if path.matched(paths::ID_SUBSCRIBE_SORT_KEY_SORT_VALUE) => {
Some("SubscribeSortKeySortValueGet")
Expand Down
6 changes: 6 additions & 0 deletions api/ceramic.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ servers:
- url: /ceramic

paths:
/liveness:
get:
summary: Test the liveness of the Ceramic node
responses:
"200":
description: success
/version:
post:
summary: Get the version of the Ceramic node
Expand Down
7 changes: 6 additions & 1 deletion api/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ use tracing::{debug, info};

use ceramic_api_server::{
models::{self, Event},
EventsPostResponse, SubscribeSortKeySortValueGetResponse, VersionPostResponse,
EventsPostResponse, LivenessGetResponse, SubscribeSortKeySortValueGetResponse,
VersionPostResponse,
};
use ceramic_core::{EventId, Interest, Network, PeerId, StreamId};

Expand Down Expand Up @@ -106,6 +107,10 @@ where
I: Recon<Key = Interest> + Sync,
M: Recon<Key = EventId> + Sync,
{
async fn liveness_get(&self, _: &C) -> std::result::Result<LivenessGetResponse, ApiError> {
Ok(LivenessGetResponse::Success)
}

async fn version_post(&self, _context: &C) -> Result<VersionPostResponse, ApiError> {
let resp = VersionPostResponse::Success(models::Version {
version: Some(ceramic_metadata::Version::default().version),
Expand Down

0 comments on commit 8e07778

Please sign in to comment.