1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct TokenRequest {
8 pub grant_type: String,
10 #[serde(rename = "pre-authorized_code")]
12 pub pre_authorized_code: String,
13 #[serde(skip_serializing_if = "Option::is_none")]
15 pub tx_code: Option<String>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct CredentialRequest {
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub credential_configuration_id: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub credential_identifier: Option<String>,
31 #[serde(skip_serializing_if = "Option::is_none")]
34 pub format: Option<String>,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub credential_definition: Option<serde_json::Value>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub proof: Option<ProofOfPossession>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct ProofOfPossession {
46 pub proof_type: String,
48 pub jwt: String,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct CredentialEntry {
55 pub credential: serde_json::Value,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct CredentialResponse {
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub credentials: Option<Vec<CredentialEntry>>,
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub transaction_id: Option<String>,
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub notification_id: Option<String>,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub interval: Option<u64>,
77}
78
79impl CredentialResponse {
80 pub fn first_credential(&self) -> Option<&serde_json::Value> {
82 self.credentials
83 .as_ref()
84 .and_then(|creds| creds.first())
85 .map(|entry| &entry.credential)
86 }
87
88 pub fn is_deferred(&self) -> bool {
90 self.transaction_id.is_some()
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96pub enum CredentialFormat {
97 #[serde(rename = "jwt_vc_json")]
99 JwtVcJson,
100 #[serde(rename = "ldp_vc")]
102 LdpVc,
103 #[serde(rename = "dc+sd-jwt")]
105 DcSdJwt,
106 #[serde(rename = "mso_mdoc")]
108 MsoMdoc,
109 #[serde(rename = "jwt_vc_json-ld")]
111 JwtVcJsonLd,
112 #[serde(untagged)]
114 Other(String),
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
119pub enum ServerErrorCode {
120 #[serde(rename = "invalid_credential_request")]
121 InvalidCredentialRequest,
122 #[serde(rename = "unknown_credential_configuration")]
123 UnknownCredentialConfiguration,
124 #[serde(rename = "unknown_credential_identifier")]
125 UnknownCredentialIdentifier,
126 #[serde(rename = "invalid_proof")]
127 InvalidProof,
128 #[serde(rename = "invalid_nonce")]
129 InvalidNonce,
130 #[serde(rename = "invalid_encryption_parameters")]
131 InvalidEncryptionParameters,
132 #[serde(rename = "credential_request_denied")]
133 CredentialRequestDenied,
134 #[serde(untagged)]
136 Other(String),
137}
138
139pub const PRE_AUTHORIZED_CODE_GRANT_TYPE: &str =
141 "urn:ietf:params:oauth:grant-type:pre-authorized_code";
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn token_request_serialization() {
149 let req = TokenRequest {
150 grant_type: PRE_AUTHORIZED_CODE_GRANT_TYPE.to_string(),
151 pre_authorized_code: "code123".to_string(),
152 tx_code: Some("1234".to_string()),
153 };
154 let json = serde_json::to_value(&req).unwrap();
155 assert_eq!(
156 json["grant_type"],
157 "urn:ietf:params:oauth:grant-type:pre-authorized_code"
158 );
159 assert_eq!(json["pre-authorized_code"], "code123");
160 assert_eq!(json["tx_code"], "1234");
161 }
162
163 #[test]
164 fn credential_request_with_configuration_id() {
165 let req = CredentialRequest {
166 credential_configuration_id: Some("UniversityDegree".to_string()),
167 credential_identifier: None,
168 format: None,
169 credential_definition: None,
170 proof: None,
171 };
172 let json = serde_json::to_value(&req).unwrap();
173 assert_eq!(json["credential_configuration_id"], "UniversityDegree");
174 assert!(json.get("format").is_none());
175 assert!(json.get("proof").is_none());
176 }
177
178 #[test]
179 fn credential_request_with_identifier() {
180 let req = CredentialRequest {
181 credential_configuration_id: None,
182 credential_identifier: Some("CivilEngineeringDegree-2023".to_string()),
183 format: None,
184 credential_definition: None,
185 proof: None,
186 };
187 let json = serde_json::to_value(&req).unwrap();
188 assert_eq!(json["credential_identifier"], "CivilEngineeringDegree-2023");
189 assert!(json.get("credential_configuration_id").is_none());
190 }
191
192 #[test]
193 fn credential_response_immediate() {
194 let json = serde_json::json!({
195 "credentials": [
196 { "credential": "eyJhbGciOiJFUzI1NiJ9..." }
197 ]
198 });
199 let resp: CredentialResponse = serde_json::from_value(json).unwrap();
200 assert!(!resp.is_deferred());
201 assert_eq!(
202 resp.first_credential().unwrap(),
203 &serde_json::json!("eyJhbGciOiJFUzI1NiJ9...")
204 );
205 }
206
207 #[test]
208 fn credential_response_multiple_with_notification() {
209 let json = serde_json::json!({
210 "credentials": [
211 { "credential": "cred-1" },
212 { "credential": "cred-2" }
213 ],
214 "notification_id": "3fwe98js"
215 });
216 let resp: CredentialResponse = serde_json::from_value(json).unwrap();
217 let creds = resp.credentials.as_ref().unwrap();
218 assert_eq!(creds.len(), 2);
219 assert_eq!(creds[1].credential, "cred-2");
220 assert_eq!(resp.notification_id.as_deref(), Some("3fwe98js"));
221 }
222
223 #[test]
224 fn credential_response_deferred() {
225 let json = serde_json::json!({
226 "transaction_id": "8xLOxBtZp8",
227 "interval": 3600
228 });
229 let resp: CredentialResponse = serde_json::from_value(json).unwrap();
230 assert!(resp.is_deferred());
231 assert_eq!(resp.transaction_id.as_deref(), Some("8xLOxBtZp8"));
232 assert_eq!(resp.interval, Some(3600));
233 assert!(resp.first_credential().is_none());
234 }
235
236 #[test]
239 fn spec_vector_credential_response_immediate() {
240 let resp: CredentialResponse =
241 serde_json::from_str(baseid_test_vectors::oid4vci::CREDENTIAL_RESPONSE_IMMEDIATE)
242 .unwrap();
243 assert!(!resp.is_deferred());
244 let cred = resp.first_credential().unwrap();
245 assert!(cred.as_str().unwrap().contains("LUpixVCWJk0"));
246 }
247
248 #[test]
249 fn spec_vector_credential_response_multiple() {
250 let resp: CredentialResponse =
251 serde_json::from_str(baseid_test_vectors::oid4vci::CREDENTIAL_RESPONSE_MULTIPLE)
252 .unwrap();
253 let creds = resp.credentials.as_ref().unwrap();
254 assert_eq!(creds.len(), 2);
255 assert_eq!(resp.notification_id.as_deref(), Some("3fwe98js"));
256 }
257
258 #[test]
259 fn spec_vector_credential_response_deferred() {
260 let resp: CredentialResponse =
261 serde_json::from_str(baseid_test_vectors::oid4vci::CREDENTIAL_RESPONSE_DEFERRED)
262 .unwrap();
263 assert!(resp.is_deferred());
264 assert_eq!(resp.transaction_id.as_deref(), Some("8xLOxBtZp8"));
265 assert_eq!(resp.interval, Some(3600));
266 }
267
268 #[test]
269 fn spec_vector_credential_request_mdoc() {
270 let v: serde_json::Value =
271 serde_json::from_str(baseid_test_vectors::oid4vci::CREDENTIAL_REQUEST_MDOC).unwrap();
272 assert_eq!(v["credential_configuration_id"], "org.iso.18013.5.1.mDL");
273 assert!(v["proofs"]["jwt"].is_array());
274 }
275
276 #[test]
277 fn spec_vector_credential_request_by_identifier() {
278 let v: serde_json::Value =
279 serde_json::from_str(baseid_test_vectors::oid4vci::CREDENTIAL_REQUEST_BY_IDENTIFIER)
280 .unwrap();
281 assert_eq!(v["credential_identifier"], "CivilEngineeringDegree-2023");
282 }
283
284 #[test]
285 fn spec_vector_proof_jwt_header() {
286 let v: serde_json::Value =
287 serde_json::from_str(baseid_test_vectors::oid4vci::PROOF_JWT_HEADER).unwrap();
288 assert_eq!(v["typ"], "openid4vci-proof+jwt");
289 assert_eq!(v["alg"], "ES256");
290 assert_eq!(v["jwk"]["kty"], "EC");
291 assert_eq!(v["jwk"]["crv"], "P-256");
292 }
293
294 #[test]
295 fn spec_vector_proof_jwt_payload() {
296 let v: serde_json::Value =
297 serde_json::from_str(baseid_test_vectors::oid4vci::PROOF_JWT_PAYLOAD).unwrap();
298 assert_eq!(v["aud"], "https://credential-issuer.example.com");
299 assert!(v["iat"].is_u64());
300 assert_eq!(v["nonce"], "LarRGSbmUPYtRYO6BQ4yn8");
301 }
302
303 #[test]
304 fn spec_vector_nonce_response() {
305 use crate::token::NonceResponse;
306 let resp: NonceResponse =
307 serde_json::from_str(baseid_test_vectors::oid4vci::NONCE_RESPONSE).unwrap();
308 assert_eq!(resp.c_nonce, "wKI4LT17ac15ES9bw8ac4");
309 }
310
311 #[test]
312 fn spec_vector_credential_error() {
313 let v: serde_json::Value =
314 serde_json::from_str(baseid_test_vectors::oid4vci::CREDENTIAL_ERROR).unwrap();
315 assert_eq!(v["error"], "unknown_credential_configuration");
316 }
317
318 #[test]
319 fn spec_vector_token_error() {
320 let v: serde_json::Value =
321 serde_json::from_str(baseid_test_vectors::oid4vci::TOKEN_ERROR).unwrap();
322 assert_eq!(v["error"], "invalid_grant");
323 }
324
325 #[test]
328 fn credential_format_serde_known() {
329 let formats = vec![
330 (CredentialFormat::JwtVcJson, "\"jwt_vc_json\""),
331 (CredentialFormat::LdpVc, "\"ldp_vc\""),
332 (CredentialFormat::DcSdJwt, "\"dc+sd-jwt\""),
333 (CredentialFormat::MsoMdoc, "\"mso_mdoc\""),
334 (CredentialFormat::JwtVcJsonLd, "\"jwt_vc_json-ld\""),
335 ];
336 for (variant, expected) in formats {
337 let json = serde_json::to_string(&variant).unwrap();
338 assert_eq!(json, expected);
339 let parsed: CredentialFormat = serde_json::from_str(&json).unwrap();
340 assert_eq!(parsed, variant);
341 }
342 }
343
344 #[test]
345 fn credential_format_serde_unknown() {
346 let unknown: CredentialFormat = serde_json::from_str("\"new_format\"").unwrap();
347 assert_eq!(unknown, CredentialFormat::Other("new_format".to_string()));
348 let json = serde_json::to_string(&unknown).unwrap();
349 assert_eq!(json, "\"new_format\"");
350 }
351
352 #[test]
353 fn server_error_code_serde_known() {
354 let code: ServerErrorCode =
355 serde_json::from_str("\"unknown_credential_configuration\"").unwrap();
356 assert_eq!(code, ServerErrorCode::UnknownCredentialConfiguration);
357 let json = serde_json::to_string(&code).unwrap();
358 assert_eq!(json, "\"unknown_credential_configuration\"");
359 }
360
361 #[test]
362 fn server_error_code_serde_all_variants() {
363 for code_str in baseid_test_vectors::oid4vci::CREDENTIAL_ERROR_CODES {
364 let code: ServerErrorCode = serde_json::from_str(&format!("\"{}\"", code_str)).unwrap();
365 let json = serde_json::to_string(&code).unwrap();
366 assert_eq!(json, format!("\"{}\"", code_str));
367 }
368 }
369}