diff --git a/Cargo.lock b/Cargo.lock index da9b6933..9a7105df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8149,9 +8149,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utoipa" -version = "5.0.0-beta.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fac56d240b49c629b9083c932ac20a23d926937e67c21ba209f836e2983d4f" +checksum = "514a48569e4e21c86d0b84b5612b5e73c0b2cf09db63260134ba426d4e8ea714" dependencies = [ "indexmap 2.5.0", "serde", @@ -8162,9 +8162,9 @@ dependencies = [ [[package]] name = "utoipa-gen" -version = "5.0.0-beta.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31d88270777931b8133b119c953062bd41665bb8507841f7d433f46d2765e9d4" +checksum = "5629efe65599d0ccd5d493688cbf6e03aa7c1da07fe59ff97cf5977ed0637f66" dependencies = [ "proc-macro2", "quote", @@ -8174,9 +8174,9 @@ dependencies = [ [[package]] name = "utoipa-scalar" -version = "0.2.0-beta.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc86065a210b8540e46d15e0844765d1d14eec7fd6221c2b0de8f6edde990648" +checksum = "c1291aa7a2223c2f8399d1c6627ca0ba57ca0d7ecac762a2094a9dfd6376445a" dependencies = [ "axum 0.7.5", "serde", diff --git a/agent_api_rest/Cargo.toml b/agent_api_rest/Cargo.toml index 987c83ea..193e3f3e 100644 --- a/agent_api_rest/Cargo.toml +++ b/agent_api_rest/Cargo.toml @@ -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", @@ -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 = [ diff --git a/agent_api_rest/README.md b/agent_api_rest/README.md index d70cdadc..3952caf3 100644 --- a/agent_api_rest/README.md +++ b/agent_api_rest/README.md @@ -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 `///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. diff --git a/agent_api_rest/src/holder/openid4vci/mod.rs b/agent_api_rest/src/holder/openid4vci/mod.rs index ec0644e5..f51a0104 100644 --- a/agent_api_rest/src/holder/openid4vci/mod.rs +++ b/agent_api_rest/src/holder/openid4vci/mod.rs @@ -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, @@ -26,7 +40,7 @@ pub struct Oid4vciOfferEndpointRequest { #[utoipa::path( get, path = "/openid4vci/offers", - request_body = Oid4vciOfferEndpointRequest, + request_body = Oid4vciOfferEndpointRequestSchema, tag = "Holder", tags = ["(public)"], responses( diff --git a/agent_api_rest/src/issuance/credential_issuer/credential.rs b/agent_api_rest/src/issuance/credential_issuer/credential.rs index 05dc2f31..116b88f4 100644 --- a/agent_api_rest/src/issuance/credential_issuer/credential.rs +++ b/agent_api_rest/src/issuance/credential_issuer/credential.rs @@ -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. @@ -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] diff --git a/agent_api_rest/src/issuance/credential_issuer/token.rs b/agent_api_rest/src/issuance/credential_issuer/token.rs index c6fd8092..dde7c340 100644 --- a/agent_api_rest/src/issuance/credential_issuer/token.rs +++ b/agent_api_rest/src/issuance/credential_issuer/token.rs @@ -12,6 +12,14 @@ 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 /// @@ -19,6 +27,7 @@ use tracing::info; #[utoipa::path( post, path = "/auth/token", + request_body = TokenRequestSchema, tags = ["(public)"], responses( (status = 200, description = "Returns an access token."), diff --git a/agent_api_rest/src/issuance/credential_issuer/well_known/oauth_authorization_server.rs b/agent_api_rest/src/issuance/credential_issuer/well_known/oauth_authorization_server.rs index d99ba393..52c8735c 100644 --- a/agent_api_rest/src/issuance/credential_issuer/well_known/oauth_authorization_server.rs +++ b/agent_api_rest/src/issuance/credential_issuer/well_known/oauth_authorization_server.rs @@ -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 /// @@ -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] diff --git a/agent_api_rest/src/issuance/credential_issuer/well_known/openid_credential_issuer.rs b/agent_api_rest/src/issuance/credential_issuer/well_known/openid_credential_issuer.rs index c95bcd9b..0ea586e5 100644 --- a/agent_api_rest/src/issuance/credential_issuer/well_known/openid_credential_issuer.rs +++ b/agent_api_rest/src/issuance/credential_issuer/well_known/openid_credential_issuer.rs @@ -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 /// @@ -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] diff --git a/agent_api_rest/src/issuance/credentials.rs b/agent_api_rest/src/issuance/credentials.rs index 176f243a..2d87c89f 100644 --- a/agent_api_rest/src/issuance/credentials.rs +++ b/agent_api_rest/src/issuance/credentials.rs @@ -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. @@ -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] @@ -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"}))), diff --git a/agent_api_rest/src/issuance/offers/send.rs b/agent_api_rest/src/issuance/offers/send.rs index 10f9b0a4..a8a79a11 100644 --- a/agent_api_rest/src/issuance/offers/send.rs +++ b/agent_api_rest/src/issuance/offers/send.rs @@ -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, @@ -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."), diff --git a/agent_api_rest/src/lib.rs b/agent_api_rest/src/lib.rs index ee736862..3ee5dde2 100644 --- a/agent_api_rest/src/lib.rs +++ b/agent_api_rest/src/lib.rs @@ -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}; @@ -107,7 +107,7 @@ fn get_base_path() -> Result { }) } -#[derive(utoipa::OpenApi)] +#[derive(OpenApi)] #[openapi( // modifiers(), paths( @@ -117,7 +117,7 @@ fn get_base_path() -> Result { 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, @@ -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 } @@ -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(); diff --git a/agent_api_rest/src/openapi.rs b/agent_api_rest/src/openapi.rs index 36a9dcf7..47a0fc82 100644 --- a/agent_api_rest/src/openapi.rs +++ b/agent_api_rest/src/openapi.rs @@ -12,14 +12,14 @@ 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; @@ -27,8 +27,8 @@ 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)) )] @@ -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; @@ -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.")), ) @@ -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)) diff --git a/agent_api_rest/src/verification/authorization_requests.rs b/agent_api_rest/src/verification/authorization_requests.rs index f6006d74..5c97d236 100644 --- a/agent_api_rest/src/verification/authorization_requests.rs +++ b/agent_api_rest/src/verification/authorization_requests.rs @@ -5,6 +5,7 @@ use agent_shared::{ }; use agent_verification::{ authorization_request::{command::AuthorizationRequestCommand, views::AuthorizationRequestView}, + generic_oid4vc::GenericAuthorizationRequest, state::VerificationState, }; use axum::{