/build/source/nativelink-config/src/backcompat.rs
Line | Count | Source |
1 | | use std::collections::HashMap; |
2 | | |
3 | | use serde::{Deserialize, Deserializer}; |
4 | | |
5 | | use crate::cas_server::WithInstanceName; |
6 | | |
7 | | #[derive(Debug, Deserialize)] |
8 | | #[serde(untagged)] |
9 | | enum WithInstanceNameBackCompat<T> { |
10 | | Map(HashMap<String, T>), |
11 | | Vec(Vec<WithInstanceName<T>>), |
12 | | } |
13 | | |
14 | | const DEPRECATION_MESSAGE: &str = r#" |
15 | | WARNING: Using deprecated map format for services. Please migrate to the new array format: |
16 | | // Old: |
17 | | "cas": { |
18 | | "main": { |
19 | | "cas_store": "STORE_NAME" |
20 | | } |
21 | | } |
22 | | // New: |
23 | | "cas": [ |
24 | | { |
25 | | "instance_name": "main", |
26 | | "cas_store": "STORE_NAME" |
27 | | } |
28 | | ] |
29 | | "#; |
30 | | |
31 | | /// Use `#[serde(default, deserialize_with = "backcompat::opt_vec_named_config")]` for backwards |
32 | | /// compatibility with map-based access. A deprecation warning will be written to stderr if the |
33 | | /// old format is used. |
34 | 2 | pub(crate) fn opt_vec_with_instance_name<'de, D, T>( |
35 | 2 | deserializer: D, |
36 | 2 | ) -> Result<Option<Vec<WithInstanceName<T>>>, D::Error> |
37 | 2 | where |
38 | 2 | D: Deserializer<'de>, |
39 | 2 | T: Deserialize<'de>, |
40 | 2 | { |
41 | 2 | let Some(back_compat) = Option::deserialize(deserializer)?0 else { Branch (41:9): [Folded - Ignored]
Branch (41:9): [True: 2, False: 0]
|
42 | 0 | return Ok(None); |
43 | | }; |
44 | | |
45 | 2 | match back_compat { |
46 | 1 | WithInstanceNameBackCompat::Map(map) => { |
47 | 1 | eprintln!("{DEPRECATION_MESSAGE}"); |
48 | 1 | let vec = map |
49 | 1 | .into_iter() |
50 | 2 | .map(|(instance_name, config)| WithInstanceName { |
51 | 2 | instance_name, |
52 | 2 | config, |
53 | 2 | }) |
54 | 1 | .collect(); |
55 | 1 | Ok(Some(vec)) |
56 | | } |
57 | 1 | WithInstanceNameBackCompat::Vec(vec) => Ok(Some(vec)), |
58 | | } |
59 | 2 | } |
60 | | |
61 | | #[cfg(test)] |
62 | | mod tests { |
63 | | use serde_json::json; |
64 | | |
65 | | use super::*; |
66 | | |
67 | | #[derive(Debug, Deserialize, PartialEq)] |
68 | | struct PartialConfig { |
69 | | store: String, |
70 | | } |
71 | | |
72 | 0 | #[derive(Debug, Deserialize, PartialEq)] |
73 | | struct FullConfig { |
74 | | #[serde(default, deserialize_with = "opt_vec_with_instance_name")] |
75 | | cas: Option<Vec<WithInstanceName<PartialConfig>>>, |
76 | | } |
77 | | |
78 | | #[test] |
79 | 1 | fn test_configs_deserialization() { |
80 | 1 | let old_format = json!({ |
81 | 1 | "cas": { |
82 | 1 | "foo": { "store": "foo_store" }, |
83 | 1 | "bar": { "store": "bar_store" } |
84 | 1 | } |
85 | 1 | }); |
86 | 1 | |
87 | 1 | let new_format = json!({ |
88 | 1 | "cas": [ |
89 | 1 | { |
90 | 1 | "instance_name": "foo", |
91 | 1 | "store": "foo_store" |
92 | 1 | }, |
93 | 1 | { |
94 | 1 | "instance_name": "bar", |
95 | 1 | "store": "bar_store" |
96 | 1 | } |
97 | 1 | ] |
98 | 1 | }); |
99 | 1 | |
100 | 1 | let mut old_format: FullConfig = serde_json::from_value(old_format).unwrap(); |
101 | 1 | let mut new_format: FullConfig = serde_json::from_value(new_format).unwrap(); |
102 | | |
103 | | // Ensure deterministic ordering. |
104 | 1 | if let Some(vec) = old_format.cas.as_mut() { Branch (104:16): [True: 1, False: 0]
|
105 | 1 | vec.sort_by(|a, b| a.instance_name.cmp(&b.instance_name)); |
106 | 0 | } |
107 | 1 | if let Some(vec) = new_format.cas.as_mut() { Branch (107:16): [True: 1, False: 0]
|
108 | 1 | vec.sort_by(|a, b| a.instance_name.cmp(&b.instance_name)); |
109 | 0 | } |
110 | | |
111 | 1 | assert_eq!(old_format, new_format); |
112 | 1 | } |
113 | | |
114 | | #[test] |
115 | 1 | fn test_deserialize_none() { |
116 | 1 | let json = json!({}); |
117 | 1 | |
118 | 1 | let value: FullConfig = serde_json::from_value(json).unwrap(); |
119 | 1 | assert_eq!(value.cas, None); |
120 | 1 | } |
121 | | } |