Skip to content

Commit c2d2a6b

Browse files
Eric Swansonrikonor
authored andcommitted
feat: type definitions for extension dependencies and dependencies.json (#3807)
1 parent 8a083db commit c2d2a6b

File tree

10 files changed

+169
-18
lines changed

10 files changed

+169
-18
lines changed

.github/workflows/update-docs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
cargo run -- schema --for networks --outfile docs/networks-json-schema.json
3030
cargo run -- schema --for dfx-metadata --outfile docs/dfx-metadata-schema.json
3131
cargo run -- schema --for extension-manifest --outfile docs/extension-manifest-schema.json
32+
cargo run -- schema --for extension-dependencies --outfile docs/extension-dependencies-schema.json
3233
3334
echo "JSON Schema changes:"
3435
if git diff --exit-code ; then

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88

99
To do this, the `@dfinity/agent` version was updated as well.
1010

11-
### feat: add `dfx schema --for extension-manifest`
11+
### feat: add `dfx schema` support for .json files related to extensions
1212

13-
The schema command can now output the schema for extension.json files.
13+
- `dfx schema --for extension-manifest` corresponds to extension.json
14+
- `dfx schema --for extension-dependencies` corresponds to dependencies.json
1415

1516
### chore!: enforce minimum password length of 9 characters
1617

docs/cli-reference/dfx-schema.mdx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,20 @@ dfx schema [option] [flag]
1616

1717
You can use the following option with the `dfx schema` command.
1818

19-
| Option | Description |
20-
|------------------------|--------------------------------------------------------------------------------------------------|
21-
| `--for <which>` | Display schema for which JSON file. (default: dfx, possible values: dfx, networks, dfx-metadata, extension-manifest) |
22-
| `--outfile <outfile>` | Specifies a file to output the schema to instead of printing it to stdout. |
19+
| Option | Description |
20+
|------------------------|----------------------------------------------------------------------------|
21+
| `--for <which>` | Display schema for which JSON file. (default: dfx) |
22+
| `--outfile <outfile>` | Specifies a file to output the schema to instead of printing it to stdout. |
23+
24+
The parameter passed to `--for` can be one of the following:
25+
26+
| `<for>` option | Corresponds To |
27+
|------------------------|--------------------------------------------------------|
28+
| dfx | dfx.json |
29+
| networks | networks.json |
30+
| dfx-metadata | dfx-related metadata stored in canister and accessible with `dfx canister metadata <canister> dfx` |
31+
| extension-manifest | extension.json [example][example-extension-json] |
32+
| extension-dependencies | dependencies.json [example][example-dependencies-json] |
2333

2434
## Examples
2535

@@ -46,3 +56,6 @@ If you want to write the schema for dfx.json to `path/to/file/schema.json`, you
4656
``` bash
4757
dfx schema --outfile path/to/file/schema.json
4858
```
59+
60+
[example-extension-json]: https://raw.githubusercontent.com/dfinity/dfx-extensions/main/extensions/nns/extension.json
61+
[example-dependencies-json]: https://raw.githubusercontent.com/dfinity/dfx-extensions/main/extensions/nns/dependencies.json
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-07/schema#",
3+
"title": "ExtensionDependencies",
4+
"type": "object",
5+
"additionalProperties": {
6+
"type": "object",
7+
"additionalProperties": {
8+
"$ref": "#/definitions/DependencyRequirement"
9+
}
10+
},
11+
"definitions": {
12+
"DependencyRequirement": {
13+
"oneOf": [
14+
{
15+
"description": "A SemVer version requirement, for example \">=0.17.0, <0.19.0\".",
16+
"type": "object",
17+
"required": [
18+
"version"
19+
],
20+
"properties": {
21+
"version": {
22+
"type": "string"
23+
}
24+
},
25+
"additionalProperties": false
26+
}
27+
]
28+
}
29+
}
30+
}

docs/extension-manifest-schema.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"null"
3939
],
4040
"additionalProperties": {
41-
"type": "string"
41+
"$ref": "#/definitions/ExtensionDependency"
4242
}
4343
},
4444
"description": {
@@ -139,6 +139,14 @@
139139
}
140140
}
141141
},
142+
"ExtensionDependency": {
143+
"anyOf": [
144+
{
145+
"description": "A SemVer version requirement, for example \">=0.17.0\".",
146+
"type": "string"
147+
}
148+
]
149+
},
142150
"ExtensionSubcommandArgOpts": {
143151
"type": "object",
144152
"properties": {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use crate::json::structure::VersionReqWithJsonSchema;
2+
use candid::Deserialize;
3+
use schemars::JsonSchema;
4+
use semver::Version;
5+
use std::collections::HashMap;
6+
7+
type ExtensionVersion = Version;
8+
type DependencyName = String;
9+
10+
#[derive(Debug, Deserialize, JsonSchema)]
11+
#[serde(rename_all = "lowercase")]
12+
pub enum DependencyRequirement {
13+
/// A SemVer version requirement, for example ">=0.17.0, <0.19.0".
14+
Version(VersionReqWithJsonSchema),
15+
}
16+
17+
#[derive(Deserialize, Debug, JsonSchema)]
18+
pub struct ExtensionDependencies(
19+
pub HashMap<ExtensionVersion, HashMap<DependencyName, DependencyRequirement>>,
20+
);
21+
22+
#[test]
23+
fn parse_test_file() {
24+
let f = r#"
25+
{
26+
"0.3.4": {
27+
"dfx": {
28+
"version": ">=0.8, <0.9"
29+
}
30+
},
31+
"0.6.2": {
32+
"dfx": {
33+
"version": ">=0.9.6"
34+
}
35+
}
36+
}
37+
"#;
38+
let m: Result<ExtensionDependencies, serde_json::Error> = dbg!(serde_json::from_str(f));
39+
assert!(m.is_ok());
40+
let manifest = m.unwrap();
41+
42+
let versions = manifest.0.keys().collect::<Vec<_>>();
43+
assert_eq!(versions.len(), 2);
44+
assert!(versions.contains(&&Version::new(0, 3, 4)));
45+
assert!(versions.contains(&&Version::new(0, 6, 2)));
46+
47+
let v_3_4 = manifest.0.get(&Version::new(0, 3, 4)).unwrap();
48+
let dfx = v_3_4.get("dfx").unwrap();
49+
let DependencyRequirement::Version(req) = dfx;
50+
assert!(req.matches(&semver::Version::new(0, 8, 5)));
51+
assert!(!req.matches(&semver::Version::new(0, 9, 0)));
52+
53+
let v_6_2 = manifest.0.get(&Version::new(0, 6, 2)).unwrap();
54+
let dfx = v_6_2.get("dfx").unwrap();
55+
let DependencyRequirement::Version(req) = dfx;
56+
assert!(req.matches(&semver::Version::new(0, 9, 6)));
57+
assert!(!req.matches(&semver::Version::new(0, 9, 5)));
58+
}

src/dfx-core/src/extension/manifest/extension.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::error::extension::{
22
ConvertExtensionSubcommandIntoClapArgError, ConvertExtensionSubcommandIntoClapCommandError,
33
LoadExtensionManifestError,
44
};
5+
use crate::json::structure::VersionReqWithJsonSchema;
56
use schemars::JsonSchema;
67
use serde::{Deserialize, Deserializer};
78
use serde_json::Value;
@@ -28,10 +29,17 @@ pub struct ExtensionManifest {
2829
pub keywords: Option<Vec<String>>,
2930
pub description: Option<String>,
3031
pub subcommands: Option<ExtensionSubcommandsOpts>,
31-
pub dependencies: Option<HashMap<String, String>>,
32+
pub dependencies: Option<HashMap<String, ExtensionDependency>>,
3233
pub canister_type: Option<ExtensionCanisterType>,
3334
}
3435

36+
#[derive(Debug, Deserialize, JsonSchema)]
37+
#[serde(untagged)]
38+
pub enum ExtensionDependency {
39+
/// A SemVer version requirement, for example ">=0.17.0".
40+
Version(VersionReqWithJsonSchema),
41+
}
42+
3543
impl ExtensionManifest {
3644
pub fn load(
3745
name: &str,
@@ -236,6 +244,9 @@ fn parse_test_file() {
236244
"sns",
237245
"nns"
238246
],
247+
"dependencies": {
248+
"dfx": ">=0.8, <0.9"
249+
},
239250
"keywords": [
240251
"sns",
241252
"nns",
@@ -361,8 +372,15 @@ fn parse_test_file() {
361372

362373
let m: Result<ExtensionManifest, serde_json::Error> = dbg!(serde_json::from_str(f));
363374
assert!(m.is_ok());
375+
let manifest = m.unwrap();
376+
377+
let dependencies = manifest.dependencies.as_ref().unwrap();
378+
let dfx_dep = dependencies.get("dfx").unwrap();
379+
let ExtensionDependency::Version(req) = dfx_dep;
380+
assert!(req.matches(&semver::Version::new(0, 8, 5)));
381+
assert!(!req.matches(&semver::Version::new(0, 9, 0)));
364382

365-
let mut subcmds = dbg!(m.unwrap().into_clap_commands().unwrap());
383+
let mut subcmds = dbg!(manifest.into_clap_commands().unwrap());
366384

367385
use clap::error::ErrorKind::*;
368386
for c in &mut subcmds {

src/dfx-core/src/extension/manifest/mod.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
//! Directory contains code that parses the .json files.
22
33
pub mod compatibility_matrix;
4+
pub mod dependencies;
5+
pub mod extension;
6+
47
/// `compatibility.json` is a file describing the compatibility
58
/// matrix between extensions versions and the dfx version.
69
pub use compatibility_matrix::ExtensionCompatibilityMatrix;
7-
/// URL to the `compatibility.json` file.
8-
pub use compatibility_matrix::COMMON_EXTENSIONS_MANIFEST_LOCATION;
910

10-
pub mod extension;
11-
/// `manifest.json` is a file describing the extension.
11+
/// A file that lists the dependencies of all versions of an extension.
12+
pub use dependencies::ExtensionDependencies;
13+
14+
/// A file that describes an extension.
1215
pub use extension::ExtensionManifest;
13-
/// File name for the file describing the extension.
14-
pub use extension::MANIFEST_FILE_NAME;

src/dfx-core/src/json/structure.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use candid::Deserialize;
22
use schemars::JsonSchema;
3+
use semver::VersionReq;
34
use serde::Serialize;
45
use std::fmt::Display;
6+
use std::ops::{Deref, DerefMut};
57
use std::str::FromStr;
68

79
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, PartialEq, Eq)]
@@ -53,3 +55,21 @@ where
5355
}
5456
}
5557
}
58+
59+
#[derive(Debug, Deserialize, JsonSchema)]
60+
#[serde(transparent)]
61+
pub struct VersionReqWithJsonSchema(#[schemars(with = "String")] pub VersionReq);
62+
63+
impl Deref for VersionReqWithJsonSchema {
64+
type Target = VersionReq;
65+
66+
fn deref(&self) -> &Self::Target {
67+
&self.0
68+
}
69+
}
70+
71+
impl DerefMut for VersionReqWithJsonSchema {
72+
fn deref_mut(&mut self) -> &mut Self::Target {
73+
&mut self.0
74+
}
75+
}

src/dfx/src/commands/schema.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::lib::{error::DfxResult, metadata::dfx::DfxMetadata};
22
use anyhow::Context;
33
use clap::{Parser, ValueEnum};
44
use dfx_core::config::model::dfinity::{ConfigInterface, TopLevelConfigNetworks};
5+
use dfx_core::extension::manifest::{ExtensionDependencies, ExtensionManifest};
56
use schemars::schema_for;
67
use std::path::PathBuf;
78

@@ -10,6 +11,7 @@ enum ForFile {
1011
Dfx,
1112
Networks,
1213
DfxMetadata,
14+
ExtensionDependencies,
1315
ExtensionManifest,
1416
}
1517

@@ -28,9 +30,8 @@ pub fn exec(opts: SchemaOpts) -> DfxResult {
2830
let schema = match opts.r#for {
2931
Some(ForFile::Networks) => schema_for!(TopLevelConfigNetworks),
3032
Some(ForFile::DfxMetadata) => schema_for!(DfxMetadata),
31-
Some(ForFile::ExtensionManifest) => {
32-
schema_for!(dfx_core::extension::manifest::ExtensionManifest)
33-
}
33+
Some(ForFile::ExtensionDependencies) => schema_for!(ExtensionDependencies),
34+
Some(ForFile::ExtensionManifest) => schema_for!(ExtensionManifest),
3435
_ => schema_for!(ConfigInterface),
3536
};
3637
let nice_schema =

0 commit comments

Comments
 (0)