Skip to content

Commit

Permalink
feat: add manual schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
daniel-mader committed Dec 3, 2024
1 parent 91fd64e commit 3e97659
Show file tree
Hide file tree
Showing 13 changed files with 121 additions and 35 deletions.
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions agent_api_rest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ tower-http.workspace = true
tracing.workspace = true
url.workspace = true
uuid.workspace = true
utoipa = { version = "=5.0.0-beta.0", features = ["axum_extras", "yaml"] }
utoipa-scalar = { version = "=0.2.0-beta.0", features = ["axum"] }
utoipa = { version = "5.2", features = ["axum_extras", "yaml"] }
utoipa-scalar = { version = "0.2", features = ["axum"] }
# TODO: wait for new release that contains PR juhaku/utoipa#1002 (current version `=5.0.0-alpha.1`)
# utoipa = { git = "https://github.com/juhaku/utoipa.git", rev = "f2a7143", features = [
# "axum_extras",
Expand All @@ -44,11 +44,15 @@ utoipa-scalar = { version = "=0.2.0-beta.0", features = ["axum"] }
# ] }

[dev-dependencies]
agent_event_publisher_http = { path = "../agent_event_publisher_http", features = ["test_utils"] }
agent_event_publisher_http = { path = "../agent_event_publisher_http", features = [
"test_utils",
] }
agent_holder = { path = "../agent_holder", features = ["test_utils"] }
agent_identity = { path = "../agent_identity", features = ["test_utils"] }
agent_issuance = { path = "../agent_issuance", features = ["test_utils"] }
agent_secret_manager = { path = "../agent_secret_manager", features = ["test_utils"] }
agent_secret_manager = { path = "../agent_secret_manager", features = [
"test_utils",
] }
agent_shared = { path = "../agent_shared", features = ["test_utils"] }
agent_store = { path = "../agent_store" }
agent_verification = { path = "../agent_verification", features = [
Expand Down
4 changes: 4 additions & 0 deletions agent_api_rest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ The current version of the REST API is `v0`.
> [!NOTE]
> UniCore uses [Scalar](https://scalar.com) to make its OpenAPI specification interactive. It is served under `/<BASE_PATH>/<API_VERSION>/api-reference` (for example: `/v0/api-reference`). The `openapi.yaml` file can be downloaded there as well. The latest version of the `openapi.yaml` file is also deployed as part of the documentation at https://docs.impierce.com/unicore/api-reference.
#### Generate OpenAPI specification

The `openapi.yaml` file can be generated manually by executing the test `generate_openapi_file()` _(ignored in a regular test run)_.

#### Swagger UI

You can also run a local Swagger UI container to inspect the OpenAPI specification.
Expand Down
18 changes: 16 additions & 2 deletions agent_api_rest/src/holder/openid4vci/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,21 @@ use serde_json::Value;
use tracing::info;
use utoipa::ToSchema;

#[derive(Deserialize, Serialize, ToSchema)]
#[derive(ToSchema)]
#[schema(as = CredentialOffer)]
pub struct CredentialOfferSchema {
pub foo: String,
pub bar: i32,
}

#[derive(ToSchema)]
#[schema(as = Oid4vciOfferEndpointRequest)]
pub struct Oid4vciOfferEndpointRequestSchema {
pub foo: String,
pub bar: i32,
}

#[derive(Deserialize, Serialize)]
pub struct Oid4vciOfferEndpointRequest {
#[serde(flatten)]
pub credential_offer: CredentialOffer,
Expand All @@ -26,7 +40,7 @@ pub struct Oid4vciOfferEndpointRequest {
#[utoipa::path(
get,
path = "/openid4vci/offers",
request_body = Oid4vciOfferEndpointRequest,
request_body = Oid4vciOfferEndpointRequestSchema,
tag = "Holder",
tags = ["(public)"],
responses(
Expand Down
21 changes: 18 additions & 3 deletions agent_api_rest/src/issuance/credential_issuer/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,29 @@ use axum::{
response::{IntoResponse, Response},
};
use axum_auth::AuthBearer;
use oid4vci::credential_request::CredentialRequest;
use oid4vci::{credential_request::CredentialRequest, credential_response::CredentialResponse};
use serde_json::json;
use tokio::time::sleep;
use tracing::{error, info};
use utoipa::ToSchema;

const DEFAULT_EXTERNAL_SERVER_RESPONSE_TIMEOUT_MS: u64 = 1000;
const POLLING_INTERVAL_MS: u64 = 100;

#[derive(ToSchema)]
#[schema(as = CredentialRequest)]
pub struct CredentialRequestSchema {
pub foo: String,
pub bar: i32,
}

#[derive(ToSchema)]
#[schema(as = CredentialResponse)]
pub struct CredentialResponseSchema {
pub foo: String,
pub bar: i32,
}

/// Credential Endpoint
///
/// Standard OpenID4VCI endpoint for redeeming a token for a credential.
Expand All @@ -32,11 +47,11 @@ const POLLING_INTERVAL_MS: u64 = 100;
path = "/openid4vci/credential",
// TODO: doesn't work since (external) `CredentialRequest` doesn't implement `ToSchema`?
// See: https://github.com/juhaku/utoipa?tab=readme-ov-file#how-to-implement-toschema-for-external-type
request_body = CredentialRequest,
request_body = CredentialRequestSchema,
tag = "Issuance",
tags = ["(public)"],
responses(
(status = 200, description = "Successfully returns the credential", body = [CredentialResponse])
(status = 200, description = "Successfully returns the credential", body = [CredentialResponseSchema])
)
)]
#[axum_macros::debug_handler]
Expand Down
9 changes: 9 additions & 0 deletions agent_api_rest/src/issuance/credential_issuer/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,22 @@ use axum::{
use oid4vci::token_request::TokenRequest;
use serde_json::json;
use tracing::info;
use utoipa::ToSchema;

#[derive(ToSchema)]
#[schema(as = TokenRequest)]
pub struct TokenRequestSchema {
pub foo: String,
pub bar: i32,
}

/// Token Endpoint
///
/// Standard OAuth 2.0 endpoint that returns an access_token after successful authorization.
#[utoipa::path(
post,
path = "/auth/token",
request_body = TokenRequestSchema,
tags = ["(public)"],
responses(
(status = 200, description = "Returns an access token."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use oid4vci::credential_issuer::authorization_server_metadata::AuthorizationServerMetadata;
use utoipa::ToSchema;

#[derive(ToSchema)]
#[schema(as = AuthorizationServerMetadata)]
pub struct AuthorizationServerMetadataSchema {
pub foo: String,
pub bar: i32,
}

/// Authorization Server Metadata
///
Expand All @@ -18,7 +27,7 @@ use axum::{
tag = "(.well-known)",
tags = ["(public)"],
responses(
(status = 200, description = "Successfully returns the Authorization Server Metadata", body = [AuthorizationServerMetadata])
(status = 200, description = "Successfully returns the Authorization Server Metadata", body = [AuthorizationServerMetadataSchema])
)
)]
#[axum_macros::debug_handler]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use oid4vci::credential_issuer::credential_issuer_metadata::CredentialIssuerMetadata;
use utoipa::ToSchema;

#[derive(ToSchema)]
#[schema(as = CredentialIssuerMetadata)]
pub struct CredentialIssuerMetadataSchema {
pub foo: String,
pub bar: i32,
}

/// Credential Issuer Metadata
///
Expand All @@ -18,7 +27,7 @@ use axum::{
tag = "(.well-known)",
tags = ["(public)"],
responses(
(status = 200, description = "Successfully returns the Credential Issuer Metadata", body = [CredentialIssuerMetadata])
(status = 200, description = "Successfully returns the Credential Issuer Metadata", body = [CredentialIssuerMetadataSchema])
)
)]
#[axum_macros::debug_handler]
Expand Down
11 changes: 9 additions & 2 deletions agent_api_rest/src/issuance/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ use serde_json::Value;
use tracing::info;
use utoipa::ToSchema;

#[derive(ToSchema)]
#[schema(as = CredentialView)]
pub struct CredentialViewSchema {
pub foo: String,
pub bar: i32,
}

/// Retrieve a credential
///
/// Retrieves an existing credential by its ID.
Expand All @@ -30,7 +37,7 @@ use utoipa::ToSchema;
("id" = String, Path, description = "Unique identifier of the Credential", example = "0001"),
),
responses(
(status = 200, description = "Credential found", body = [CredentialView])
(status = 200, description = "Credential found", body = [CredentialViewSchema])
)
)]
#[axum_macros::debug_handler]
Expand Down Expand Up @@ -66,7 +73,7 @@ pub struct CredentialsEndpointRequest {
),
tag = "Issuance",
responses(
(status = 201, description = "Successfully created a new credential.", body = CredentialView,
(status = 201, description = "Successfully created a new credential.", body = CredentialViewSchema,
headers(("Location" = String, description = "URL of the created resource")),
examples(
("w3c-vc-1-1" = (summary = "W3C VC Data Model v1.1", description = "A credential following the W3C Verifiable Credentials Data Model v1.1", value = json!({"offerId": "0001"}))),
Expand Down
10 changes: 8 additions & 2 deletions agent_api_rest/src/issuance/offers/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ use tracing::info;
use url::Url;
use utoipa::ToSchema;

#[derive(Deserialize, Serialize, ToSchema)]
#[derive(ToSchema)]
pub struct SendOfferEndpointRequestSchema {
pub offer_id: String,
pub target_url: String,
}

#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SendOfferEndpointRequest {
pub offer_id: String,
Expand All @@ -25,7 +31,7 @@ pub struct SendOfferEndpointRequest {
#[utoipa::path(
post,
path = "/offers/send",
request_body(content = SendOfferEndpointRequest, example = json!({"offerId": "0001", "targetUrl": "https://wallet.example.com/openid4vci/offers"})),
request_body(content = SendOfferEndpointRequestSchema, example = json!({"offerId": "0001", "targetUrl": "https://wallet.example.com/openid4vci/offers"})),
tag = "Issuance",
responses(
(status = 200, description = "Successfully sent credential offer to Holder."),
Expand Down
19 changes: 12 additions & 7 deletions agent_api_rest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use axum::{body::Bytes, extract::MatchedPath, http::Request, response::Response,
use std::time::Duration;
use tower_http::{cors::CorsLayer, trace::TraceLayer};
use tracing::{info, info_span, Span};
use utoipa::OpenApi;
use utoipa::{openapi::HttpMethod, OpenApi};
use utoipa_scalar::{Scalar, Servable};

use crate::openapi::{did_configuration, did_web, HolderApi, IssuanceApi, VerificationApi};
Expand Down Expand Up @@ -107,7 +107,7 @@ fn get_base_path() -> Result<String, ConfigError> {
})
}

#[derive(utoipa::OpenApi)]
#[derive(OpenApi)]
#[openapi(
// modifiers(),
paths(
Expand All @@ -117,7 +117,7 @@ fn get_base_path() -> Result<String, ConfigError> {
crate::verification::relying_party::request::request,
crate::issuance::credential_issuer::token::token,
// OpenID4VCI
crate::holder::openid4vci::offers,
crate::holder::openid4vci::offers_params,
crate::issuance::credential_issuer::credential::credential,
// .well-known
crate::issuance::credential_issuer::well_known::oauth_authorization_server::oauth_authorization_server,
Expand Down Expand Up @@ -149,10 +149,14 @@ pub fn patch_generated_openapi(mut openapi: utoipa::openapi::OpenApi) -> utoipa:
// .build()]
// .into();
// Append endpoints defined outside of `agent_api_rest`.
openapi.paths.add_path("/.well-known/did.json", did_web());
openapi
.paths
.add_path("/.well-known/did-configuration.json", did_configuration());
// openapi
// .paths
// .add_path_operation("/.well-known/did.json", vec![HttpMethod::Get], did_web);

// openapi.paths.add_path("/.well-known/did.json", did_web());
// openapi
// .paths
// .add_path("/.well-known/did-configuration.json", did_configuration());
openapi
}

Expand Down Expand Up @@ -215,6 +219,7 @@ mod tests {
async fn handler() {}

#[tokio::test]
#[ignore = "Execute manually to generate the openapi.yaml file"]
async fn generate_openapi_file() {
let yaml_value = patch_generated_openapi(ApiDoc::openapi());
let yaml_string = serde_yaml::to_string(&yaml_value).unwrap();
Expand Down
17 changes: 10 additions & 7 deletions agent_api_rest/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,23 @@ use crate::verification::authorization_requests;
#[openapi(
paths(
credentials::credentials,
credentials::get_credentials,
holder::credentials::credentials,
offers::offers,
offers::send::send
),
components(schemas(
credentials::CredentialsEndpointRequest,
offers::OffersEndpointRequest,
offers::send::SendOfferEndpointRequest
offers::send::SendOfferEndpointRequestSchema
))
)]
pub(crate) struct IssuanceApi;

#[derive(OpenApi)]
#[openapi(
paths(
authorization_requests::authorization_requests,
authorization_requests::get_authorization_requests
authorization_requests::all_authorization_requests,
authorization_requests::authorization_requests
),
components(schemas(authorization_requests::AuthorizationRequestsEndpointRequest))
)]
Expand All @@ -42,7 +42,7 @@ pub(crate) struct VerificationApi;
holder::offers::accept::accept,
holder::offers::reject::reject
),
components(schemas(openid4vci::Oid4vciOfferEndpointRequest))
components(schemas(openid4vci::Oid4vciOfferEndpointRequestSchema))
)]
pub(crate) struct HolderApi;

Expand All @@ -56,7 +56,10 @@ pub(crate) fn did_web() -> PathItem {
"200",
ResponseBuilder::new()
.description("DID Document for `did:web` method")
.content("application/json", Content::new(Ref::from_schema_name("CoreDocument"))),
.content(
"application/json",
Content::new(Some(Ref::from_schema_name("CoreDocument"))),
),
)
.response("404", Response::new("DID method `did:web` inactive.")),
)
Expand All @@ -79,7 +82,7 @@ pub(crate) fn did_configuration() -> PathItem {
.description("DID Configuration Resource")
.content(
"application/json",
Content::new(Ref::from_schema_name("DomainLinkageConfiguration")),
Content::new(Some(Ref::from_schema_name("DomainLinkageConfiguration"))),
// Content::new(
// ObjectBuilder::new()
// .schema_type(SchemaType::Type(schema::Type::Object))
Expand Down
Loading

0 comments on commit 3e97659

Please sign in to comment.