microsoft/openvmm

Public

mirrored fromhttps://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
e05e4a6007555f4b6f4a63e4b3cb781cc4637cf1

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

flowey/schema_ado_yaml/src/lib.rs

275lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Serde defs for ADO YAML
5
6#![expect(missing_docs)]
7#![forbid(unsafe_code)]
8
9use serde::Deserialize;
10use serde::Serialize;
11use serde::Serializer;
12use std::collections::BTreeMap;
13use std::collections::BTreeSet;
14
15mod none {
16 use serde::Deserialize;
17 use serde::Deserializer;
18 use serde::Serializer;
19
20 pub fn serialize<S>(_: &(), ser: S) -> Result<S::Ok, S::Error>
21 where
22 S: Serializer,
23 {
24 ser.serialize_str("none")
25 }
26
27 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<(), D::Error> {
28 let s: &str = Deserialize::deserialize(d)?;
29 if s != "none" {
30 return Err(serde::de::Error::custom("field must be 'none'"));
31 }
32 Ok(())
33 }
34}
35
36/// Valid names may only contain alphanumeric characters and '_' and may not
37/// start with a number.
38fn validate_name<S>(s: &str, ser: S) -> Result<S::Ok, S::Error>
39where
40 S: Serializer,
41{
42 if s.is_empty() {
43 return Err(serde::ser::Error::custom("name cannot be empty"));
44 }
45
46 if s.chars().next().unwrap().is_ascii_digit() {
47 return Err(serde::ser::Error::custom("name cannot start with a number"));
48 }
49
50 if !s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
51 return Err(serde::ser::Error::custom(
52 "name must be ascii alphanumeric + '_'",
53 ));
54 }
55
56 ser.serialize_str(s)
57}
58
59#[derive(Debug, Serialize, Deserialize)]
60#[serde(rename_all = "camelCase")]
61pub struct TriggerBranches {
62 #[serde(skip_serializing_if = "Vec::is_empty")]
63 pub include: Vec<String>,
64 // Wrapping this in an Option is necessary to prevent problems when deserializing and exclude isn't present
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub exclude: Option<Vec<String>>,
67}
68
69#[derive(Debug, Serialize, Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub struct TriggerTags {
72 #[serde(skip_serializing_if = "Vec::is_empty")]
73 pub include: Vec<String>,
74 // Wrapping this in an Option is necessary to prevent problems when deserializing and exclude isn't present
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub exclude: Option<Vec<String>>,
77}
78
79#[derive(Debug, Serialize, Deserialize)]
80#[serde(untagged)]
81#[serde(rename_all = "camelCase")]
82pub enum PrTrigger {
83 None(#[serde(with = "none")] ()),
84 #[serde(rename_all = "camelCase")]
85 Some {
86 auto_cancel: bool,
87 drafts: bool,
88 branches: TriggerBranches,
89 },
90 // serde has a bug with untagged and `with` during deserialization
91 NoneWorkaround(String),
92}
93
94#[derive(Debug, Serialize, Deserialize)]
95#[serde(untagged)]
96#[serde(rename_all = "camelCase")]
97pub enum CiTrigger {
98 None(#[serde(with = "none")] ()),
99 #[serde(rename_all = "camelCase")]
100 Some {
101 batch: bool,
102 #[serde(skip_serializing_if = "Option::is_none")]
103 branches: Option<TriggerBranches>,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 tags: Option<TriggerTags>,
106 },
107 // serde has a bug with untagged and `with` during deserialization
108 NoneWorkaround(String),
109}
110
111#[derive(Debug, Serialize, Deserialize)]
112#[serde(rename_all = "camelCase")]
113pub struct Schedule {
114 // FUTURE?: proper cron validation?
115 pub cron: String,
116 pub display_name: String,
117 pub branches: TriggerBranches,
118 #[serde(skip_serializing_if = "std::ops::Not::not")]
119 #[serde(default)]
120 pub batch: bool,
121}
122
123#[derive(Debug, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct Variable {
126 pub name: String,
127 pub value: String,
128}
129
130#[derive(Debug, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct Pipeline {
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub name: Option<String>,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub trigger: Option<CiTrigger>,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 pub pr: Option<PrTrigger>,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub schedules: Option<Vec<Schedule>>,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub variables: Option<Vec<Variable>>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub parameters: Option<Vec<Parameter>>,
145 #[serde(skip_serializing_if = "Option::is_none")]
146 pub resources: Option<Resources>,
147 #[serde(skip_serializing_if = "Option::is_none")]
148 pub stages: Option<Vec<Stage>>,
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub jobs: Option<Vec<Job>>,
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub extends: Option<Extends>,
153}
154
155#[derive(Debug, Serialize, Deserialize)]
156#[serde(rename_all = "camelCase")]
157pub struct Extends {
158 pub template: String,
159 pub parameters: BTreeMap<String, serde_yaml::Value>,
160}
161
162#[derive(Debug, Default, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct Resources {
165 #[serde(skip_serializing_if = "Vec::is_empty")]
166 pub repositories: Vec<ResourcesRepository>,
167}
168
169#[derive(Debug, Serialize, Deserialize)]
170#[serde(rename_all = "camelCase")]
171pub struct ResourcesRepository {
172 // Alias for the specified repository.
173 //
174 // Acceptable values: [-_A-Za-z0-9]*.
175 pub repository: String,
176 /// ID of the service endpoint connecting to this repository
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub endpoint: Option<String>,
179 /// Repository name. Format depends on 'type'; does not accept variables
180 pub name: String,
181 /// ref name to checkout; defaults to 'refs/heads/main'.
182 #[serde(rename = "ref")]
183 pub r#ref: String,
184 #[serde(rename = "type")]
185 pub r#type: ResourcesRepositoryType,
186}
187
188#[derive(Debug, Serialize, Deserialize)]
189#[serde(rename_all = "lowercase")]
190pub enum ResourcesRepositoryType {
191 Git,
192 GitHub,
193 GitHubEnterprise,
194 Bitbucket,
195}
196
197#[derive(Debug, Serialize, Deserialize)]
198#[serde(rename_all = "camelCase")]
199pub struct Parameter {
200 pub name: String,
201 pub display_name: String,
202 #[serde(flatten)]
203 pub ty: ParameterType,
204}
205
206// ADO also has specialized types for things like steps/jobs/stages, etc... but
207// at this time, it's unclear how they'd be useful in flowey.
208#[derive(Debug, Serialize, Deserialize)]
209#[serde(tag = "type", rename_all = "camelCase")]
210pub enum ParameterType {
211 Boolean {
212 #[serde(skip_serializing_if = "Option::is_none")]
213 default: Option<bool>,
214 },
215 String {
216 #[serde(skip_serializing_if = "Option::is_none")]
217 default: Option<String>,
218 #[serde(skip_serializing_if = "Option::is_none")]
219 values: Option<Vec<String>>,
220 },
221 Number {
222 #[serde(skip_serializing_if = "Option::is_none")]
223 default: Option<i64>,
224 #[serde(skip_serializing_if = "Option::is_none")]
225 values: Option<Vec<i64>>,
226 },
227 Object {
228 #[serde(skip_serializing_if = "Option::is_none")]
229 default: Option<serde_yaml::Value>,
230 },
231}
232
233#[derive(Debug, Serialize, Deserialize)]
234#[serde(rename_all = "camelCase")]
235pub struct Stage {
236 /// Valid names may only contain alphanumeric characters and '_' and may
237 /// not start with a number.
238 #[serde(serialize_with = "validate_name")]
239 pub stage: String,
240 pub display_name: String,
241 pub depends_on: BTreeSet<String>,
242 #[serde(skip_serializing_if = "Option::is_none")]
243 pub condition: Option<String>,
244 pub jobs: Vec<Job>,
245}
246
247#[derive(Debug, Serialize, Deserialize)]
248#[serde(untagged)]
249#[serde(rename_all = "camelCase")]
250pub enum Pool {
251 Pool(String),
252 PoolWithMetadata(BTreeMap<String, serde_yaml::Value>),
253}
254
255#[derive(Debug, Serialize, Deserialize)]
256#[serde(rename_all = "camelCase")]
257pub struct Job {
258 #[serde(serialize_with = "validate_name")]
259 pub job: String,
260 pub display_name: String,
261 pub pool: Pool,
262 pub depends_on: BTreeSet<String>,
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub timeout_in_minutes: Option<u32>,
265 #[serde(skip_serializing_if = "Option::is_none")]
266 pub condition: Option<String>,
267 #[serde(skip_serializing_if = "Option::is_none")]
268 pub variables: Option<Vec<Variable>>,
269 // individual steps are not type-checked by the serde schema, as there are a
270 // _lot_ of different step kinds nodes might be emitting.
271 //
272 // instead, trust that the user knows what they're doing when emitting yaml
273 // snippets.
274 pub steps: Vec<serde_yaml::Value>,
275}
276