diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 0d3ad234f..918be2f9f 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -729,9 +729,7 @@ schemaNotAvailable = "No Schema found and `validate` is not supported" securityContext = "Security context" utf8Conversion = "UTF-8 conversion" unknown = "Unknown" -unrecognizedSchemaUri = "Unrecognized $schema URI" validation = "Validation" -validSchemaUrisAre = "Valid schema URIs are" extension = "Extension" unsupportedCapability = "does not support capability" setting = "Setting" diff --git a/lib/dsc-lib/src/configure/config_doc.rs b/lib/dsc-lib/src/configure/config_doc.rs index 3f91b3ea2..6489114b6 100644 --- a/lib/dsc-lib/src/configure/config_doc.rs +++ b/lib/dsc-lib/src/configure/config_doc.rs @@ -2,17 +2,16 @@ // Licensed under the MIT License. use chrono::{DateTime, Local}; -use dsc_lib_jsonschema::transforms::{ - idiomaticize_externally_tagged_enum, - idiomaticize_string_enum -}; use rust_i18n::t; use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::{Map, Value}; use std::{collections::HashMap, fmt::Display}; -use crate::{dscerror::DscError, schemas::DscRepoSchema}; +use crate::schemas::{ + dsc_repo::{DscRepoSchema, UnrecognizedSchemaUri}, + transforms::{idiomaticize_externally_tagged_enum, idiomaticize_string_enum} +}; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] @@ -412,18 +411,18 @@ impl DscRepoSchema for Configuration { const SCHEMA_FOLDER_PATH: &'static str = "config"; const SCHEMA_SHOULD_BUNDLE: bool = true; - fn schema_metadata() -> schemars::Schema { + fn schema_property_metadata() -> schemars::Schema { json_schema!({ "title": t!("configure.config_doc.configurationDocumentSchemaTitle").to_string(), "description": t!("configure.config_doc.configurationDocumentSchemaDescription").to_string(), }) } - fn validate_schema_uri(&self) -> Result<(), DscError> { + fn validate_schema_uri(&self) -> Result<(), UnrecognizedSchemaUri> { if Self::is_recognized_schema_uri(&self.schema) { Ok(()) } else { - Err(DscError::UnrecognizedSchemaUri( + Err(UnrecognizedSchemaUri( self.schema.clone(), Self::recognized_schema_uris(), )) @@ -485,11 +484,9 @@ impl Default for Resource { #[cfg(test)] mod test { - use crate::{ - configure::config_doc::Configuration, - dscerror::DscError, - schemas::DscRepoSchema - }; + use crate::schemas::dsc_repo::{DscRepoSchema, UnrecognizedSchemaUri}; + + use crate::configure::config_doc::Configuration; #[test] fn test_validate_schema_uri_with_invalid_uri() { @@ -505,13 +502,10 @@ mod test { assert!(result.as_ref().is_err()); match result.as_ref().unwrap_err() { - DscError::UnrecognizedSchemaUri(actual, recognized) => { + UnrecognizedSchemaUri(actual, recognized) => { assert_eq!(actual, &invalid_uri); assert_eq!(recognized, &Configuration::recognized_schema_uris()) }, - _ => { - panic!("Expected validate_schema_uri() to error on unrecognized schema uri, but was {:?}", result.as_ref().unwrap_err()) - } } } diff --git a/lib/dsc-lib/src/configure/config_progress.rs b/lib/dsc-lib/src/configure/config_progress.rs index 9501038fb..25a251aaa 100644 --- a/lib/dsc-lib/src/configure/config_progress.rs +++ b/lib/dsc-lib/src/configure/config_progress.rs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use dsc_lib_jsonschema::transforms::idiomaticize_string_enum; + use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::schemas::transforms::idiomaticize_string_enum; + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ConfigurationResourceStartedEvent { diff --git a/lib/dsc-lib/src/configure/config_result.rs b/lib/dsc-lib/src/configure/config_result.rs index e5207101e..3bce3860d 100644 --- a/lib/dsc-lib/src/configure/config_result.rs +++ b/lib/dsc-lib/src/configure/config_result.rs @@ -1,12 +1,13 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use dsc_lib_jsonschema::transforms::idiomaticize_string_enum; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; + use crate::dscresources::invoke_result::{GetResult, SetResult, TestResult}; use crate::configure::config_doc::{Configuration, Metadata}; +use crate::schemas::transforms::idiomaticize_string_enum; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] diff --git a/lib/dsc-lib/src/discovery/command_discovery.rs b/lib/dsc-lib/src/discovery/command_discovery.rs index e34ec66b2..8479b8cb6 100644 --- a/lib/dsc-lib/src/discovery/command_discovery.rs +++ b/lib/dsc-lib/src/discovery/command_discovery.rs @@ -12,7 +12,7 @@ use crate::extensions::dscextension::{self, DscExtension, Capability as Extensio use crate::extensions::extension_manifest::ExtensionManifest; use crate::progress::{ProgressBar, ProgressFormat}; use crate::util::convert_wildcard_to_regex; -use dsc_lib_jsonschema::transforms::idiomaticize_externally_tagged_enum; +use crate::schemas::transforms::idiomaticize_externally_tagged_enum; use regex::RegexBuilder; use rust_i18n::t; use semver::{Version, VersionReq}; diff --git a/lib/dsc-lib/src/dscerror.rs b/lib/dsc-lib/src/dscerror.rs index 947531831..02a482107 100644 --- a/lib/dsc-lib/src/dscerror.rs +++ b/lib/dsc-lib/src/dscerror.rs @@ -128,8 +128,8 @@ pub enum DscError { message: String, }, - #[error("{t}: {0}. {t2}: {1:?}", t = t!("dscerror.unrecognizedSchemaUri"), t2 = t!("dscerror.validSchemaUrisAre"))] - UnrecognizedSchemaUri(String, Vec), + #[error(transparent)] + UnrecognizedSchemaUri(#[from] crate::schemas::dsc_repo::UnrecognizedSchemaUri), #[error("{t} '{0}' {t2} '{1}'", t = t!("dscerror.extension"), t2 = t!("dscerror.unsupportedCapability"))] UnsupportedCapability(String, String), diff --git a/lib/dsc-lib/src/dscresources/dscresource.rs b/lib/dsc-lib/src/dscresources/dscresource.rs index 55f00f7e3..cf230c231 100644 --- a/lib/dsc-lib/src/dscresources/dscresource.rs +++ b/lib/dsc-lib/src/dscresources/dscresource.rs @@ -3,7 +3,7 @@ use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::{SECURE_VALUE_REDACTED, is_secure_value}}, dscresources::resource_manifest::{AdapterInputKind, Kind}}; use crate::dscresources::invoke_result::{ResourceGetResponse, ResourceSetResponse}; -use dsc_lib_jsonschema::transforms::idiomaticize_string_enum; +use crate::schemas::transforms::idiomaticize_string_enum; use dscerror::DscError; use jsonschema::Validator; use rust_i18n::t; diff --git a/lib/dsc-lib/src/dscresources/resource_manifest.rs b/lib/dsc-lib/src/dscresources/resource_manifest.rs index bc41eb23e..513d9b2b2 100644 --- a/lib/dsc-lib/src/dscresources/resource_manifest.rs +++ b/lib/dsc-lib/src/dscresources/resource_manifest.rs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use dsc_lib_jsonschema::transforms::idiomaticize_string_enum; use rust_i18n::t; use schemars::{Schema, JsonSchema, json_schema}; use semver::Version; @@ -9,7 +8,10 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; use std::collections::HashMap; -use crate::{dscerror::DscError, schemas::DscRepoSchema}; +use crate::{ + dscerror::DscError, + schemas::{dsc_repo::{DscRepoSchema, UnrecognizedSchemaUri}, transforms::idiomaticize_string_enum}, +}; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] @@ -259,18 +261,18 @@ impl DscRepoSchema for ResourceManifest { const SCHEMA_FOLDER_PATH: &'static str = "resource"; const SCHEMA_SHOULD_BUNDLE: bool = true; - fn schema_metadata() -> Schema { + fn schema_property_metadata() -> Schema { json_schema!({ "title": t!("dscresources.resource_manifest.resourceManifestSchemaTitle").to_string(), "description": t!("dscresources.resource_manifest.resourceManifestSchemaDescription").to_string(), }) } - fn validate_schema_uri(&self) -> Result<(), DscError> { + fn validate_schema_uri(&self) -> Result<(), UnrecognizedSchemaUri> { if Self::is_recognized_schema_uri(&self.schema_version) { Ok(()) } else { - Err(DscError::UnrecognizedSchemaUri( + Err(UnrecognizedSchemaUri( self.schema_version.clone(), Self::recognized_schema_uris(), )) @@ -321,11 +323,9 @@ pub fn validate_semver(version: &str) -> Result<(), semver::Error> { #[cfg(test)] mod test { - use crate::{ - dscerror::DscError, - dscresources::resource_manifest::ResourceManifest, - schemas::DscRepoSchema - }; + use crate::schemas::dsc_repo::{DscRepoSchema, UnrecognizedSchemaUri}; + + use crate::dscresources::resource_manifest::ResourceManifest; #[test] fn test_validate_schema_uri_with_invalid_uri() { @@ -343,13 +343,10 @@ mod test { assert!(result.as_ref().is_err()); match result.as_ref().unwrap_err() { - DscError::UnrecognizedSchemaUri(actual, recognized) => { + UnrecognizedSchemaUri(actual, recognized) => { assert_eq!(actual, &invalid_uri); assert_eq!(recognized, &ResourceManifest::recognized_schema_uris()) }, - _ => { - panic!("Expected validate_schema_uri() to error on unrecognized schema uri, but was {:?}", result.as_ref().unwrap_err()) - } } } diff --git a/lib/dsc-lib/src/extensions/dscextension.rs b/lib/dsc-lib/src/extensions/dscextension.rs index e668a3332..cc21002a9 100644 --- a/lib/dsc-lib/src/extensions/dscextension.rs +++ b/lib/dsc-lib/src/extensions/dscextension.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use crate::extensions::import::ImportMethod; -use dsc_lib_jsonschema::transforms::idiomaticize_string_enum; +use crate::schemas::transforms::idiomaticize_string_enum; use serde::{Deserialize, Serialize}; use serde_json::Value; use schemars::JsonSchema; diff --git a/lib/dsc-lib/src/extensions/extension_manifest.rs b/lib/dsc-lib/src/extensions/extension_manifest.rs index 4fb867afc..1337d48b6 100644 --- a/lib/dsc-lib/src/extensions/extension_manifest.rs +++ b/lib/dsc-lib/src/extensions/extension_manifest.rs @@ -8,8 +8,9 @@ use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; use std::collections::HashMap; -use crate::{dscerror::DscError, schemas::DscRepoSchema}; +use crate::dscerror::DscError; use crate::extensions::{discover::DiscoverMethod, import::ImportMethod, secret::SecretMethod}; +use crate::schemas::dsc_repo::{DscRepoSchema, UnrecognizedSchemaUri}; #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] #[serde(deny_unknown_fields)] @@ -51,18 +52,18 @@ impl DscRepoSchema for ExtensionManifest { const SCHEMA_FOLDER_PATH: &'static str = "extension"; const SCHEMA_SHOULD_BUNDLE: bool = true; - fn schema_metadata() -> Schema { + fn schema_property_metadata() -> Schema { json_schema!({ "title": t!("extensions.extension_manifest.extensionManifestSchemaTitle").to_string(), "description": t!("extensions.extension_manifest.extensionManifestSchemaDescription").to_string(), }) } - fn validate_schema_uri(&self) -> Result<(), DscError> { + fn validate_schema_uri(&self) -> Result<(), UnrecognizedSchemaUri> { if Self::is_recognized_schema_uri(&self.schema_version) { Ok(()) } else { - Err(DscError::UnrecognizedSchemaUri( + Err(UnrecognizedSchemaUri( self.schema_version.clone(), Self::recognized_schema_uris(), )) @@ -109,9 +110,9 @@ pub fn validate_semver(version: &str) -> Result<(), semver::Error> { #[cfg(test)] mod test { - use crate::{ - dscerror::DscError, extensions::extension_manifest::ExtensionManifest, schemas::DscRepoSchema - }; + use crate::schemas::dsc_repo::{DscRepoSchema, UnrecognizedSchemaUri}; + + use crate::extensions::extension_manifest::ExtensionManifest; #[test] fn test_validate_schema_uri_with_invalid_uri() { @@ -129,13 +130,10 @@ mod test { assert!(result.as_ref().is_err()); match result.as_ref().unwrap_err() { - DscError::UnrecognizedSchemaUri(actual, recognized) => { + UnrecognizedSchemaUri(actual, recognized) => { assert_eq!(actual, &invalid_uri); assert_eq!(recognized, &ExtensionManifest::recognized_schema_uris()) }, - _ => { - panic!("Expected validate_schema_uri() to error on unrecognized schema uri, but was {:?}", result.as_ref().unwrap_err()) - } } } diff --git a/lib/dsc-lib/src/lib.rs b/lib/dsc-lib/src/lib.rs index 26b53be75..32b0cd320 100644 --- a/lib/dsc-lib/src/lib.rs +++ b/lib/dsc-lib/src/lib.rs @@ -19,7 +19,9 @@ pub mod functions; pub mod parser; pub mod progress; pub mod util; -pub mod schemas; + +// Re-export the dependency crate to minimize dependency management. +pub use dsc_lib_jsonschema as schemas; i18n!("locales", fallback = "en-us"); diff --git a/lib/dsc-lib/src/schemas/mod.rs b/lib/dsc-lib/src/schemas/mod.rs deleted file mode 100644 index 85d1d1a88..000000000 --- a/lib/dsc-lib/src/schemas/mod.rs +++ /dev/null @@ -1,694 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -//! Contains helpers for JSON schemas and DSC - -use schemars::{Schema, JsonSchema, json_schema}; - -use crate::dscerror::DscError; - -/// Defines the URI prefix for the hosted schemas. -/// -/// While the schemas are currently hosted in the GitHub repository, DSC provides the shortened -/// `aka.ms` link for convenience. Using this enum simplifies migrating to a new URI for schemas -/// in the future. -#[derive(Debug, Default, Clone, Copy, Hash, Eq, PartialEq)] -pub enum SchemaUriPrefix { - #[default] - AkaDotMs, - Github, -} - -impl std::fmt::Display for SchemaUriPrefix { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::AkaDotMs => write!(f, "https://aka.ms/dsc/schemas"), - Self::Github => write!(f, "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas"), - } - } -} - -impl SchemaUriPrefix { - /// Returns every known URI prefix for convenient iteration. - #[must_use] - pub fn all() -> Vec { - vec![ - Self::AkaDotMs, - Self::Github, - ] - } -} - -/// Defines the different forms of JSON Schema that DSC publishes. -#[derive(Debug, Default, Clone, Copy, Hash, Eq, PartialEq)] -pub enum SchemaForm { - /// Indicates that the schema is bundled using the 2020-12 schema bundling contract. - /// - /// These schemas include all of their references in the `$defs` keyword where the key for - /// each reference is the `$id` of that subschema and the value is the subschema. - /// - /// The bundled schemas are preferred for offline usage or where network latency is a concern. - #[default] - Bundled, - /// Indicates that the schema is enhanced for interactively viewing, authoring, and editing - /// the data in VS Code. - /// - /// These schemas include keywords not recognized by JSON Schema libraries and clients outside - /// of VS Code, like `markdownDescription` and `defaultSnippets`. The schema references and - /// definitions do not follow the canonical bundling for schema 2020-12, as the VS Code - /// JSON language server doesn't correctly resolve canonically bundled schemas. - VSCode, - /// Indicates that the schema is canonical but not bundled. It may contain references to other - /// JSON Schemas that require resolution by retrieving those schemas over the network. All - /// DSC schemas are published in this form for easier review, reuse, and retrieval. - Canonical, -} - -impl SchemaForm { - /// Returns the file extension for a given form of schema. - /// - /// The extension for [`Bundled`] and [`Canonical`] schemas is `.json` - /// - /// The extension for [`VSCode`] schemas is `.vscode.json` - /// - /// [`Bundled`]: SchemaForm::Bundled - /// [`Canonical`]: SchemaForm::Canonical - /// [`VSCode`]: SchemaForm::VSCode - #[must_use] - pub fn to_extension(&self) -> String { - match self { - Self::Bundled | Self::Canonical => ".json".to_string(), - Self::VSCode => ".vscode.json".to_string(), - } - } - - /// Return the prefix for a schema's folder path. - /// - /// The [`Bundled`] and [`VSCode`] schemas are always published in the `bundled` folder - /// immediately beneath the version folder. The [`Canonical`] schemas use the folder path - /// as defined for that schema. - /// - /// [`Bundled`]: SchemaForm::Bundled - /// [`Canonical`]: SchemaForm::Canonical - /// [`VSCode`]: SchemaForm::VSCode - #[must_use] - pub fn to_folder_prefix(&self) -> String { - match self { - Self::Bundled | Self::VSCode => "bundled/".to_string(), - Self::Canonical => String::new(), - } - } - - /// Returns every schema form for convenient iteration. - #[must_use] - pub fn all() -> Vec { - vec![ - Self::Bundled, - Self::VSCode, - Self::Canonical, - ] - } -} - -/// Defines the versions of DSC recognized for schema validation and handling. -/// -/// The DSC schemas are published into three folders: -/// -/// - `v..` always includes the exact JSON Schema that shipped in that release -/// of DSC. -/// - `v.` always includes the latest JSON Schema compatible with that minor version -/// of DSC. -/// - `v` always includes the latest JSON Schema compatible with that major version of DSC. -/// -/// Pinning to `v` requires the least-frequent updating of the `$schema` in configuration -/// documents and resource manifests, but also introduces changes that affect those schemas -/// (without breaking changes) regularly. Some of the added features may not be effective in the -/// version of DSC a user has installed. -/// -/// Pinning to `v.` ensures that users always have the latest schemas for the version -/// of DSC they're using without schema changes that they may not be able to take advantage of. -/// However, it requires updating the resource manifests and configuration documents with each -/// minor release of DSC. -/// -/// Pinning to `v..` is the most specific option, but requires the most -/// frequent updating on the part of resource and configuration authors. -#[derive(Debug, Default, Clone, Copy, Hash, Eq, PartialEq)] -pub enum RecognizedSchemaVersion { - // Before any relase is published, this enum must be updated with the new version variants. - // Every release requires a patch version, like `V3_0_1` or `v3_1_0`. New minor releases also - // require a new minor version, like `v3_1`. - - /// Represents `v3` schema folder. - #[default] - V3, - /// Represents the `v3.1` schema folder. - V3_1, - /// Represents the `v3.1.0` schema folder. - V3_1_0, - /// Represents the `v3.0` schema folder. - V3_0, - /// Represents the `v3.0.2` schema folder. - V3_0_2, - /// Represents the `v3.0.1` schema folder. - V3_0_1, - /// Represents the `v3.0.0` schema folder. - V3_0_0, -} - -impl std::fmt::Display for RecognizedSchemaVersion { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::V3 => write!(f, "v3"), - Self::V3_1 => write!(f, "v3.1"), - Self::V3_1_0 => write!(f, "v3.1.0"), - Self::V3_0 => write!(f, "v3.0"), - Self::V3_0_2 => write!(f, "v3.0.2"), - Self::V3_0_1 => write!(f, "v3.0.1"), - Self::V3_0_0 => write!(f, "v3.0.0"), - } - } -} - -impl RecognizedSchemaVersion { - /// Returns every recognized schema version for convenient iteration. - #[must_use] - pub fn all() -> Vec { - vec![ - Self::V3, - Self::V3_1, - Self::V3_1_0, - Self::V3_0, - Self::V3_0_2, - Self::V3_0_1, - Self::V3_0_0, - ] - } - - //// Returns the latest version with major, minor, and patch segments, like `3.0.0`. - #[must_use] - pub fn latest() -> RecognizedSchemaVersion { - Self::V3_1_0 - } - - /// Returns the latest minor version for the latest major version, like `3.0`. - #[must_use] - pub fn latest_minor() -> RecognizedSchemaVersion { - Self::V3_1 - } - - /// Returns the latest major version, like `3` - #[must_use] - pub fn latest_major() -> RecognizedSchemaVersion { - Self::V3 - } -} - -/// Returns the constructed URI for a hosted DSC schema. -/// -/// This convenience function simplifies constructing the URIs for the various published schemas -/// that DSC recognizes, instead of needing to maintain long lists of those recognized schemas. -/// This function should primarily be called by [`get_recognized_schema_uris`], not called -/// directly. -/// -/// Parameters: -/// -/// - `schema_file_base_name` - specify the base name for the schema file, like `document` for -/// the configuration document schema or `manifest` for the resource manifest schema. -/// - `schema_folder_path` - specify the folder path for the schema file relative to the version -/// folder, like `config` for the configuration document schema or `resource` for the resource -/// manifest schema. -/// - `schema_version` - specify the version of the schema. -/// - `schema_form` - specify whether the schema is bundled, for VS Code, or is the canonical -/// (non-bundled) schema. -/// - `uri_prefix` - Specify whether the URI should be prefixed for `aka.ms` or GitHub. -pub(crate) fn get_recognized_schema_uri( - schema_file_base_name: &str, - schema_folder_path: &str, - schema_version: RecognizedSchemaVersion, - schema_form: SchemaForm, - schema_uri_prefix: SchemaUriPrefix -) -> String { - format!( - "{schema_uri_prefix}/{schema_version}/{}{schema_folder_path}/{schema_file_base_name}{}", - schema_form.to_folder_prefix(), - schema_form.to_extension() - ) -} - -/// Returns the vector of recognized URIs for a given schema. -/// -/// This convenience function generates a vector containing every recognized JSON Schema `$id` URI -/// for a specific schema. It handles returning the schemas for every recognized host, version, -/// and form. -/// -/// Parameters: -/// -/// - `schema_file_base_name` - specify the base name for the schema file, like `document` for -/// the configuration document schema or `manifest` for the resource manifest schema. -/// - `schema_folder_path` - specify the folder path for the schema file relative to the version -/// folder, like `config` for the configuration document schema or `resource` for the resource -/// manifest schema. -/// - `should_bundle` - specify whether the schema should be published in its bundled form. All -/// bundled schemas are also published with their VS Code form. Schemas that aren't bundled -/// aren't published with the VS Code form. -pub(crate) fn get_recognized_schema_uris( - schema_file_base_name: &str, - schema_folder_path: &str, - should_bundle: bool -) -> Vec { - let mut uris: Vec = Vec::new(); - let schema_forms = if should_bundle { - SchemaForm::all() - } else { - vec![SchemaForm::Canonical] - }; - for uri_prefix in SchemaUriPrefix::all() { - for schema_form in schema_forms.iter().copied() { - for schema_version in RecognizedSchemaVersion::all() { - uris.push( - get_recognized_schema_uri( - schema_file_base_name, - schema_folder_path, - schema_version, - schema_form, - uri_prefix - ) - ); - } - } - } - - uris -} - -/// Returns the JSON Schema to validate that a `$schema` keyword for a DSC type is one of the -/// recognized URIs. -/// -/// This is a convenience function used by the [`DscRepoSchema`] trait. It's not intended for -/// direct use. -#[must_use] -pub(crate) fn get_recognized_uris_subschema( - metadata: &Schema, - schema_file_base_name: &str, - schema_folder_path: &str, - should_bundle: bool -) -> Schema { - let enums: Vec = get_recognized_schema_uris( - schema_file_base_name, - schema_folder_path, - should_bundle - ).iter().map( - |schema_uri| serde_json::Value::String(schema_uri.clone()) - ).collect(); - - json_schema!({ - "type": "string", - "format": Some("uri".to_string()), - "enum": Some(enums), - "title": metadata.get("title"), - "description": metadata.get("description"), - }) -} - -/// Returns the recognized schema URI for the latest major version with the -/// `aka.ms` URI prefix. -/// -/// If the schema is published in bundled form, this function returns the URI for that form. -/// Otherwise, it returns the URI for the canonical (non-bundled) form. The VS Code form of the -/// schema is never returned as the default. -/// -/// Parameters: -/// -/// - `schema_file_base_name` - specify the base name for the schema file, like `document` for -/// the configuration document schema or `manifest` for the resource manifest schema. -/// - `schema_folder_path` - specify the folder path for the schema file relative to the version -/// folder, like `config` for the configuration document schema or `resource` for the resource -/// manifest schema. -/// - `should_bundle` - specify whether the schema should be published in its bundled form. All -/// bundled schemas are also published with their VS Code form. Schemas that aren't bundled -/// aren't published with the VS Code form. -pub(crate) fn get_default_schema_uri( - schema_file_base_name: &str, - schema_folder_path: &str, - should_bundle: bool -) -> String { - get_recognized_schema_uri( - schema_file_base_name, - schema_folder_path, - RecognizedSchemaVersion::default(), - get_default_schema_form(should_bundle), - SchemaUriPrefix::default() - ) -} - -/// Returns the default form for a schema depending on whether it publishes with its references -/// bundled. -/// -/// If a schema is published in bundled form, the bundled form is the default. Otherwise, the -/// default form is canonical (non-bundled). -fn get_default_schema_form(should_bundle: bool) -> SchemaForm { - if should_bundle { - SchemaForm::Bundled - } else { - SchemaForm::Canonical - } -} - -/// Defines a reusable trait to simplify managing multiple versions of JSON Schemas for DSC -/// structs and enums. -/// -/// This trait is only intended for use by definitions in the DSC repository. -pub trait DscRepoSchema : JsonSchema { - /// Defines the base name for the exported JSON Schema, like `document` for - /// [`Configuration`]. - /// - /// [`Configuration`]: crate::configure::config_doc::Configuration - const SCHEMA_FILE_BASE_NAME: &'static str; - - /// Defines the folder path for the schema relative to the published version folder, like - /// `config` for [`Configuration`]. - /// - /// [`Configuration`]: crate::configure::config_doc::Configuration - const SCHEMA_FOLDER_PATH: &'static str; - - /// Indicates whether the schema should be published in its bundled form. All bundled schemas - /// are also published with their VS Code form. Schemas that aren't bundled aren't published - /// with the VS Code form. - const SCHEMA_SHOULD_BUNDLE: bool; - fn schema_metadata() -> Schema; - - /// Returns the default URI for the schema. - /// - /// An object representing an instance of the schema can specify any valid URI, but the - /// default when creating an instance is the latest major version of the schema with the - /// `aka.ms` prefix. If the schema is published in the bundled form, the default is for the - /// bundled schema. Otherwise, the default is for the canonical (non-bundled) schema. - #[must_use] - fn default_schema_id_uri() -> String { - get_default_schema_uri( - Self::SCHEMA_FILE_BASE_NAME, - Self::SCHEMA_FOLDER_PATH, - Self::SCHEMA_SHOULD_BUNDLE - ) - } - - /// Returns the schema URI for a given version, form, and prefix. - #[must_use] - fn get_schema_id_uri( - schema_version: RecognizedSchemaVersion, - schema_form: SchemaForm, - uri_prefix: SchemaUriPrefix - ) -> String { - get_recognized_schema_uri( - Self::SCHEMA_FILE_BASE_NAME, - Self::SCHEMA_FOLDER_PATH, - schema_version, - schema_form, - uri_prefix - ) - } - - /// Returns the URI for the VS Code form of the schema with the default prefix for a given - /// version. - /// - /// If the type isn't published in bundled form, this function returns `None`. - #[must_use] - fn get_enhanced_schema_id_uri(schema_version: RecognizedSchemaVersion) -> Option { - if !Self::SCHEMA_SHOULD_BUNDLE { - return None; - } - - Some(get_recognized_schema_uri( - Self::SCHEMA_FILE_BASE_NAME, - Self::SCHEMA_FOLDER_PATH, - schema_version, - SchemaForm::VSCode, - SchemaUriPrefix::default() - )) - } - - /// Returns the URI for the canonical (non-bundled) form of the schema with the default - /// prefix for a given version. - #[must_use] - fn get_canonical_schema_id_uri(schema_version: RecognizedSchemaVersion) -> String { - get_recognized_schema_uri( - Self::SCHEMA_FILE_BASE_NAME, - Self::SCHEMA_FOLDER_PATH, - schema_version, - SchemaForm::Canonical, - SchemaUriPrefix::default() - ) - } - - /// Returns the URI for the bundled form of the schema with the default prefix for a given - /// version. - #[must_use] - fn get_bundled_schema_id_uri(schema_version: RecognizedSchemaVersion) -> Option { - if !Self::SCHEMA_SHOULD_BUNDLE { - return None; - } - - Some(get_recognized_schema_uri( - Self::SCHEMA_FILE_BASE_NAME, - Self::SCHEMA_FOLDER_PATH, - schema_version, - SchemaForm::Bundled, - SchemaUriPrefix::default() - )) - } - - /// Returns the list of recognized schema URIs for the struct or enum. - /// - /// This convenience function generates a vector containing every recognized JSON Schema `$id` - /// URI for a specific schema. It handles returning the schemas for every recognized prefix, - /// version, and form. - #[must_use] - fn recognized_schema_uris() -> Vec { - get_recognized_schema_uris( - Self::SCHEMA_FILE_BASE_NAME, - Self::SCHEMA_FOLDER_PATH, - Self::SCHEMA_SHOULD_BUNDLE - ) - } - - /// Returns the subschema to validate a `$schema` keyword pointing to the type. - /// - /// Every schema has a canonical `$id`, but DSC needs to maintain compatibility with schemas - /// within a major version and ensure that previous schema versions can be correctly - /// recognized and validated. This method generates the appropriate subschema with every - /// valid URI for the schema's `$id` without needing to regularly update an enum for each - /// schema and release. - #[must_use] - fn recognized_schema_uris_subschema(_: &mut schemars::SchemaGenerator) -> Schema { - get_recognized_uris_subschema( - &Self::schema_metadata(), - Self::SCHEMA_FILE_BASE_NAME, - Self::SCHEMA_FOLDER_PATH, - Self::SCHEMA_SHOULD_BUNDLE - ) - } - - /// Indicates whether a given string is a recognized shema URI. - #[must_use] - fn is_recognized_schema_uri(uri: &String) -> bool { - Self::recognized_schema_uris().contains(uri) - } - - /// Validates the `$schema` keyword for deserializing instances. - /// - /// This method simplifies the validation of a type that has the `$schema` keyword and expects - /// that instances of the type in data indicate which schema version DSC should use to validate - /// them. - /// - /// This method includes a default implementation to avoid requiring the implementation for - /// types that don't define the `$schema` keyword in their serialized form. - /// - /// Any DSC type that serializes with the `$schema` keyword **must** define this - /// method to actually validate the instance. - /// - /// # Errors - /// - /// If the value for the schema field isn't a recognized schema, the method should raise the - /// [`DscError::UnrecognizedSchemaUri`] error. - fn validate_schema_uri(&self) -> Result<(), DscError> { - Ok(()) - } -} - -#[cfg(test)] -mod test { - use serde::{Deserialize, Serialize}; - - use super::*; - - #[test] - fn test_get_recognized_schema_uri() { - let expected = "https://aka.ms/dsc/schemas/v3/bundled/config/document.json".to_string(); - let actual = get_recognized_schema_uri( - "document", - "config", - RecognizedSchemaVersion::V3, - SchemaForm::Bundled, - SchemaUriPrefix::AkaDotMs - ); - assert_eq!(expected, actual) - } - - #[test] - fn test_get_recognized_schema_uris() { - let expected: Vec = vec![ - "https://aka.ms/dsc/schemas/v3/bundled/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3.1/bundled/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3.1.0/bundled/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3.0/bundled/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3.0.2/bundled/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3.0.1/bundled/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3.0.0/bundled/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3/bundled/config/document.vscode.json".to_string(), - "https://aka.ms/dsc/schemas/v3.1/bundled/config/document.vscode.json".to_string(), - "https://aka.ms/dsc/schemas/v3.1.0/bundled/config/document.vscode.json".to_string(), - "https://aka.ms/dsc/schemas/v3.0/bundled/config/document.vscode.json".to_string(), - "https://aka.ms/dsc/schemas/v3.0.2/bundled/config/document.vscode.json".to_string(), - "https://aka.ms/dsc/schemas/v3.0.1/bundled/config/document.vscode.json".to_string(), - "https://aka.ms/dsc/schemas/v3.0.0/bundled/config/document.vscode.json".to_string(), - "https://aka.ms/dsc/schemas/v3/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3.1/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3.1.0/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3.0/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3.0.2/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3.0.1/config/document.json".to_string(), - "https://aka.ms/dsc/schemas/v3.0.0/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1.0/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.2/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.1/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.0/bundled/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1.0/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.2/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.1/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.0/bundled/config/document.vscode.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.1.0/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.2/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.1/config/document.json".to_string(), - "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3.0.0/config/document.json".to_string(), - ]; - - let actual = get_recognized_schema_uris( - "document", - "config", - true - ); - - for (index, expected_uri) in expected.iter().enumerate() { - assert_eq!(*expected_uri, actual[index]); - } - // assert_eq!(expected, actual) - } - - #[test] - fn test_get_default_schema_uri() { - let expected_bundled = "https://aka.ms/dsc/schemas/v3/bundled/config/document.json".to_string(); - let expected_canonical = "https://aka.ms/dsc/schemas/v3/config/document.json".to_string(); - - let schema_file_base_name = "document"; - let schema_folder_path = "config"; - - assert_eq!(expected_bundled, get_default_schema_uri(schema_file_base_name, schema_folder_path, true)); - assert_eq!(expected_canonical, get_default_schema_uri(schema_file_base_name, schema_folder_path, false)) - } - - #[test] - fn test_dsc_repo_schema_bundled() { - #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] - struct ExampleBundledSchema { - pub schema_version: String, - } - - impl DscRepoSchema for ExampleBundledSchema { - const SCHEMA_FILE_BASE_NAME: &'static str = "schema"; - const SCHEMA_FOLDER_PATH: &'static str = "example"; - const SCHEMA_SHOULD_BUNDLE: bool = true; - - fn schema_metadata() -> Schema { - json_schema!({ - "description": "An example schema for testing.", - }) - } - } - - let bundled_uri = "https://aka.ms/dsc/schemas/v3/bundled/example/schema.json".to_string(); - let vscode_uri = "https://aka.ms/dsc/schemas/v3/bundled/example/schema.vscode.json".to_string(); - let canonical_uri = "https://aka.ms/dsc/schemas/v3/example/schema.json".to_string(); - let schema_version = RecognizedSchemaVersion::V3; - - assert_eq!( - bundled_uri, - ExampleBundledSchema::default_schema_id_uri() - ); - - assert_eq!( - Some(bundled_uri), - ExampleBundledSchema::get_bundled_schema_id_uri(schema_version) - ); - - assert_eq!( - Some(vscode_uri), - ExampleBundledSchema::get_enhanced_schema_id_uri(schema_version) - ); - - assert_eq!( - canonical_uri, - ExampleBundledSchema::get_canonical_schema_id_uri(schema_version) - ) - } - - #[test] - fn test_dsc_repo_schema_not_bundled() { - #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize, JsonSchema)] - struct ExampleNotBundledSchema { - pub schema_version: String, - } - - impl DscRepoSchema for ExampleNotBundledSchema { - const SCHEMA_FILE_BASE_NAME: &'static str = "schema"; - const SCHEMA_FOLDER_PATH: &'static str = "example"; - const SCHEMA_SHOULD_BUNDLE: bool = false; - - fn schema_metadata() -> Schema { - json_schema!({ - "description": "An example schema for testing.", - }) - } - } - - let canonical_uri = "https://aka.ms/dsc/schemas/v3/example/schema.json".to_string(); - let schema_version = RecognizedSchemaVersion::V3; - assert_eq!( - canonical_uri, - ExampleNotBundledSchema::default_schema_id_uri() - ); - - assert_eq!( - None, - ExampleNotBundledSchema::get_bundled_schema_id_uri(schema_version) - ); - - assert_eq!( - None, - ExampleNotBundledSchema::get_enhanced_schema_id_uri(schema_version) - ); - - assert_eq!( - canonical_uri, - ExampleNotBundledSchema::get_canonical_schema_id_uri(schema_version) - ) - } -} diff --git a/tools/test_group_resource/src/main.rs b/tools/test_group_resource/src/main.rs index 1536aab27..f3888c408 100644 --- a/tools/test_group_resource/src/main.rs +++ b/tools/test_group_resource/src/main.rs @@ -7,7 +7,7 @@ use args::{Args, SubCommand}; use clap::Parser; use dsc_lib::dscresources::resource_manifest::{ResourceManifest, GetMethod, Kind}; use dsc_lib::dscresources::dscresource::{Capability, DscResource, ImplementedAs}; -use dsc_lib::schemas::DscRepoSchema; +use dsc_lib::schemas::dsc_repo::DscRepoSchema; use std::path::PathBuf; fn main() {