baseid_anoncreds/
schema.rs1use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6#[derive(Debug, Error)]
8pub enum SchemaError {
9 #[error("Schema name must not be empty / Le nom du schéma ne peut pas être vide")]
11 EmptyName,
12
13 #[error("Schema must have at least one attribute / Le schéma doit avoir au moins un attribut")]
15 EmptyAttributes,
16
17 #[error(
19 "Invalid schema version '{0}' — expected MAJOR.MINOR / Version de schéma invalide '{0}'"
20 )]
21 InvalidVersion(String),
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26pub struct Schema {
27 pub id: String,
29 pub name: String,
31 pub version: String,
33 pub attr_names: Vec<String>,
35}
36
37impl Schema {
38 pub fn new(
44 id: impl Into<String>,
45 name: impl Into<String>,
46 version: impl Into<String>,
47 attr_names: Vec<String>,
48 ) -> Result<Self, SchemaError> {
49 let name = name.into();
50 let version = version.into();
51
52 let schema = Self {
53 id: id.into(),
54 name,
55 version,
56 attr_names,
57 };
58 schema.validate()?;
59 Ok(schema)
60 }
61
62 pub fn validate(&self) -> Result<(), SchemaError> {
64 if self.name.trim().is_empty() {
65 return Err(SchemaError::EmptyName);
66 }
67 if self.attr_names.is_empty() {
68 return Err(SchemaError::EmptyAttributes);
69 }
70 if !is_valid_version(&self.version) {
71 return Err(SchemaError::InvalidVersion(self.version.clone()));
72 }
73 Ok(())
74 }
75
76 pub fn contains_attribute(&self, attr: &str) -> bool {
78 self.attr_names.iter().any(|a| a == attr)
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84pub struct CredentialDefinition {
85 pub id: String,
87 pub schema_id: String,
89 pub tag: String,
91 #[serde(default = "default_signature_type")]
93 pub signature_type: String,
94}
95
96fn default_signature_type() -> String {
97 "CL".to_string()
98}
99
100impl CredentialDefinition {
101 pub fn new(
103 id: impl Into<String>,
104 schema_id: impl Into<String>,
105 tag: impl Into<String>,
106 ) -> Self {
107 Self {
108 id: id.into(),
109 schema_id: schema_id.into(),
110 tag: tag.into(),
111 signature_type: "CL".to_string(),
112 }
113 }
114
115 pub fn supports_schema(&self, schema_id: &str) -> bool {
117 self.schema_id == schema_id
118 }
119}
120
121fn is_valid_version(v: &str) -> bool {
123 let parts: Vec<&str> = v.split('.').collect();
124 if parts.len() != 2 {
125 return false;
126 }
127 parts[0].parse::<u32>().is_ok() && parts[1].parse::<u32>().is_ok()
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 #[test]
135 fn schema_create_valid() {
136 let schema = Schema::new(
137 "did:sov:NcYxiDXkpYi6ov5FcYDi1e:2:degree:1.0",
138 "degree",
139 "1.0",
140 vec![
141 "name".to_string(),
142 "degree".to_string(),
143 "university".to_string(),
144 ],
145 )
146 .unwrap();
147 assert_eq!(schema.name, "degree");
148 assert_eq!(schema.attr_names.len(), 3);
149 }
150
151 #[test]
152 fn schema_validate_empty_name() {
153 let result = Schema::new("id", "", "1.0", vec!["a".to_string()]);
154 assert!(result.is_err());
155 }
156
157 #[test]
158 fn schema_validate_empty_attrs() {
159 let result = Schema::new("id", "degree", "1.0", vec![]);
160 assert!(result.is_err());
161 }
162
163 #[test]
164 fn schema_validate_invalid_version() {
165 let result = Schema::new("id", "degree", "abc", vec!["a".to_string()]);
166 assert!(result.is_err());
167 }
168
169 #[test]
170 fn schema_contains_attribute() {
171 let schema = Schema::new(
172 "id",
173 "degree",
174 "1.0",
175 vec!["name".to_string(), "degree".to_string()],
176 )
177 .unwrap();
178 assert!(schema.contains_attribute("name"));
179 assert!(!schema.contains_attribute("age"));
180 }
181
182 #[test]
183 fn schema_serde_roundtrip() {
184 let schema = Schema::new(
185 "did:sov:NcYxiDXkpYi6ov5FcYDi1e:2:degree:1.0",
186 "degree",
187 "1.0",
188 vec!["name".to_string(), "degree".to_string()],
189 )
190 .unwrap();
191 let json = serde_json::to_string(&schema).unwrap();
192 let deserialized: Schema = serde_json::from_str(&json).unwrap();
193 assert_eq!(schema, deserialized);
194 }
195
196 #[test]
197 fn cred_def_create_and_supports_schema() {
198 let cred_def = CredentialDefinition::new(
199 "did:sov:NcYxiDXkpYi6ov5FcYDi1e:3:CL:12:default",
200 "did:sov:NcYxiDXkpYi6ov5FcYDi1e:2:degree:1.0",
201 "default",
202 );
203 assert_eq!(cred_def.signature_type, "CL");
204 assert!(cred_def.supports_schema("did:sov:NcYxiDXkpYi6ov5FcYDi1e:2:degree:1.0"));
205 assert!(!cred_def.supports_schema("other:schema"));
206 }
207
208 #[test]
209 fn cred_def_serde_roundtrip() {
210 let cred_def = CredentialDefinition::new("id", "schema-id", "tag");
211 let json = serde_json::to_string(&cred_def).unwrap();
212 let deserialized: CredentialDefinition = serde_json::from_str(&json).unwrap();
213 assert_eq!(cred_def, deserialized);
214 }
215}