]> git.proxmox.com Git - proxmox.git/blob - proxmox-schema/src/de/mod.rs
clippy fix: useless use of `format!`
[proxmox.git] / proxmox-schema / src / de / mod.rs
1 //! Property string deserialization.
2
3 use std::borrow::Cow;
4 use std::cell::Cell;
5 use std::fmt;
6 use std::ops::Range;
7
8 use serde::de::{self, IntoDeserializer};
9
10 use crate::schema::{self, ArraySchema, Schema};
11
12 mod cow3;
13 mod extract;
14 mod no_schema;
15
16 pub mod verify;
17
18 pub use extract::ExtractValueDeserializer;
19
20 use cow3::{str_slice_to_range, Cow3};
21
22 // Used to disable calling `check_constraints` on a `StringSchema` if it is being deserialized
23 // for a `PropertyString`, which performs its own checking.
24 thread_local! {
25 static IN_PROPERTY_STRING: Cell<bool> = Cell::new(false);
26 }
27
28 pub(crate) struct InPropertyStringGuard;
29
30 pub(crate) fn set_in_property_string() -> InPropertyStringGuard {
31 IN_PROPERTY_STRING.with(|v| v.set(true));
32 InPropertyStringGuard
33 }
34
35 impl Drop for InPropertyStringGuard {
36 fn drop(&mut self) {
37 IN_PROPERTY_STRING.with(|v| v.set(false));
38 }
39 }
40
41 #[derive(Debug)]
42 pub struct Error(Cow<'static, str>);
43
44 impl std::error::Error for Error {}
45
46 impl fmt::Display for Error {
47 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48 fmt::Display::fmt(&self.0, f)
49 }
50 }
51
52 impl Error {
53 pub(crate) fn msg<T: Into<Cow<'static, str>>>(msg: T) -> Self {
54 Self(msg.into())
55 }
56
57 fn invalid<T: fmt::Display>(msg: T) -> Self {
58 Self::msg(format!("schema validation failed: {}", msg))
59 }
60 }
61
62 impl serde::de::Error for Error {
63 fn custom<T: fmt::Display>(msg: T) -> Self {
64 Self(msg.to_string().into())
65 }
66 }
67
68 impl From<serde_json::Error> for Error {
69 fn from(error: serde_json::Error) -> Self {
70 Self(error.to_string().into())
71 }
72 }
73
74 impl From<fmt::Error> for Error {
75 fn from(err: fmt::Error) -> Self {
76 Self::msg(err.to_string())
77 }
78 }
79
80 /// Deserializer for parts a part of a property string given a schema.
81 pub struct SchemaDeserializer<'de, 'i> {
82 input: Cow3<'de, 'i, str>,
83 schema: &'static Schema,
84 }
85
86 impl<'de, 'i> SchemaDeserializer<'de, 'i> {
87 pub fn new_cow(input: Cow3<'de, 'i, str>, schema: &'static Schema) -> Self {
88 Self { input, schema }
89 }
90
91 pub fn new<T>(input: T, schema: &'static Schema) -> Self
92 where
93 T: Into<Cow<'de, str>>,
94 {
95 Self {
96 input: Cow3::from_original(input.into()),
97 schema,
98 }
99 }
100
101 fn deserialize_str<V>(
102 self,
103 visitor: V,
104 schema: &'static schema::StringSchema,
105 ) -> Result<V::Value, Error>
106 where
107 V: de::Visitor<'de>,
108 {
109 if !IN_PROPERTY_STRING.with(|v| v.get()) {
110 schema
111 .check_constraints(&self.input)
112 .map_err(Error::invalid)?;
113 }
114 match self.input {
115 Cow3::Original(input) => visitor.visit_borrowed_str(input),
116 Cow3::Intermediate(input) => visitor.visit_str(input),
117 Cow3::Owned(input) => visitor.visit_string(input),
118 }
119 }
120
121 fn deserialize_property_string<V>(
122 self,
123 visitor: V,
124 schema: &'static Schema,
125 ) -> Result<V::Value, Error>
126 where
127 V: de::Visitor<'de>,
128 {
129 match schema {
130 Schema::Object(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
131 Schema::AllOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
132 _ => Err(Error::msg(
133 "non-object-like schema in ApiStringFormat::PropertyString while deserializing a property string",
134 )),
135 }
136 }
137
138 fn deserialize_array_string<V>(
139 self,
140 visitor: V,
141 schema: &'static Schema,
142 ) -> Result<V::Value, Error>
143 where
144 V: de::Visitor<'de>,
145 {
146 match schema {
147 Schema::Array(schema) => visitor.visit_seq(SeqAccess::new(self.input, schema)),
148 _ => Err(Error::msg(
149 "non-array schema in ApiStringFormat::PropertyString while deserializing an array",
150 )),
151 }
152 }
153 }
154
155 impl<'de, 'i> de::Deserializer<'de> for SchemaDeserializer<'de, 'i> {
156 type Error = Error;
157
158 fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
159 where
160 V: de::Visitor<'de>,
161 {
162 match self.schema {
163 Schema::Array(schema) => visitor.visit_seq(SeqAccess::new(self.input, schema)),
164 Schema::AllOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
165 Schema::Object(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
166 Schema::Null => Err(Error::msg("null")),
167 Schema::Boolean(_) => visitor.visit_bool(
168 schema::parse_boolean(&self.input)
169 .map_err(|_| Error::msg(format!("not a boolean: {:?}", self.input)))?,
170 ),
171 Schema::Integer(schema) => {
172 // FIXME: isize vs explicit i64, needs fixing in schema check_constraints api
173 let value: isize = self
174 .input
175 .parse()
176 .map_err(|_| Error::msg(format!("not an integer: {:?}", self.input)))?;
177
178 schema.check_constraints(value).map_err(Error::invalid)?;
179
180 let value: i64 = i64::try_from(value)
181 .map_err(|_| Error::invalid("isize did not fit into i64"))?;
182
183 if let Ok(value) = u64::try_from(value) {
184 visitor.visit_u64(value)
185 } else {
186 visitor.visit_i64(value)
187 }
188 }
189 Schema::Number(schema) => {
190 let value: f64 = self
191 .input
192 .parse()
193 .map_err(|_| Error::msg(format!("not a valid number: {:?}", self.input)))?;
194
195 schema.check_constraints(value).map_err(Error::invalid)?;
196
197 visitor.visit_f64(value)
198 }
199 Schema::String(schema) => {
200 // If not requested differently, strings stay strings, otherwise deserializing to a
201 // `Value` will get objects here instead of strings, which we do not expect
202 // anywhere.
203 self.deserialize_str(visitor, schema)
204 }
205 }
206 }
207
208 fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
209 where
210 V: de::Visitor<'de>,
211 {
212 if self.input.is_empty() {
213 visitor.visit_none()
214 } else {
215 visitor.visit_some(self)
216 }
217 }
218
219 fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Error>
220 where
221 V: de::Visitor<'de>,
222 {
223 match self.schema {
224 Schema::String(schema) => self.deserialize_str(visitor, schema),
225 _ => Err(Error::msg(
226 "tried to deserialize a string with a non-string-schema",
227 )),
228 }
229 }
230
231 fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Error>
232 where
233 V: de::Visitor<'de>,
234 {
235 match self.schema {
236 Schema::String(schema) => self.deserialize_str(visitor, schema),
237 _ => Err(Error::msg(
238 "tried to deserialize a string with a non-string-schema",
239 )),
240 }
241 }
242
243 fn deserialize_newtype_struct<V>(
244 self,
245 _name: &'static str,
246 visitor: V,
247 ) -> Result<V::Value, Error>
248 where
249 V: de::Visitor<'de>,
250 {
251 visitor.visit_newtype_struct(self)
252 }
253
254 fn deserialize_struct<V>(
255 self,
256 name: &'static str,
257 _fields: &'static [&'static str],
258 visitor: V,
259 ) -> Result<V::Value, Error>
260 where
261 V: de::Visitor<'de>,
262 {
263 match self.schema {
264 Schema::Object(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
265 Schema::AllOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
266 Schema::String(schema) => match schema.format {
267 Some(schema::ApiStringFormat::PropertyString(schema)) => {
268 self.deserialize_property_string(visitor, schema)
269 }
270 _ => Err(Error::msg(format!(
271 "cannot deserialize struct '{}' with a string schema",
272 name
273 ))),
274 },
275 _ => Err(Error::msg(format!(
276 "cannot deserialize struct '{}' with non-object schema",
277 name,
278 ))),
279 }
280 }
281
282 fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Error>
283 where
284 V: de::Visitor<'de>,
285 {
286 match self.schema {
287 Schema::Object(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
288 Schema::AllOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
289 Schema::String(schema) => match schema.format {
290 Some(schema::ApiStringFormat::PropertyString(schema)) => {
291 self.deserialize_property_string(visitor, schema)
292 }
293 _ => Err(Error::msg("cannot deserialize map with a string schema")),
294 },
295 _ => Err(Error::msg("cannot deserialize map with non-object schema")),
296 }
297 }
298
299 fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Error>
300 where
301 V: de::Visitor<'de>,
302 {
303 match self.schema {
304 Schema::Array(schema) => visitor.visit_seq(SeqAccess::new(self.input, schema)),
305 Schema::String(schema) => match schema.format {
306 Some(schema::ApiStringFormat::PropertyString(schema)) => {
307 self.deserialize_array_string(visitor, schema)
308 }
309 _ => Err(Error::msg("cannot deserialize array with a string schema")),
310 },
311 _ => Err(Error::msg(
312 "cannot deserialize array with non-object schema",
313 )),
314 }
315 }
316
317 fn deserialize_enum<V>(
318 self,
319 name: &'static str,
320 _variants: &'static [&'static str],
321 visitor: V,
322 ) -> Result<V::Value, Error>
323 where
324 V: de::Visitor<'de>,
325 {
326 match self.schema {
327 Schema::String(_) => visitor.visit_enum(self.input.into_deserializer()),
328 _ => Err(Error::msg(format!(
329 "cannot deserialize enum '{}' with non-string schema",
330 name,
331 ))),
332 }
333 }
334
335 serde::forward_to_deserialize_any! {
336 i8 i16 i32 i64
337 u8 u16 u32 u64
338 f32 f64
339 bool
340 char
341 bytes byte_buf
342 unit unit_struct
343 tuple tuple_struct
344 identifier
345 ignored_any
346 }
347 }
348
349 fn next_str_entry(input: &str, at: &mut usize, has_null: bool) -> Option<Range<usize>> {
350 while *at != input.len() {
351 let begin = *at;
352
353 let part = &input[*at..];
354
355 let part_end = if has_null {
356 part.find('\0')
357 } else {
358 part.find(|c: char| c == ',' || c == ';' || char::is_ascii_whitespace(&c))
359 };
360
361 let end = match part_end {
362 None => {
363 *at = input.len();
364 input.len()
365 }
366 Some(rel_end) => {
367 *at += rel_end + 1;
368 begin + rel_end
369 }
370 };
371
372 if input[..end].is_empty() {
373 continue;
374 }
375
376 return Some(begin..end);
377 }
378
379 None
380 }
381
382 /// Parse an array with a schema.
383 ///
384 /// Provides both `SeqAccess` and `Deserializer` implementations.
385 pub struct SeqAccess<'o, 'i, 's> {
386 schema: &'s ArraySchema,
387 was_empty: bool,
388 input: Cow3<'o, 'i, str>,
389 has_null: bool,
390 at: usize,
391 count: usize,
392 }
393
394 impl<'o, 'i, 's> SeqAccess<'o, 'i, 's> {
395 pub fn new(input: Cow3<'o, 'i, str>, schema: &'s ArraySchema) -> Self {
396 Self {
397 schema,
398 was_empty: input.is_empty(),
399 has_null: input.contains('\0'),
400 input,
401 at: 0,
402 count: 0,
403 }
404 }
405 }
406
407 impl<'de, 'i, 's> de::SeqAccess<'de> for SeqAccess<'de, 'i, 's> {
408 type Error = Error;
409
410 fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>, Error>
411 where
412 T: de::DeserializeSeed<'de>,
413 {
414 if self.was_empty {
415 return Ok(None);
416 }
417
418 while let Some(el_range) = next_str_entry(&self.input, &mut self.at, self.has_null) {
419 if el_range.is_empty() {
420 continue;
421 }
422
423 if let Some(max) = self.schema.max_length {
424 if self.count == max {
425 return Err(Error::msg("too many elements"));
426 }
427 }
428
429 self.count += 1;
430
431 return seed
432 .deserialize(SchemaDeserializer::new_cow(
433 self.input.slice(el_range),
434 self.schema.items,
435 ))
436 .map(Some);
437 }
438
439 if let Some(min) = self.schema.min_length {
440 if self.count < min {
441 return Err(Error::msg("not enough elements"));
442 }
443 }
444
445 Ok(None)
446 }
447 }
448
449 impl<'de, 'i, 's> de::Deserializer<'de> for SeqAccess<'de, 'i, 's> {
450 type Error = Error;
451
452 fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Error>
453 where
454 V: de::Visitor<'de>,
455 {
456 visitor.visit_seq(self)
457 }
458
459 fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Error>
460 where
461 V: de::Visitor<'de>,
462 {
463 if self.was_empty {
464 visitor.visit_none()
465 } else {
466 visitor.visit_some(self)
467 }
468 }
469
470 serde::forward_to_deserialize_any! {
471 i8 i16 i32 i64 u8 u16 u32 u64 f32 f64
472 bool char str string
473 bytes byte_buf
474 unit unit_struct
475 newtype_struct
476 tuple tuple_struct
477 enum map seq
478 struct
479 identifier ignored_any
480 }
481 }
482
483 /// Provides serde's `MapAccess` for parsing a property string.
484 pub struct MapAccess<'de, 'i> {
485 // The property string iterator and quoted string handler.
486 input: Cow3<'de, 'i, str>,
487 input_at: usize, // for when using `Cow3::Owned`.
488
489 /// As a `Deserializer` we want to be able to handle `deserialize_option` and need to know
490 /// whether this was an empty string.
491 was_empty: bool,
492
493 /// The schema used to verify the contents and distinguish between structs and property
494 /// strings.
495 schema: &'static dyn schema::ObjectSchemaType,
496
497 /// The current next value's key, value and schema (if available).
498 value: Option<(Cow<'de, str>, Cow<'de, str>, Option<&'static Schema>)>,
499 }
500
501 impl<'de, 'i> MapAccess<'de, 'i> {
502 pub fn new<S: schema::ObjectSchemaType>(input: &'de str, schema: &'static S) -> Self {
503 Self {
504 was_empty: input.is_empty(),
505 input: Cow3::Original(input),
506 schema,
507 input_at: 0,
508 value: None,
509 }
510 }
511
512 pub fn new_cow<S: schema::ObjectSchemaType>(
513 input: Cow3<'de, 'i, str>,
514 schema: &'static S,
515 ) -> Self {
516 Self {
517 was_empty: input.is_empty(),
518 input,
519 schema,
520 input_at: 0,
521 value: None,
522 }
523 }
524
525 pub fn new_intermediate<S: schema::ObjectSchemaType>(
526 input: &'i str,
527 schema: &'static S,
528 ) -> Self {
529 Self {
530 was_empty: input.is_empty(),
531 input: Cow3::Intermediate(input),
532 schema,
533 input_at: 0,
534 value: None,
535 }
536 }
537 }
538
539 impl<'de, 'i> de::MapAccess<'de> for MapAccess<'de, 'i> {
540 type Error = Error;
541
542 fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Error>
543 where
544 K: de::DeserializeSeed<'de>,
545 {
546 use crate::property_string::next_property;
547
548 if self.was_empty {
549 // shortcut
550 return Ok(None);
551 }
552
553 let (key, value, rem) = match next_property(&self.input[self.input_at..]) {
554 None => return Ok(None),
555 Some(entry) => entry?,
556 };
557
558 if rem.is_empty() {
559 self.input_at = self.input.len();
560 } else {
561 let ofs = unsafe { rem.as_ptr().offset_from(self.input.as_ptr()) };
562 if ofs < 0 || (ofs as usize) > self.input.len() {
563 // 'rem' is either an empty string (rem.is_empty() is true), or a valid offset into
564 // the input string...
565 panic!("unexpected remainder in next_property");
566 }
567 self.input_at = ofs as usize;
568 }
569
570 let value = match value {
571 Cow::Owned(value) => Cow::Owned(value),
572 Cow::Borrowed(value) => match str_slice_to_range(&self.input, value) {
573 None => Cow::Owned(value.to_string()),
574 Some(range) => match &self.input {
575 Cow3::Original(orig) => Cow::Borrowed(&orig[range]),
576 _ => Cow::Owned(value.to_string()),
577 },
578 },
579 };
580
581 let (key, schema) = match key {
582 Some(key) => {
583 let schema = self.schema.lookup(key);
584 let key = match str_slice_to_range(&self.input, key) {
585 None => Cow::Owned(key.to_string()),
586 Some(range) => match &self.input {
587 Cow3::Original(orig) => Cow::Borrowed(&orig[range]),
588 _ => Cow::Owned(key.to_string()),
589 },
590 };
591 (key, schema)
592 }
593 None => match self.schema.default_key() {
594 Some(key) => {
595 let schema = self
596 .schema
597 .lookup(key)
598 .ok_or(Error::msg("bad default key"))?;
599 (Cow::Borrowed(key), Some(schema))
600 }
601 None => {
602 return Err(Error::msg(
603 "value without key, but schema does not define a default key",
604 ))
605 }
606 },
607 };
608 let schema = schema.map(|(_optional, schema)| schema);
609
610 let out = match &key {
611 Cow::Borrowed(key) => {
612 seed.deserialize(de::value::BorrowedStrDeserializer::<'de, Error>::new(key))?
613 }
614 Cow::Owned(key) => {
615 seed.deserialize(IntoDeserializer::<Error>::into_deserializer(key.as_str()))?
616 }
617 };
618
619 self.value = Some((key, value, schema));
620
621 Ok(Some(out))
622 }
623
624 fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Error>
625 where
626 V: de::DeserializeSeed<'de>,
627 {
628 let (key, input, schema) = self.value.take().ok_or(Error::msg("bad map access"))?;
629
630 if let Some(schema) = schema {
631 seed.deserialize(SchemaDeserializer::new(input, schema))
632 } else {
633 if !verify::is_verifying() && !self.schema.additional_properties() {
634 return Err(Error::msg(format!("unknown key {:?}", key.as_ref())));
635 }
636
637 // additional properties are treated as strings...
638 let deserializer = no_schema::NoSchemaDeserializer::new(input);
639 seed.deserialize(deserializer)
640 }
641 }
642 }