1use schemars::generate::SchemaSettings;
14use schemars::transform::{Transform, transform_subschemas};
15use schemars::{Schema, SchemaGenerator, json_schema};
16use serde_json::Value;
17
18use super::{
19 AttestParams, AttestSlotParams, CheckValueParams, ClockCheckParams, ConfirmParams,
20 EncryptDataParams, EnterShareParams, EntryParams, GatherEntropyParams, GenerateCsrParams,
21 GenerateKeyParams, ImportKeyParams, IssueCertificateParams, MachineInfoParams,
22 OralReadbackParams, PivReadCertificateParams, PivSignParams, RevealParams, SignDataParams,
23 SplitSecretParams, UnwrapKeyParams, VerifySignatureParams, WrapKeyParams,
24};
25use crate::{ActionType, Format};
26
27pub const KEYWORDS: &[&str] = &[
33 "type",
34 "enum",
35 "const",
36 "pattern",
37 "minimum",
38 "maximum",
39 "minItems",
40 "items",
41 "properties",
42 "required",
43 "additionalProperties",
44 "allOf",
45 "oneOf",
46 "if",
47 "then",
48 "not",
49 "$ref",
50 "$defs",
51 "description",
52 "default",
53 "title",
54 "x-message",
55];
56
57const MAX_SAFE_INTEGER: i64 = (1 << 53) - 1;
59
60#[must_use]
62pub fn generator() -> SchemaGenerator {
63 SchemaSettings::draft2020_12()
64 .with_transform(Strict)
65 .into_generator()
66}
67
68#[must_use]
71pub fn with_schema(action: ActionType) -> Schema {
72 let mut generator = generator();
73 let mut schema = action.params_schema(&mut generator);
74 Strict.transform(&mut schema);
75 let definitions = generator.take_definitions(true);
76 if !definitions.is_empty() {
77 schema.insert("$defs".to_string(), Value::Object(definitions));
78 }
79 schema
80}
81
82impl ActionType {
83 pub fn params_schema(self, generator: &mut SchemaGenerator) -> Schema {
89 match self {
90 ActionType::ClockCheck => generator.subschema_for::<ClockCheckParams>(),
91 ActionType::Confirm => generator.subschema_for::<ConfirmParams>(),
92 ActionType::CheckValue => generator.subschema_for::<CheckValueParams>(),
93 ActionType::OralReadback => generator.subschema_for::<OralReadbackParams>(),
94 ActionType::Reveal => generator.subschema_for::<RevealParams>(),
95 ActionType::MachineInfo => generator.subschema_for::<MachineInfoParams>(),
96 ActionType::EnterValue => enter_value(generator),
97 ActionType::EnterSecret => generator.subschema_for::<EntryParams>(),
98 ActionType::GenerateKey => generator.subschema_for::<GenerateKeyParams>(),
99 ActionType::WrapKey => generator.subschema_for::<WrapKeyParams>(),
100 ActionType::UnwrapKey => generator.subschema_for::<UnwrapKeyParams>(),
101 ActionType::ImportKey => generator.subschema_for::<ImportKeyParams>(),
102 ActionType::EncryptData => generator.subschema_for::<EncryptDataParams>(),
103 ActionType::SplitSecret => generator.subschema_for::<SplitSecretParams>(),
104 ActionType::EnterShare => generator.subschema_for::<EnterShareParams>(),
105 ActionType::SignData => generator.subschema_for::<SignDataParams>(),
106 ActionType::VerifySignature => generator.subschema_for::<VerifySignatureParams>(),
107 ActionType::Attest => generator.subschema_for::<AttestParams>(),
108 ActionType::GatherEntropy => generator.subschema_for::<GatherEntropyParams>(),
109 ActionType::PivReadCertificate => generator.subschema_for::<PivReadCertificateParams>(),
110 ActionType::PivSign => generator.subschema_for::<PivSignParams>(),
111 ActionType::YubikeyAttestSlot => generator.subschema_for::<AttestSlotParams>(),
112 ActionType::IssueCertificate => generator.subschema_for::<IssueCertificateParams>(),
113 ActionType::GenerateCsr => generator.subschema_for::<GenerateCsrParams>(),
114 ActionType::ExportPublic
117 | ActionType::DecryptData
118 | ActionType::CombineShares
119 | ActionType::TpmAttest => json_schema!({
120 "type": "object",
121 "properties": {},
122 "additionalProperties": false,
123 }),
124 }
125 }
126}
127
128fn enter_value(generator: &mut SchemaGenerator) -> Schema {
132 let entry = generator.subschema_for::<EntryParams>();
133 let secret: Vec<&str> = Format::ALL
134 .iter()
135 .filter(|format| format.rows().is_some())
136 .map(|format| format.as_str())
137 .collect();
138 json_schema!({
139 "allOf": [
140 entry,
141 {
142 "properties": {
143 "format": {
144 "not": { "enum": secret },
145 "x-message": format!(
146 "{} are for a value written down, which is a secret: use enter_secret",
147 secret.join(" and ")
148 ),
149 }
150 }
151 }
152 ]
153 })
154}
155
156pub(crate) fn names<'a>(names: impl Iterator<Item = &'a str>) -> Schema {
158 let names: Vec<&str> = names.collect();
159 json_schema!({ "type": "string", "enum": names })
160}
161
162pub(super) fn add_rule(schema: &mut Schema, rule: Value) {
165 let rules = schema
166 .ensure_object()
167 .entry("allOf")
168 .or_insert_with(|| Value::Array(Vec::new()));
169 if let Value::Array(rules) = rules {
170 rules.push(rule);
171 }
172}
173
174pub(super) fn property<'a>(
180 schema: &'a mut Schema,
181 name: &str,
182) -> &'a mut serde_json::Map<String, Value> {
183 schema
184 .get_mut("properties")
185 .and_then(|properties| properties.get_mut(name))
186 .and_then(Value::as_object_mut)
187 .unwrap_or_else(|| panic!("the schema has no property '{name}'"))
188}
189
190#[derive(Clone)]
200struct Strict;
201
202impl Transform for Strict {
203 fn transform(&mut self, schema: &mut Schema) {
204 if let Some(Value::Array(kinds)) = schema.get_mut("type") {
205 kinds.retain(|kind| kind != "null");
206 if let [only] = kinds.as_slice() {
207 let only = only.clone();
208 schema.insert("type".to_string(), only);
209 }
210 }
211 if let Some(Value::Array(branches)) = schema.get("anyOf")
212 && let [value, null] = branches.as_slice()
213 && *null == serde_json::json!({ "type": "null" })
214 && let Value::Object(value) = value.clone()
215 {
216 schema.remove("anyOf");
217 for (key, keyword) in value {
218 schema.insert(key, keyword);
219 }
220 }
221 if schema.get("default") == Some(&Value::Null) {
222 schema.remove("default");
223 }
224 if schema.get("type").and_then(Value::as_str) == Some("integer") {
225 let (low, high) = match schema.remove("format").as_ref().and_then(Value::as_str) {
226 Some("uint8") => (0, i64::from(u8::MAX)),
227 Some("uint16") => (0, i64::from(u16::MAX)),
228 Some("uint32") => (0, i64::from(u32::MAX)),
229 Some("uint" | "uint64") => (0, MAX_SAFE_INTEGER),
230 Some("int8") => (i64::from(i8::MIN), i64::from(i8::MAX)),
231 Some("int16") => (i64::from(i16::MIN), i64::from(i16::MAX)),
232 Some("int32") => (i64::from(i32::MIN), i64::from(i32::MAX)),
233 _ => (-MAX_SAFE_INTEGER, MAX_SAFE_INTEGER),
234 };
235 let minimum = schema
236 .get("minimum")
237 .and_then(Value::as_i64)
238 .map_or(low, |min| min.max(low));
239 let maximum = schema
240 .get("maximum")
241 .and_then(Value::as_i64)
242 .map_or(high, |max| max.min(high));
243 schema.insert("minimum".to_string(), minimum.into());
244 schema.insert("maximum".to_string(), maximum.into());
245 }
246 transform_subschemas(self, schema);
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253 use schemars::JsonSchema;
254 use serde_json::json;
255
256 #[test]
259 fn an_integer_is_bounded_by_its_rust_type() {
260 #[derive(JsonSchema)]
261 #[allow(dead_code)]
262 struct Integers {
263 small: i8,
264 medium: i16,
265 large: i32,
266 count: u8,
267 #[schemars(range(min = 1))]
268 shares: u8,
269 }
270 let schema = generator().root_schema_for::<Integers>();
271 let value = schema.as_value();
272 let bounds = |name: &str| {
273 let at = |keyword: &str| {
274 value
275 .pointer(&format!("/properties/{name}/{keyword}"))
276 .cloned()
277 };
278 (at("minimum"), at("maximum"))
279 };
280 let expect = |low: i64, high: i64| (Some(json!(low)), Some(json!(high)));
281 assert_eq!(bounds("small"), expect(-128, 127));
282 assert_eq!(bounds("medium"), expect(-32768, 32767));
283 assert_eq!(bounds("large"), expect(i32::MIN.into(), i32::MAX.into()));
284 assert_eq!(bounds("count"), expect(0, 255));
285 assert_eq!(bounds("shares"), expect(1, 255));
286 assert!(value.pointer("/properties/small/format").is_none());
287 }
288}