Skip to content

Commit

Permalink
feat(cat-gateway): Add document endpoints (#1470)
Browse files Browse the repository at this point in the history
* feat(cat-gateway): Add document get endpoint

* feat(cat-gateway): Add generic cbor payload type

* fix(docs): spelling

* fix(cat-gateway): code format

* feat(cat-gateway): Add document put endpoint

* fix(cat-gateway): fix documentation for the error responses.

* feat(general): Bump cat-ci to v3.2.31 to get documentation updates

* feat(docs): Add alternative OpenAPI viewers because Elements has issues

* feat(cat-gateway): Add document index endpoint (request side only)

* feat(cat-gateway): Add response type for document index endpoint

* fix(cat-gateway): code format

* fix api lint errors

* fix(cat-gateway): Response Documentation consistency for document endpoints

* feat: increase time schemathesis waits for schema

---------

Co-authored-by: kukkok3 <[email protected]>
  • Loading branch information
stevenj and kukkok3 authored Jan 15, 2025
1 parent e169a08 commit f9eca94
Show file tree
Hide file tree
Showing 48 changed files with 1,891 additions and 36 deletions.
2 changes: 2 additions & 0 deletions .config/dictionaries/project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ adminer
afinet
androidx
anypolicy
apirequest
appbar
appspot
Arbritrary
Expand Down Expand Up @@ -319,6 +320,7 @@ Unstaked
upskilling
UTXO
Utxos
uuidv
varint
Vespr
vite
Expand Down
6 changes: 3 additions & 3 deletions Earthfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
VERSION 0.8

IMPORT github.com/input-output-hk/catalyst-ci/earthly/mdlint:v3.2.27 AS mdlint-ci
IMPORT github.com/input-output-hk/catalyst-ci/earthly/cspell:v3.2.27 AS cspell-ci
IMPORT github.com/input-output-hk/catalyst-ci/earthly/postgresql:v3.2.27 AS postgresql-ci
IMPORT github.com/input-output-hk/catalyst-ci/earthly/mdlint:v3.2.31 AS mdlint-ci
IMPORT github.com/input-output-hk/catalyst-ci/earthly/cspell:v3.2.31 AS cspell-ci
IMPORT github.com/input-output-hk/catalyst-ci/earthly/postgresql:v3.2.31 AS postgresql-ci

FROM debian:stable-slim

Expand Down
2 changes: 1 addition & 1 deletion catalyst-gateway/Earthfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
VERSION 0.8

IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust:v3.2.28 AS rust-ci
IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust:v3.2.31 AS rust-ci

#cspell: words rustfmt toolsets USERARCH stdcfgs

Expand Down
6 changes: 4 additions & 2 deletions catalyst-gateway/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ rust_decimal = { version = "1.36.0", features = [
"serde-with-float",
"db-tokio-postgres",
] }
poem = { version = "3.1.3", features = ["embed", "prometheus", "compression"] }
poem-openapi = { version = "5.1.2", features = [
poem = { version = "3.1.6", features = ["embed", "prometheus", "compression"] }
poem-openapi = { version = "5.1.5", features = [
"openapi-explorer",
"rapidoc",
"redoc",
Expand All @@ -96,6 +96,8 @@ bech32 = "0.11.0"
const_format = "0.2.33"
regex = "1.11.1"
minijinja = "2.5.0"
bytes = "1.9.0"
mime = "0.3.17"

[dev-dependencies]
proptest = "1.5.0"
Expand Down
34 changes: 34 additions & 0 deletions catalyst-gateway/bin/src/service/api/documents/get_document.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//! Implementation of the GET `/document` endpoint
use poem::Body;
use poem_openapi::ApiResponse;

use crate::service::common::{responses::WithErrorResponses, types::payload::cbor::Cbor};

/// Endpoint responses.
#[derive(ApiResponse)]
#[allow(dead_code)]
pub(crate) enum Responses {
/// ## OK
///
/// The Document that was requested.
#[oai(status = 200)]
Ok(Cbor<Body>),
/// ## Not Found
///
/// The document could not be found.
#[oai(status = 404)]
NotFound,
}

/// All responses.
pub(crate) type AllResponses = WithErrorResponses<Responses>;

/// # GET `/document`
#[allow(clippy::unused_async, clippy::no_effect_underscore_binding)]
pub(crate) async fn endpoint(document_id: uuid::Uuid, version: Option<uuid::Uuid>) -> AllResponses {
let _doc = document_id;
let _ver = version;

Responses::NotFound.into()
}
113 changes: 113 additions & 0 deletions catalyst-gateway/bin/src/service/api/documents/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//! Signed Documents API endpoints
use anyhow::anyhow;
use poem::{error::ReadBodyError, Body};
use poem_openapi::{
param::{Path, Query},
payload::Json,
OpenApi,
};
use post_document_index_query::query_filter::DocumentIndexQueryFilterBody;
use put_document::{bad_put_request::PutDocumentBadRequest, MAXIMUM_DOCUMENT_SIZE};

use crate::service::{
common::{
self,
auth::{none_or_rbac::NoneOrRBAC, rbac::scheme::CatalystRBACSecurityScheme},
tags::ApiTags,
types::{generic::uuidv7::UUIDv7, payload::cbor::Cbor},
},
utilities::middleware::schema_validation::schema_version_validation,
};

mod get_document;
mod post_document_index_query;
mod put_document;

/// Cardano Follower API Endpoints
pub(crate) struct DocumentApi;

#[OpenApi(tag = "ApiTags::Documents")]
impl DocumentApi {
/// Get A Signed Document.
///
/// This endpoint returns either a specific or latest version of a registered signed
/// document.
#[oai(
path = "/draft/document/:document_id",
method = "get",
operation_id = "getDocument",
transform = "schema_version_validation"
)]
async fn get_document(
&self, /// UUIDv7 Document ID to retrieve
document_id: Path<UUIDv7>,
/// UUIDv7 Version of the Document to retrieve, if omitted, returns the latest
/// version.
version: Query<Option<UUIDv7>>,
/// No Authorization required, but Token permitted.
_auth: NoneOrRBAC,
) -> get_document::AllResponses {
let Ok(doc_id) = document_id.0.try_into() else {
let err = anyhow!("Invalid UUIDv7"); // Should not happen as UUIDv7 is validating.
return get_document::AllResponses::internal_error(&err);
};
let Ok(ver_id) = version.0.map(std::convert::TryInto::try_into).transpose() else {
let err = anyhow!("Invalid UUIDv7"); // Should not happen as UUIDv7 is validating.
return get_document::AllResponses::internal_error(&err);
};
get_document::endpoint(doc_id, ver_id).await
}

/// Put A Signed Document.
///
/// This endpoint returns OK if the document is valid, able to be put by the
/// submitter, and if it already exists, is identical to the existing document.
#[oai(
path = "/draft/document",
method = "put",
operation_id = "putDocument",
transform = "schema_version_validation"
)]
async fn put_document(
&self, /// The document to PUT
document: Cbor<Body>,
/// Authorization required.
_auth: CatalystRBACSecurityScheme,
) -> put_document::AllResponses {
match document.0.into_bytes_limit(MAXIMUM_DOCUMENT_SIZE).await {
Ok(document) => put_document::endpoint(document).await,
Err(ReadBodyError::PayloadTooLarge) => put_document::Responses::PayloadTooLarge.into(),
Err(_err) => {
put_document::Responses::BadRequest(Json(PutDocumentBadRequest::new(
"Failed to read document from the request",
)))
.into()
},
}
}

/// Post A Signed Document Index Query.
///
/// This endpoint produces a summary of signed documents that meet the criteria
/// defined in the request body.
///
/// It does not return the actual documents, just an index of the document identifiers
/// which allows the documents to be retrieved by the `GET document` endpoint.
#[oai(
path = "/draft/document/index",
method = "post",
operation_id = "postDocument",
transform = "schema_version_validation"
)]
async fn post_document(
&self, /// The Query Filter Specification
query: Json<DocumentIndexQueryFilterBody>,
page: Query<Option<common::types::generic::query::pagination::Page>>,
limit: Query<Option<common::types::generic::query::pagination::Limit>>,
/// Authorization required.
_auth: CatalystRBACSecurityScheme,
) -> post_document_index_query::AllResponses {
post_document_index_query::endpoint(query.0 .0, page.0, limit.0).await
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//! Document Index Query
use poem_openapi::{payload::Json, ApiResponse, Object};
use query_filter::DocumentIndexQueryFilter;
use response::DocumentIndexListDocumented;

use super::common;
use crate::service::common::responses::WithErrorResponses;

pub(crate) mod query_filter;
pub(crate) mod response;

/// Endpoint responses.
#[derive(ApiResponse)]
#[allow(dead_code)]
pub(crate) enum Responses {
/// ## OK
///
/// The Index of documents which match the query filter.
#[oai(status = 200)]
Ok(Json<DocumentIndexListDocumented>),
/// ## Not Found
///
/// No documents were found which match the query filter.
#[oai(status = 404)]
NotFound,
}

/// All responses.
pub(crate) type AllResponses = WithErrorResponses<Responses>;

/// Update user schema
#[derive(Debug, Object, Clone, Eq, PartialEq)]
pub(crate) struct QueryDocumentIndex {
/// Name
name: Option<String>,
}

/// # POST `/document/index`
#[allow(clippy::unused_async, clippy::no_effect_underscore_binding)]
pub(crate) async fn endpoint(
filter: DocumentIndexQueryFilter,
page: Option<common::types::generic::query::pagination::Page>,
limit: Option<common::types::generic::query::pagination::Limit>,
) -> AllResponses {
let _filter = filter;
let _page = page;
let _limit = limit;

// We return this when the filter results in no documents found.
Responses::NotFound.into()
}
Loading

0 comments on commit f9eca94

Please sign in to comment.