use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ValueType {
String,
Number,
Boolean,
Date,
Object,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ProofRung {
Predicate,
Derived,
SelectiveDisclosure,
Whole,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum Provenance {
SelfAsserted,
#[serde(rename_all = "camelCase")]
CredentialBacked {
credential_id: String,
claim_path: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
issuer_did: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
proof: Option<ProofRung>,
},
#[serde(rename_all = "camelCase")]
Generated {
generator: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
per_verifier: Option<bool>,
},
#[serde(rename_all = "camelCase")]
Derived {
source: String,
derived_at: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct OverrideValue {
pub value: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct InlineValue {
#[serde(rename = "type")]
pub claim_type: String,
pub value: Value,
pub value_type: ValueType,
pub provenance: Provenance,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged, deny_unknown_fields)]
pub enum ProfileEntry {
Ref {
#[serde(rename = "ref")]
attribute_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
slot: Option<String>,
},
#[serde(rename_all = "camelCase")]
Pinned {
#[serde(rename = "ref")]
attribute_id: String,
pin_version: ::std::num::NonZeroU64,
#[serde(default, skip_serializing_if = "Option::is_none")]
slot: Option<String>,
},
Override {
#[serde(rename = "ref")]
attribute_id: String,
#[serde(rename = "override")]
override_value: OverrideValue,
#[serde(default, skip_serializing_if = "Option::is_none")]
slot: Option<String>,
},
Inline {
inline: InlineValue,
#[serde(default, skip_serializing_if = "Option::is_none")]
slot: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct LocalProfileEntry {
pub inline: InlineValue,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub slot: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaAttributePutBody {
#[serde(rename = "type")]
pub claim_type: String,
pub value: Value,
pub value_type: ValueType,
pub provenance: Provenance,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub endorsements: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attribute_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expected_version: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaAttributeListBody {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub type_prefix: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_values: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_sensitive: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_stale: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<::std::num::NonZeroU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaAttributeDeleteBody {
pub attribute_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cascade: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expected_version: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaProfilePutBody {
pub name: String,
pub entries: Vec<ProfileEntry>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub credential_refs: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reach: Option<trust_tasks_rs::specs::persona::profile::put::v1_0::FaceReach>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expected_version: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaProfileGetBody {
pub profile_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resolve: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaProfileListBody {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<::std::num::NonZeroU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_retired: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaProfileDeleteBody {
pub profile_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub unbind: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expected_version: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaBindingSetBody {
pub context_id: String,
pub persona_did: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile_id: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub public_entries: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub until: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expected_version: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaBindingGetBody {
pub context_id: String,
pub persona_did: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaBindingListBody {
pub context_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<::std::num::NonZeroU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContactClaim {
#[serde(rename = "type")]
pub claim_type: String,
pub value: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value_type: Option<ValueType>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provenance: Option<Provenance>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContactDocument {
pub claims: Vec<ContactClaim>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub card_version: Option<::std::num::NonZeroU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub publisher: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaContactPutBody {
pub context_id: String,
pub subject_did: String,
pub known_by_persona: String,
pub document: ContactDocument,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub credential_refs: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaContactGetBody {
pub context_id: String,
pub contact_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rev: Option<::std::num::NonZeroU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub include_history: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaContactListBody {
pub context_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub known_by_persona: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub changed_since: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<::std::num::NonZeroU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaContactDeleteBody {
pub context_id: String,
pub contact_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaDisclosurePreviewBody {
pub context_id: String,
pub persona_did: String,
pub verifier_did: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub requested_claims: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub purpose: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub renderer: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaDisclosurePresentBody {
pub context_id: String,
pub preview_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub challenge: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mint: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaDisclosureHistoryBody {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub context_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub verifier_did: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attribute_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub since: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<::std::num::NonZeroU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaCorrelationAnalyzeBody {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attribute_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub candidate: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaRenderersListBody {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaLocalProfilePutBody {
pub context_id: String,
pub name: String,
pub entries: Vec<LocalProfileEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expected_version: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaLocalProfileGetBody {
pub context_id: String,
pub profile_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaLocalProfileListBody {
pub context_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<::std::num::NonZeroU64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaLocalProfileDeleteBody {
pub context_id: String,
pub profile_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub unbind: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PersonaLocalBindingSetBody {
pub context_id: String,
pub persona_did: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub until: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expected_version: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ext: Option<Value>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_profile_entry_form_survives_a_round_trip() {
let cases = [
(
"ref",
serde_json::json!({ "ref": "01J0000000000000000000000A" }),
),
(
"pinned",
serde_json::json!({
"ref": "01J0000000000000000000000A",
"pinVersion": 3,
}),
),
(
"override",
serde_json::json!({
"ref": "01J0000000000000000000000A",
"override": { "value": "+61 400 000 000" },
}),
),
(
"inline",
serde_json::json!({
"inline": {
"type": "name.display",
"value": "Ada",
"valueType": "string",
"provenance": { "kind": "selfAsserted" },
}
}),
),
(
"ref+slot",
serde_json::json!({ "ref": "01J0000000000000000000000A", "slot": "displayName" }),
),
(
"pinned+slot",
serde_json::json!({ "ref": "01J0000000000000000000000A", "pinVersion": 3, "slot": "primaryPhone" }),
),
(
"override+slot",
serde_json::json!({
"ref": "01J0000000000000000000000A",
"override": { "value": "Mickey" },
"slot": "displayName",
}),
),
(
"inline+slot",
serde_json::json!({
"inline": {
"type": "name.display",
"value": "Donald",
"valueType": "string",
"provenance": { "kind": "selfAsserted" },
},
"slot": "displayName",
}),
),
];
for (label, doc) in cases {
let parsed: ProfileEntry =
serde_json::from_value(doc.clone()).unwrap_or_else(|e| panic!("{label}: {e}"));
let back = serde_json::to_value(&parsed).expect("re-encodes");
assert_eq!(back, doc, "{label} form did not survive the round trip");
}
}
#[test]
fn a_pin_does_not_collapse_into_a_bare_reference() {
let doc = serde_json::json!({
"ref": "01J0000000000000000000000A",
"pinVersion": 7,
});
let parsed: ProfileEntry = serde_json::from_value(doc).expect("parses");
assert!(
matches!(parsed, ProfileEntry::Pinned { pin_version, .. } if pin_version.get() == 7),
"a pinned entry parsed as {parsed:?}"
);
}
#[test]
fn a_local_entry_cannot_reference_the_pool() {
let doc = serde_json::json!({ "ref": "01J0000000000000000000000A" });
serde_json::from_value::<LocalProfileEntry>(doc)
.expect_err("a local entry must not accept a pool reference");
}
#[test]
fn credential_backed_provenance_needs_its_credential() {
serde_json::from_value::<Provenance>(serde_json::json!({ "kind": "credentialBacked" }))
.expect_err("credentialBacked without a credentialId must not parse");
}
}