From 8e07778c4c804c6b48e143222e5b4a4161b6c8e6 Mon Sep 17 00:00:00 2001 From: Mohsin Zaidi <2236875+smrz2001@users.noreply.github.com> Date: Tue, 19 Sep 2023 16:38:52 -0400 Subject: [PATCH] feat: add liveness endpoint (#127) --- api-server/README.md | 4 +- api-server/api/openapi.yaml | 6 +++ api-server/docs/default_api.md | 23 +++++++++ api-server/examples/client/main.rs | 12 ++++- api-server/examples/server/server.rs | 10 +++- api-server/src/client/mod.rs | 73 +++++++++++++++++++++++++++- api-server/src/lib.rs | 18 +++++++ api-server/src/server/mod.rs | 48 ++++++++++++++++-- api/ceramic.yaml | 6 +++ api/src/server.rs | 7 ++- 10 files changed, 198 insertions(+), 9 deletions(-) diff --git a/api-server/README.md b/api-server/README.md index b50164761..425bb3a28 100644 --- a/api-server/README.md +++ b/api-server/README.md @@ -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] @@ -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 ``` @@ -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 diff --git a/api-server/api/openapi.yaml b/api-server/api/openapi.yaml index 3e9b4c2f5..ad9256312 100644 --- a/api-server/api/openapi.yaml +++ b/api-server/api/openapi.yaml @@ -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: diff --git a/api-server/docs/default_api.md b/api-server/docs/default_api.md index 60c995b61..9ac9ca080 100644 --- a/api-server/docs/default_api.md +++ b/api-server/docs/default_api.md @@ -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 @@ -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 (sort_key, sort_value, optional) Get events for a stream diff --git a/api-server/examples/client/main.rs b/api-server/examples/client/main.rs index 1dcd1ca49..b5598ba86 100644 --- a/api-server/examples/client/main.rs +++ b/api-server/examples/client/main.rs @@ -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}; @@ -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), ) @@ -95,6 +95,14 @@ fn main() { info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has).get().clone()); }, */ + Some("LivenessGet") => { + let result = rt.block_on(client.liveness_get()); + info!( + "{:?} (X-Span-ID: {:?})", + result, + (client.context() as &dyn Has).get().clone() + ); + } Some("SubscribeSortKeySortValueGet") => { let result = rt.block_on(client.subscribe_sort_key_sort_value_get( "sort_key_example".to_string(), diff --git a/api-server/examples/server/server.rs b/api-server/examples/server/server.rs index c005c0347..9378df25a 100644 --- a/api-server/examples/server/server.rs +++ b/api-server/examples/server/server.rs @@ -101,7 +101,8 @@ impl Server { 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; @@ -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 { + 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, diff --git a/api-server/src/client/mod.rs b/api-server/src/client/mod.rs index be5d644de..375e33c3b 100644 --- a/api-server/src/client/mod.rs +++ b/api-server/src/client/mod.rs @@ -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( @@ -476,6 +479,74 @@ where } } + async fn liveness_get(&self, context: &C) -> Result { + 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::::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!("", e), + }, + Err(e) => format!("", e), + } + ))) + } + } + } + async fn subscribe_sort_key_sort_value_get( &self, param_sort_key: String, diff --git a/api-server/src/lib.rs b/api-server/src/lib.rs index f69f16966..f98b4d0df 100644 --- a/api-server/src/lib.rs +++ b/api-server/src/lib.rs @@ -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 @@ -58,6 +64,9 @@ pub trait Api { context: &C, ) -> Result; + /// Test the liveness of the Ceramic node + async fn liveness_get(&self, context: &C) -> Result; + /// Get events for a stream async fn subscribe_sort_key_sort_value_get( &self, @@ -88,6 +97,9 @@ pub trait ApiNoContext { /// Creates a new event async fn events_post(&self, event: models::Event) -> Result; + /// Test the liveness of the Ceramic node + async fn liveness_get(&self) -> Result; + /// Get events for a stream async fn subscribe_sort_key_sort_value_get( &self, @@ -134,6 +146,12 @@ impl + Send + Sync, C: Clone + Send + Sync> ApiNoContext for Contex self.api().events_post(event, &context).await } + /// Test the liveness of the Ceramic node + async fn liveness_get(&self) -> Result { + 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, diff --git a/api-server/src/server/mod.rs b/api-server/src/server/mod.rs index e83834133..7da330f56 100644 --- a/api-server/src/server/mod.rs +++ b/api-server/src/server/mod.rs @@ -21,7 +21,10 @@ pub use crate::context; type ServiceFuture = BoxFuture<'static, Result, crate::ServiceError>>; -use crate::{Api, EventsPostResponse, SubscribeSortKeySortValueGetResponse, VersionPostResponse}; +use crate::{ + Api, EventsPostResponse, LivenessGetResponse, SubscribeSortKeySortValueGetResponse, + VersionPostResponse, +}; mod paths { use lazy_static::lazy_static; @@ -29,13 +32,15 @@ mod paths { lazy_static! { pub static ref GLOBAL_REGEX_SET: regex::RegexSet = regex::RegexSet::new(vec![ r"^/ceramic/events$", + r"^/ceramic/liveness$", r"^/ceramic/subscribe/(?P[^/?#]*)/(?P[^/?#]*)$", 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)] @@ -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 @@ -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) + .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 @@ -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() @@ -459,6 +499,8 @@ impl RequestParser 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") diff --git a/api/ceramic.yaml b/api/ceramic.yaml index 19dd02eb1..b50411cf0 100644 --- a/api/ceramic.yaml +++ b/api/ceramic.yaml @@ -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 diff --git a/api/src/server.rs b/api/src/server.rs index a46778090..6930151ef 100644 --- a/api/src/server.rs +++ b/api/src/server.rs @@ -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}; @@ -106,6 +107,10 @@ where I: Recon + Sync, M: Recon + Sync, { + async fn liveness_get(&self, _: &C) -> std::result::Result { + Ok(LivenessGetResponse::Success) + } + async fn version_post(&self, _context: &C) -> Result { let resp = VersionPostResponse::Success(models::Version { version: Some(ceramic_metadata::Version::default().version),