Skip to content

Commit 5f35f67

Browse files
(PowerShellGH-642) Add initial plumbing for validating schema URIs
Prior to this change, DSC didn't have a convenient way to verify whether a configuration or manifest used a recognized schema or to switch behaviors based on the declared URI. This change begins the process of plumbing the recognized schemas into DSC so the engine can correctly validate and handle items based on the value of the `$schema` keyword. In this first iteration, the `DscRepoSchema` trait adds a method to validate whether the item uses a recognized schema. In the future, the engine should provide further handling than pass/fail. This change only adds the methods, it doesn't alter how DSC processes configuration documents or resource manifests.
1 parent 4d27190 commit 5f35f67

File tree

5 files changed

+138
-1
lines changed

5 files changed

+138
-1
lines changed

dsc_lib/locales/en-us.toml

+2
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,9 @@ schemaNotAvailable = "No Schema found and `validate` is not supported"
319319
securityContext = "Security context"
320320
utf8Conversion = "UTF-8 conversion"
321321
unknown = "Unknown"
322+
unrecognizedSchemaUri = "Unrecognized $schema URI"
322323
validation = "Validation"
324+
validSchemaUrisAre = "Valid schema URIs are"
323325
setting = "Setting"
324326

325327
[progress]

dsc_lib/src/configure/config_doc.rs

+54-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
77
use serde_json::{Map, Value};
88
use std::collections::HashMap;
99

10-
use crate::schemas::DscRepoSchema;
10+
use crate::{dscerror::DscError, schemas::DscRepoSchema};
1111

1212
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
1313
#[serde(rename_all = "camelCase")]
@@ -165,6 +165,16 @@ impl DscRepoSchema for Configuration {
165165
..Default::default()
166166
}
167167
}
168+
169+
fn validate_schema_uri(&self) -> Result<(), DscError> {
170+
match Self::is_recognized_schema_uri(&self.schema) {
171+
true => Ok(()),
172+
false => Err(DscError::UnrecognizedSchemaUri(
173+
self.schema.clone(),
174+
Self::recognized_schema_uris(),
175+
))
176+
}
177+
}
168178
}
169179

170180
impl Configuration {
@@ -199,3 +209,46 @@ impl Default for Resource {
199209
Self::new()
200210
}
201211
}
212+
213+
#[allow(unused_imports)]
214+
mod test {
215+
use serde_json::json;
216+
217+
use crate::{configure::config_doc::Configuration, dscerror::DscError, dscresources::resource_manifest::{import_manifest, ResourceManifest}, schemas::DscRepoSchema};
218+
219+
#[test]
220+
fn test_validate_schema_uri_with_invalid_uri() {
221+
let invalid_uri = "https://invalid.schema.uri".to_string();
222+
223+
let manifest = Configuration{
224+
schema: invalid_uri.clone(),
225+
..Default::default()
226+
};
227+
228+
let ref result = manifest.validate_schema_uri();
229+
230+
assert!(result.as_ref().is_err());
231+
232+
match result.as_ref().unwrap_err() {
233+
DscError::UnrecognizedSchemaUri(actual, recognized) => {
234+
assert_eq!(actual, &invalid_uri);
235+
assert_eq!(recognized, &Configuration::recognized_schema_uris())
236+
},
237+
_ => {
238+
panic!("Expected validate_schema_uri() to error on unrecognized schema uri, but was {:?}", result.as_ref().unwrap_err())
239+
}
240+
}
241+
}
242+
243+
#[test]
244+
fn test_validate_schema_uri_with_valid_uri() {
245+
let manifest = Configuration{
246+
schema: Configuration::default_schema_id_uri(),
247+
..Default::default()
248+
};
249+
250+
let result = manifest.validate_schema_uri();
251+
252+
assert!(result.is_ok());
253+
}
254+
}

dsc_lib/src/dscerror.rs

+3
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ pub enum DscError {
113113
message: String,
114114
},
115115

116+
#[error("{t}: {0}. {t2}: {1:?}", t = t!("dscerror.unrecognizedSchemaUri"), t2 = t!("dscerror.validSchemaUrisAre"))]
117+
UnrecognizedSchemaUri(String, Vec<String>),
118+
116119
#[error("{t}: {0}", t = t!("dscerror.validation"))]
117120
Validation(String),
118121

dsc_lib/src/dscresources/resource_manifest.rs

+57
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,16 @@ impl DscRepoSchema for ResourceManifest {
246246
..Default::default()
247247
}
248248
}
249+
250+
fn validate_schema_uri(&self) -> Result<(), DscError> {
251+
match Self::is_recognized_schema_uri(&self.schema_version) {
252+
true => Ok(()),
253+
false => Err(DscError::UnrecognizedSchemaUri(
254+
self.schema_version.clone(),
255+
Self::recognized_schema_uris(),
256+
))
257+
}
258+
}
249259
}
250260

251261
/// Import a resource manifest from a JSON value.
@@ -288,3 +298,50 @@ pub fn validate_semver(version: &str) -> Result<(), semver::Error> {
288298
Version::parse(version)?;
289299
Ok(())
290300
}
301+
302+
#[allow(unused_imports)]
303+
mod test {
304+
use serde_json::json;
305+
306+
use crate::{dscerror::DscError, dscresources::resource_manifest::{import_manifest, ResourceManifest}, schemas::DscRepoSchema};
307+
308+
#[test]
309+
fn test_validate_schema_uri_with_invalid_uri() {
310+
let invalid_uri = "https://invalid.schema.uri".to_string();
311+
312+
let manifest = ResourceManifest{
313+
schema_version: invalid_uri.clone(),
314+
resource_type: "Microsoft.Dsc.Test/InvalidSchemaUri".to_string(),
315+
version: "0.1.0".to_string(),
316+
..Default::default()
317+
};
318+
319+
let ref result = manifest.validate_schema_uri();
320+
321+
assert!(result.as_ref().is_err());
322+
323+
match result.as_ref().unwrap_err() {
324+
DscError::UnrecognizedSchemaUri(actual, recognized) => {
325+
assert_eq!(actual, &invalid_uri);
326+
assert_eq!(recognized, &ResourceManifest::recognized_schema_uris())
327+
},
328+
_ => {
329+
panic!("Expected validate_schema_uri() to error on unrecognized schema uri, but was {:?}", result.as_ref().unwrap_err())
330+
}
331+
}
332+
}
333+
334+
#[test]
335+
fn test_validate_schema_uri_with_valid_uri() {
336+
let manifest = ResourceManifest{
337+
schema_version: ResourceManifest::default_schema_id_uri(),
338+
resource_type: "Microsoft.Dsc.Test/ValidSchemaUri".to_string(),
339+
version: "0.1.0".to_string(),
340+
..Default::default()
341+
};
342+
343+
let result = manifest.validate_schema_uri();
344+
345+
assert!(result.is_ok());
346+
}
347+
}

dsc_lib/src/schemas/mod.rs

+22
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
33
use schemars::{schema::{Metadata, Schema}, JsonSchema};
44

5+
use crate::dscerror::DscError;
6+
57
/// Defines the URI prefix for the hosted schemas.
68
///
79
/// While the schemas are currently hosted in the GitHub repository, DSC provides the shortened
@@ -447,6 +449,26 @@ pub trait DscRepoSchema : JsonSchema {
447449
Self::SCHEMA_SHOULD_BUNDLE
448450
)
449451
}
452+
453+
/// Indicates whether a given string is a recognized shema URI.
454+
fn is_recognized_schema_uri(uri: &String) -> bool {
455+
Self::recognized_schema_uris().contains(uri)
456+
}
457+
458+
/// Validates the `$schema` keyword for deserializing instances.
459+
///
460+
/// This method simplifies the validation of a type that has the `$schema` keyword and expects
461+
/// that instances of the type in data indicate which schema version DSC should use to validate
462+
/// them.
463+
///
464+
/// This method includes a default implementation to avoid requiring the implementation for
465+
/// types that don't define the `$schema` keyword in their serialized form.
466+
///
467+
/// Any DSC type that serializes with the `$schema` keyword **must** define this
468+
/// method to actually validate the instance.
469+
fn validate_schema_uri(&self) -> Result<(), DscError> {
470+
Ok(())
471+
}
450472
}
451473

452474
#[allow(unused_imports)]

0 commit comments

Comments
 (0)