From e7b89d7d3c8b33dee30932bbc5f5aab4aa9f2c5b Mon Sep 17 00:00:00 2001 From: Eric Swanson <64809312+ericswanson-dfinity@users.noreply.github.com> Date: Tue, 25 Jun 2024 16:13:41 -0700 Subject: [PATCH] feat: generate extension manifest schema (#3806) --- .github/workflows/update-docs.yml | 1 + CHANGELOG.md | 4 + docs/cli-reference/dfx-schema.mdx | 2 +- docs/extension-manifest-schema.json | 233 ++++++++++++++++++ .../src/extension/manifest/extension.rs | 13 +- src/dfx/src/commands/schema.rs | 4 + 6 files changed, 250 insertions(+), 7 deletions(-) create mode 100644 docs/extension-manifest-schema.json diff --git a/.github/workflows/update-docs.yml b/.github/workflows/update-docs.yml index fc710ab4c2..7c1c1c5208 100644 --- a/.github/workflows/update-docs.yml +++ b/.github/workflows/update-docs.yml @@ -28,6 +28,7 @@ jobs: cargo run -- schema --outfile docs/dfx-json-schema.json cargo run -- schema --for networks --outfile docs/networks-json-schema.json cargo run -- schema --for dfx-metadata --outfile docs/dfx-metadata-schema.json + cargo run -- schema --for extension-manifest --outfile docs/extension-manifest-schema.json echo "JSON Schema changes:" if git diff --exit-code ; then diff --git a/CHANGELOG.md b/CHANGELOG.md index f4813c9b9a..27b17a1acc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ # UNRELEASED +### feat: add `dfx schema --for extension-manifest` + +The schema command can now output the schema for extension.json files. + # 0.21.0 ### feat: dfx killall diff --git a/docs/cli-reference/dfx-schema.mdx b/docs/cli-reference/dfx-schema.mdx index bab489083a..2e2655361f 100644 --- a/docs/cli-reference/dfx-schema.mdx +++ b/docs/cli-reference/dfx-schema.mdx @@ -18,7 +18,7 @@ You can use the following option with the `dfx schema` command. | Option | Description | |------------------------|--------------------------------------------------------------------------------------------------| -| `--for ` | Display schema for which JSON file. (default: dfx, possible values: dfx, networks, dfx-metadata) | +| `--for ` | Display schema for which JSON file. (default: dfx, possible values: dfx, networks, dfx-metadata, extension-manifest) | | `--outfile ` | Specifies a file to output the schema to instead of printing it to stdout. | ## Examples diff --git a/docs/extension-manifest-schema.json b/docs/extension-manifest-schema.json new file mode 100644 index 0000000000..e6f095a8d5 --- /dev/null +++ b/docs/extension-manifest-schema.json @@ -0,0 +1,233 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExtensionManifest", + "type": "object", + "required": [ + "categories", + "homepage", + "name", + "summary", + "version" + ], + "properties": { + "authors": { + "type": [ + "string", + "null" + ] + }, + "canister_type": { + "anyOf": [ + { + "$ref": "#/definitions/ExtensionCanisterType" + }, + { + "type": "null" + } + ] + }, + "categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "dependencies": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "homepage": { + "type": "string" + }, + "keywords": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "subcommands": { + "anyOf": [ + { + "$ref": "#/definitions/ExtensionSubcommandsOpts" + }, + { + "type": "null" + } + ] + }, + "summary": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false, + "definitions": { + "ArgNumberOfValues": { + "oneOf": [ + { + "description": "zero or more values", + "type": "object", + "required": [ + "Number" + ], + "properties": { + "Number": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "non-inclusive range", + "type": "object", + "required": [ + "Range" + ], + "properties": { + "Range": { + "$ref": "#/definitions/Range_of_uint" + } + }, + "additionalProperties": false + }, + { + "description": "unlimited values", + "type": "string", + "enum": [ + "Unlimited" + ] + } + ] + }, + "ExtensionCanisterType": { + "type": "object", + "properties": { + "defaults": { + "description": "Default values for the canister type. These values are used when the user does not provide values in dfx.json. The \"metadata\" field, if present, is appended to the metadata field from dfx.json, which has the effect of providing defaults. The \"tech_stack field, if present, it merged with the tech_stack field from dfx.json, which also has the effect of providing defaults.", + "default": {}, + "type": "object", + "additionalProperties": true + }, + "evaluation_order": { + "description": "If one field depends on another and both specify a handlebars expression, list the fields in the order that they should be evaluated.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "ExtensionSubcommandArgOpts": { + "type": "object", + "properties": { + "about": { + "type": [ + "string", + "null" + ] + }, + "long": { + "type": [ + "string", + "null" + ] + }, + "multiple": { + "default": false, + "deprecated": true, + "type": "boolean" + }, + "short": { + "type": [ + "string", + "null" + ], + "maxLength": 1, + "minLength": 1 + }, + "values": { + "$ref": "#/definitions/ArgNumberOfValues" + } + }, + "additionalProperties": false + }, + "ExtensionSubcommandOpts": { + "type": "object", + "properties": { + "about": { + "type": [ + "string", + "null" + ] + }, + "args": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/ExtensionSubcommandArgOpts" + } + }, + "subcommands": { + "anyOf": [ + { + "$ref": "#/definitions/ExtensionSubcommandsOpts" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "ExtensionSubcommandsOpts": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ExtensionSubcommandOpts" + } + }, + "Range_of_uint": { + "type": "object", + "required": [ + "end", + "start" + ], + "properties": { + "end": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + } + } + } +} \ No newline at end of file diff --git a/src/dfx-core/src/extension/manifest/extension.rs b/src/dfx-core/src/extension/manifest/extension.rs index fb6747f96b..6a63e2356d 100644 --- a/src/dfx-core/src/extension/manifest/extension.rs +++ b/src/dfx-core/src/extension/manifest/extension.rs @@ -2,6 +2,7 @@ use crate::error::extension::{ ConvertExtensionSubcommandIntoClapArgError, ConvertExtensionSubcommandIntoClapCommandError, LoadExtensionManifestError, }; +use schemars::JsonSchema; use serde::{Deserialize, Deserializer}; use serde_json::Value; use std::path::PathBuf; @@ -15,7 +16,7 @@ pub static MANIFEST_FILE_NAME: &str = "extension.json"; type SubcmdName = String; type ArgName = String; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ExtensionManifest { pub name: String, @@ -62,7 +63,7 @@ impl ExtensionManifest { } } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, JsonSchema)] pub struct ExtensionCanisterType { /// If one field depends on another and both specify a handlebars expression, /// list the fields in the order that they should be evaluated. @@ -79,10 +80,10 @@ pub struct ExtensionCanisterType { pub defaults: BTreeMap, } -#[derive(Debug, Deserialize, Default)] +#[derive(Debug, Deserialize, Default, JsonSchema)] pub struct ExtensionSubcommandsOpts(BTreeMap); -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ExtensionSubcommandOpts { pub about: Option, @@ -90,7 +91,7 @@ pub struct ExtensionSubcommandOpts { pub subcommands: Option, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] pub struct ExtensionSubcommandArgOpts { pub about: Option, @@ -103,7 +104,7 @@ pub struct ExtensionSubcommandArgOpts { pub values: ArgNumberOfValues, } -#[derive(Debug)] +#[derive(Debug, JsonSchema)] pub enum ArgNumberOfValues { /// zero or more values Number(usize), diff --git a/src/dfx/src/commands/schema.rs b/src/dfx/src/commands/schema.rs index 964380af82..b3f7d506ae 100644 --- a/src/dfx/src/commands/schema.rs +++ b/src/dfx/src/commands/schema.rs @@ -10,6 +10,7 @@ enum ForFile { Dfx, Networks, DfxMetadata, + ExtensionManifest, } /// Prints the schema for dfx.json. @@ -27,6 +28,9 @@ pub fn exec(opts: SchemaOpts) -> DfxResult { let schema = match opts.r#for { Some(ForFile::Networks) => schema_for!(TopLevelConfigNetworks), Some(ForFile::DfxMetadata) => schema_for!(DfxMetadata), + Some(ForFile::ExtensionManifest) => { + schema_for!(dfx_core::extension::manifest::ExtensionManifest) + } _ => schema_for!(ConfigInterface), }; let nice_schema =