From 1e9d0fc2a27aad4cf5051d9d7701d8af59c632d7 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Thu, 21 Oct 2021 20:17:53 +0100 Subject: [PATCH 1/6] Added first cut design of Intermediate Representation module --- components/support/nimbus-fml/Cargo.toml | 5 +- components/support/nimbus-fml/src/error.rs | 22 +++ components/support/nimbus-fml/src/fixtures.rs | 5 + .../support/nimbus-fml/src/fixtures/ir.rs | 71 +++++++++ components/support/nimbus-fml/src/ir.rs | 138 ++++++++++++++++++ components/support/nimbus-fml/src/lib.rs | 6 + 6 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 components/support/nimbus-fml/src/error.rs create mode 100644 components/support/nimbus-fml/src/fixtures.rs create mode 100644 components/support/nimbus-fml/src/fixtures/ir.rs create mode 100644 components/support/nimbus-fml/src/ir.rs diff --git a/components/support/nimbus-fml/Cargo.toml b/components/support/nimbus-fml/Cargo.toml index ab4eaa9a98..1bd50c4c87 100644 --- a/components/support/nimbus-fml/Cargo.toml +++ b/components/support/nimbus-fml/Cargo.toml @@ -8,4 +8,7 @@ license = "MPL-2.0" [dependencies] clap = "2.33" -anyhow = "1" \ No newline at end of file +anyhow = "1.0.44" +serde_json = "1" +serde = { version = "1.0", features = ["derive"] } +thiserror = "1.0.29" diff --git a/components/support/nimbus-fml/src/error.rs b/components/support/nimbus-fml/src/error.rs new file mode 100644 index 0000000000..85c18072f2 --- /dev/null +++ b/components/support/nimbus-fml/src/error.rs @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * */ + +//! Not complete yet +//! This is where the error definitions can go +//! TODO: Implement proper error handling, this would include defining the error enum, +//! impl std::error::Error using `thiserror` and ensuring all errors are handled appropriately +#[derive(Debug, thiserror::Error)] +pub enum FMLError { + #[error("IO error: {0}")] + IOError(#[from] std::io::Error), + #[error("JSON Error: {0}")] + JSONError(#[from] serde_json::Error), + #[error("Invalid path: {0}")] + InvalidPath(String), + #[error("Internal error: {0}")] + InternalError(&'static str), +} + +pub type Result = std::result::Result; diff --git a/components/support/nimbus-fml/src/fixtures.rs b/components/support/nimbus-fml/src/fixtures.rs new file mode 100644 index 0000000000..da54a8807b --- /dev/null +++ b/components/support/nimbus-fml/src/fixtures.rs @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub(crate) mod ir; diff --git a/components/support/nimbus-fml/src/fixtures/ir.rs b/components/support/nimbus-fml/src/fixtures/ir.rs new file mode 100644 index 0000000000..6c3d7afad5 --- /dev/null +++ b/components/support/nimbus-fml/src/fixtures/ir.rs @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::ir::{EnumDef, FeatureDef, FeatureManifest, PropDef, TypeRef, VariantDef}; +use serde_json::json; + +pub(crate) fn get_simple_nimbus_validation_feature() -> FeatureManifest { + FeatureManifest { + enum_defs: vec![], + obj_defs: vec![], + feature_defs: vec![FeatureDef::new( + "nimbus-validation", + "A simple validation feature", + vec![ + PropDef { + name: "enabled".into(), + doc: "An example boolean property".into(), + typ: TypeRef::Boolean, + default: json!(true), + }, + PropDef { + name: "row-count".into(), + doc: "An example integer property".into(), + typ: TypeRef::Boolean, + default: json!(2), + }, + PropDef { + name: "deeplink".into(), + doc: "An example string property".into(), + typ: TypeRef::String, + default: json!("deeplink://settings"), + }, + ], + None, + )], + } +} + +pub(crate) fn get_simple_homescreen_feature() -> FeatureManifest { + FeatureManifest { + enum_defs: vec![EnumDef { + name: "SectionId".into(), + doc: "The sections of the homescreen".into(), + variants: vec![ + VariantDef::new("top-sites", "The original frecency sorted sites"), + VariantDef::new("jump-back-in", "Jump back in section"), + VariantDef::new("recently-saved", "Tabs that have been bookmarked recently"), + ], + }], + obj_defs: vec![], + feature_defs: vec![FeatureDef::new( + "homescreen", + "Represents the homescreen feature", + vec![PropDef { + name: "sections-enabled".into(), + doc: "A map of booleans".into(), + typ: TypeRef::EnumMap( + Box::new(TypeRef::Enum("SectionId".into())), + Box::new(TypeRef::String), + ), + default: json!({ + "top-sites": true, + "jump-back-in": false, + "recently-saved": false, + }), + }], + None, + )], + } +} diff --git a/components/support/nimbus-fml/src/ir.rs b/components/support/nimbus-fml/src/ir.rs new file mode 100644 index 0000000000..82f4707ef4 --- /dev/null +++ b/components/support/nimbus-fml/src/ir.rs @@ -0,0 +1,138 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +/// The `TypeRef` enum defines a reference to a type. +/// +/// Other types will be defined in terms of these enum values. +/// +/// They represent the types available via the current `Variables` API— +/// some primitives and structural types— and can be represented by +/// Kotlin, Swift and JSON Schema. +/// +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub(crate) enum TypeRef { + // Current primitives. + String, + Int, + Boolean, + + // Strings can be coerced into a few types. + // The types here will require the app's bundle or context to look up the final value. + // They will likely have + BundleText(String), + BundleImage(String), + + Enum(String), + // JSON objects can represent a data class. + Object(String), + + // JSON objects can also represent a `Map` or a `Map` with + // keys that can be derived from a string. + StringMap(Box), + // We can coerce the String keys into Enums, so this repesents that. + EnumMap(Box, Box), + + List(Box), + Option(Box), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct FeatureManifest { + pub enum_defs: Vec, + pub obj_defs: Vec, + pub feature_defs: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct FeatureDef { + name: String, + doc: String, + props: Vec, + default: Option, +} +impl FeatureDef { + pub fn new(name: &str, doc: &str, props: Vec, default: Option) -> Self { + Self { + name: name.into(), + doc: doc.into(), + props, + default, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct EnumDef { + pub name: String, + pub doc: String, + pub variants: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct FromStringDef { + pub name: String, + pub doc: String, + pub variants: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct VariantDef { + name: String, + doc: String, +} +impl VariantDef { + pub fn new(name: &str, doc: &str) -> Self { + Self { + name: name.into(), + doc: doc.into(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct ObjectDef { + name: String, + doc: String, + props: Vec, +} +impl ObjectDef { + pub fn new(name: &str, doc: &str, props: Vec) -> Self { + Self { + name: name.into(), + doc: doc.into(), + props, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct PropDef { + pub name: String, + pub doc: String, + pub typ: TypeRef, + pub default: Literal, +} + +type Literal = Value; + +#[cfg(test)] +mod unit_tests { + use super::*; + use crate::error::Result; + use crate::fixtures::ir; + + #[test] + fn can_ir_represent_smoke_test() -> Result<()> { + let m1 = ir::get_simple_homescreen_feature(); + let string = serde_json::to_string(&m1)?; + let m2: FeatureManifest = serde_json::from_str(&string)?; + + assert_eq!(m1, m2); + + Ok(()) + } +} diff --git a/components/support/nimbus-fml/src/lib.rs b/components/support/nimbus-fml/src/lib.rs index e9d454721a..84fe99dbe2 100644 --- a/components/support/nimbus-fml/src/lib.rs +++ b/components/support/nimbus-fml/src/lib.rs @@ -2,4 +2,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +pub mod error; +pub mod ir; pub mod parser; + +#[cfg(test)] +#[allow(dead_code)] +pub mod fixtures; From 8da1958464d610a9c54c1412bb72562f47c5f595 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Fri, 22 Oct 2021 18:00:59 +0100 Subject: [PATCH 2/6] Rename ir to intermediate_representation and add hints for strings --- components/support/nimbus-fml/src/fixtures.rs | 2 +- .../support/nimbus-fml/src/fixtures/ir.rs | 71 --------- components/support/nimbus-fml/src/ir.rs | 138 ------------------ components/support/nimbus-fml/src/lib.rs | 2 +- 4 files changed, 2 insertions(+), 211 deletions(-) delete mode 100644 components/support/nimbus-fml/src/fixtures/ir.rs delete mode 100644 components/support/nimbus-fml/src/ir.rs diff --git a/components/support/nimbus-fml/src/fixtures.rs b/components/support/nimbus-fml/src/fixtures.rs index da54a8807b..0e5a5fec85 100644 --- a/components/support/nimbus-fml/src/fixtures.rs +++ b/components/support/nimbus-fml/src/fixtures.rs @@ -2,4 +2,4 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -pub(crate) mod ir; +pub(crate) mod intermediate_representation; diff --git a/components/support/nimbus-fml/src/fixtures/ir.rs b/components/support/nimbus-fml/src/fixtures/ir.rs deleted file mode 100644 index 6c3d7afad5..0000000000 --- a/components/support/nimbus-fml/src/fixtures/ir.rs +++ /dev/null @@ -1,71 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public -* License, v. 2.0. If a copy of the MPL was not distributed with this -* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -use crate::ir::{EnumDef, FeatureDef, FeatureManifest, PropDef, TypeRef, VariantDef}; -use serde_json::json; - -pub(crate) fn get_simple_nimbus_validation_feature() -> FeatureManifest { - FeatureManifest { - enum_defs: vec![], - obj_defs: vec![], - feature_defs: vec![FeatureDef::new( - "nimbus-validation", - "A simple validation feature", - vec![ - PropDef { - name: "enabled".into(), - doc: "An example boolean property".into(), - typ: TypeRef::Boolean, - default: json!(true), - }, - PropDef { - name: "row-count".into(), - doc: "An example integer property".into(), - typ: TypeRef::Boolean, - default: json!(2), - }, - PropDef { - name: "deeplink".into(), - doc: "An example string property".into(), - typ: TypeRef::String, - default: json!("deeplink://settings"), - }, - ], - None, - )], - } -} - -pub(crate) fn get_simple_homescreen_feature() -> FeatureManifest { - FeatureManifest { - enum_defs: vec![EnumDef { - name: "SectionId".into(), - doc: "The sections of the homescreen".into(), - variants: vec![ - VariantDef::new("top-sites", "The original frecency sorted sites"), - VariantDef::new("jump-back-in", "Jump back in section"), - VariantDef::new("recently-saved", "Tabs that have been bookmarked recently"), - ], - }], - obj_defs: vec![], - feature_defs: vec![FeatureDef::new( - "homescreen", - "Represents the homescreen feature", - vec![PropDef { - name: "sections-enabled".into(), - doc: "A map of booleans".into(), - typ: TypeRef::EnumMap( - Box::new(TypeRef::Enum("SectionId".into())), - Box::new(TypeRef::String), - ), - default: json!({ - "top-sites": true, - "jump-back-in": false, - "recently-saved": false, - }), - }], - None, - )], - } -} diff --git a/components/support/nimbus-fml/src/ir.rs b/components/support/nimbus-fml/src/ir.rs deleted file mode 100644 index 82f4707ef4..0000000000 --- a/components/support/nimbus-fml/src/ir.rs +++ /dev/null @@ -1,138 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public -* License, v. 2.0. If a copy of the MPL was not distributed with this -* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -/// The `TypeRef` enum defines a reference to a type. -/// -/// Other types will be defined in terms of these enum values. -/// -/// They represent the types available via the current `Variables` API— -/// some primitives and structural types— and can be represented by -/// Kotlin, Swift and JSON Schema. -/// -#[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub(crate) enum TypeRef { - // Current primitives. - String, - Int, - Boolean, - - // Strings can be coerced into a few types. - // The types here will require the app's bundle or context to look up the final value. - // They will likely have - BundleText(String), - BundleImage(String), - - Enum(String), - // JSON objects can represent a data class. - Object(String), - - // JSON objects can also represent a `Map` or a `Map` with - // keys that can be derived from a string. - StringMap(Box), - // We can coerce the String keys into Enums, so this repesents that. - EnumMap(Box, Box), - - List(Box), - Option(Box), -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub(crate) struct FeatureManifest { - pub enum_defs: Vec, - pub obj_defs: Vec, - pub feature_defs: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub(crate) struct FeatureDef { - name: String, - doc: String, - props: Vec, - default: Option, -} -impl FeatureDef { - pub fn new(name: &str, doc: &str, props: Vec, default: Option) -> Self { - Self { - name: name.into(), - doc: doc.into(), - props, - default, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub(crate) struct EnumDef { - pub name: String, - pub doc: String, - pub variants: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub(crate) struct FromStringDef { - pub name: String, - pub doc: String, - pub variants: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct VariantDef { - name: String, - doc: String, -} -impl VariantDef { - pub fn new(name: &str, doc: &str) -> Self { - Self { - name: name.into(), - doc: doc.into(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub(crate) struct ObjectDef { - name: String, - doc: String, - props: Vec, -} -impl ObjectDef { - pub fn new(name: &str, doc: &str, props: Vec) -> Self { - Self { - name: name.into(), - doc: doc.into(), - props, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub(crate) struct PropDef { - pub name: String, - pub doc: String, - pub typ: TypeRef, - pub default: Literal, -} - -type Literal = Value; - -#[cfg(test)] -mod unit_tests { - use super::*; - use crate::error::Result; - use crate::fixtures::ir; - - #[test] - fn can_ir_represent_smoke_test() -> Result<()> { - let m1 = ir::get_simple_homescreen_feature(); - let string = serde_json::to_string(&m1)?; - let m2: FeatureManifest = serde_json::from_str(&string)?; - - assert_eq!(m1, m2); - - Ok(()) - } -} diff --git a/components/support/nimbus-fml/src/lib.rs b/components/support/nimbus-fml/src/lib.rs index 84fe99dbe2..df15c62e2e 100644 --- a/components/support/nimbus-fml/src/lib.rs +++ b/components/support/nimbus-fml/src/lib.rs @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ pub mod error; -pub mod ir; +pub mod intermediate_representation; pub mod parser; #[cfg(test)] From 2e2084f105091514bc4e0552c7881716e123cef2 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Fri, 22 Oct 2021 19:46:18 +0100 Subject: [PATCH 3/6] Add fixtures --- .../fixtures/intermediate_representation.rs | 73 +++++++++ .../src/intermediate_representation.rs | 145 ++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 components/support/nimbus-fml/src/fixtures/intermediate_representation.rs create mode 100644 components/support/nimbus-fml/src/intermediate_representation.rs diff --git a/components/support/nimbus-fml/src/fixtures/intermediate_representation.rs b/components/support/nimbus-fml/src/fixtures/intermediate_representation.rs new file mode 100644 index 0000000000..ce1f9a496a --- /dev/null +++ b/components/support/nimbus-fml/src/fixtures/intermediate_representation.rs @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::intermediate_representation::{EnumDef, FeatureDef, FeatureManifest, PropDef, TypeRef, VariantDef}; +use serde_json::json; + +pub(crate) fn get_simple_nimbus_validation_feature() -> FeatureManifest { + FeatureManifest { + enum_defs: Default::default(), + obj_defs: Default::default(), + hints: Default::default(), + feature_defs: vec![FeatureDef::new( + "nimbus-validation", + "A simple validation feature", + vec![ + PropDef { + name: "enabled".into(), + doc: "An example boolean property".into(), + typ: TypeRef::Boolean, + default: json!(true), + }, + PropDef { + name: "row-count".into(), + doc: "An example integer property".into(), + typ: TypeRef::Boolean, + default: json!(2), + }, + PropDef { + name: "deeplink".into(), + doc: "An example string property".into(), + typ: TypeRef::String, + default: json!("deeplink://settings"), + }, + ], + None, + )], + } +} + +pub(crate) fn get_simple_homescreen_feature() -> FeatureManifest { + FeatureManifest { + enum_defs: vec![EnumDef { + name: "SectionId".into(), + doc: "The sections of the homescreen".into(), + variants: vec![ + VariantDef::new("top-sites", "The original frecency sorted sites"), + VariantDef::new("jump-back-in", "Jump back in section"), + VariantDef::new("recently-saved", "Tabs that have been bookmarked recently"), + ], + }], + obj_defs: Default::default(), + hints: Default::default(), + feature_defs: vec![FeatureDef::new( + "homescreen", + "Represents the homescreen feature", + vec![PropDef { + name: "sections-enabled".into(), + doc: "A map of booleans".into(), + typ: TypeRef::EnumMap( + Box::new(TypeRef::Enum("SectionId".into())), + Box::new(TypeRef::String), + ), + default: json!({ + "top-sites": true, + "jump-back-in": false, + "recently-saved": false, + }), + }], + None, + )], + } +} diff --git a/components/support/nimbus-fml/src/intermediate_representation.rs b/components/support/nimbus-fml/src/intermediate_representation.rs new file mode 100644 index 0000000000..663c77f892 --- /dev/null +++ b/components/support/nimbus-fml/src/intermediate_representation.rs @@ -0,0 +1,145 @@ +use std::collections::HashMap; + +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +/// The `TypeRef` enum defines a reference to a type. +/// +/// Other types will be defined in terms of these enum values. +/// +/// They represent the types available via the current `Variables` API— +/// some primitives and structural types— and can be represented by +/// Kotlin, Swift and JSON Schema. +/// +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub(crate) enum TypeRef { + // Current primitives. + String, + Int, + Boolean, + + // Strings can be coerced into a few types. + // The types here will require the app's bundle or context to look up the final value. + // They will likely have + BundleText(StringId), + BundleImage(StringId), + + Enum(String), + // JSON objects can represent a data class. + Object(String), + + // JSON objects can also represent a `Map` or a `Map` with + // keys that can be derived from a string. + StringMap(Box), + // We can coerce the String keys into Enums, so this repesents that. + EnumMap(Box, Box), + + List(Box), + Option(Box), +} + +pub(crate) type StringId = String; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct FeatureManifest { + pub enum_defs: Vec, + pub obj_defs: Vec, + // `hints` are useful for things that will be constructed from strings + // such as images and display text. + pub hints: HashMap, + pub feature_defs: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct FeatureDef { + name: String, + doc: String, + props: Vec, + default: Option, +} +impl FeatureDef { + pub fn new(name: &str, doc: &str, props: Vec, default: Option) -> Self { + Self { + name: name.into(), + doc: doc.into(), + props, + default, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct EnumDef { + pub name: String, + pub doc: String, + pub variants: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct FromStringDef { + pub name: String, + pub doc: String, + pub variants: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct VariantDef { + name: String, + doc: String, +} +impl VariantDef { + pub fn new(name: &str, doc: &str) -> Self { + Self { + name: name.into(), + doc: doc.into(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct ObjectDef { + name: String, + doc: String, + props: Vec, +} +impl ObjectDef { + pub fn new(name: &str, doc: &str, props: Vec) -> Self { + Self { + name: name.into(), + doc: doc.into(), + props, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub(crate) struct PropDef { + pub name: String, + pub doc: String, + pub typ: TypeRef, + pub default: Literal, +} + +type Literal = Value; + +#[cfg(test)] +mod unit_tests { + use super::*; + use crate::error::Result; + use crate::fixtures::intermediate_representation; + + #[test] + fn can_ir_represent_smoke_test() -> Result<()> { + let m1 = intermediate_representation::get_simple_homescreen_feature(); + let string = serde_json::to_string(&m1)?; + let m2: FeatureManifest = serde_json::from_str(&string)?; + + assert_eq!(m1, m2); + + Ok(()) + } +} From 11cbf403ce96f9289a51d66049252e30b64dbcfd Mon Sep 17 00:00:00 2001 From: James Hugman Date: Fri, 22 Oct 2021 21:23:13 +0100 Subject: [PATCH 4/6] Add commands to load and save IR --- components/support/nimbus-fml/Cargo.toml | 2 +- .../fixtures/simple_homescreen.json | 50 ++++++++++++++ .../fixtures/simple_nimbus_validation.json | 32 +++++++++ components/support/nimbus-fml/src/cli.yaml | 37 ++++++++++ .../fixtures/intermediate_representation.rs | 37 +++++++++- .../src/intermediate_representation.rs | 17 +++-- components/support/nimbus-fml/src/main.rs | 69 +++++++++++++------ 7 files changed, 213 insertions(+), 31 deletions(-) create mode 100644 components/support/nimbus-fml/fixtures/simple_homescreen.json create mode 100644 components/support/nimbus-fml/fixtures/simple_nimbus_validation.json create mode 100644 components/support/nimbus-fml/src/cli.yaml diff --git a/components/support/nimbus-fml/Cargo.toml b/components/support/nimbus-fml/Cargo.toml index 1bd50c4c87..3c648bd0c3 100644 --- a/components/support/nimbus-fml/Cargo.toml +++ b/components/support/nimbus-fml/Cargo.toml @@ -7,7 +7,7 @@ license = "MPL-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = "2.33" +clap = {version = "2.33.0", features = ["yaml"]} anyhow = "1.0.44" serde_json = "1" serde = { version = "1.0", features = ["derive"] } diff --git a/components/support/nimbus-fml/fixtures/simple_homescreen.json b/components/support/nimbus-fml/fixtures/simple_homescreen.json new file mode 100644 index 0000000000..dbef796efc --- /dev/null +++ b/components/support/nimbus-fml/fixtures/simple_homescreen.json @@ -0,0 +1,50 @@ +{ + "enum_defs": [ + { + "name": "SectionId", + "doc": "The sections of the homescreen", + "variants": [ + { + "name": "top-sites", + "doc": "The original frecency sorted sites" + }, + { + "name": "jump-back-in", + "doc": "Jump back in section" + }, + { + "name": "recently-saved", + "doc": "Tabs that have been bookmarked recently" + } + ] + } + ], + "obj_defs": [], + "hints": {}, + "feature_defs": [ + { + "name": "homescreen", + "doc": "Represents the homescreen feature", + "props": [ + { + "name": "sections-enabled", + "doc": "A map of booleans", + "typ": { + "EnumMap": [ + { + "Enum": "SectionId" + }, + "String" + ] + }, + "default": { + "jump-back-in": false, + "recently-saved": false, + "top-sites": true + } + } + ], + "default": null + } + ] +} \ No newline at end of file diff --git a/components/support/nimbus-fml/fixtures/simple_nimbus_validation.json b/components/support/nimbus-fml/fixtures/simple_nimbus_validation.json new file mode 100644 index 0000000000..2caf20f559 --- /dev/null +++ b/components/support/nimbus-fml/fixtures/simple_nimbus_validation.json @@ -0,0 +1,32 @@ +{ + "enum_defs": [], + "obj_defs": [], + "hints": {}, + "feature_defs": [ + { + "name": "nimbus-validation", + "doc": "A simple validation feature", + "props": [ + { + "name": "enabled", + "doc": "An example boolean property", + "typ": "Boolean", + "default": true + }, + { + "name": "row-count", + "doc": "An example integer property", + "typ": "Boolean", + "default": 2 + }, + { + "name": "deeplink", + "doc": "An example string property", + "typ": "String", + "default": "deeplink://settings" + } + ], + "default": null + } + ] +} \ No newline at end of file diff --git a/components/support/nimbus-fml/src/cli.yaml b/components/support/nimbus-fml/src/cli.yaml new file mode 100644 index 0000000000..951737cea3 --- /dev/null +++ b/components/support/nimbus-fml/src/cli.yaml @@ -0,0 +1,37 @@ +name: nimbus-fml +version: "1.0" +author: nimbus-dev@mozilla.com +about: Tool for working with Nimbus Feature Manifests +args: + - config: + short: c + long: config + value_name: FILE + help: Sets a custom config file + takes_value: true + - verbose: + short: v + multiple: true + help: Sets the level of verbosity +subcommands: + - struct: + about: Generate the app code for configuring features + args: + - language: + short: l + long: language + value_name: LANGUAGE + possible_values: [ kotlin, swift, ir ] + - INPUT: + help: Sets the input file to use + required: true + index: 1 + - ir: + help: The input file is intermediate representation. Useful for debugging FML. + long: ir + - output: + help: The output file + short: o + long: output + value_name: FILE + required: true diff --git a/components/support/nimbus-fml/src/fixtures/intermediate_representation.rs b/components/support/nimbus-fml/src/fixtures/intermediate_representation.rs index ce1f9a496a..2031e8293c 100644 --- a/components/support/nimbus-fml/src/fixtures/intermediate_representation.rs +++ b/components/support/nimbus-fml/src/fixtures/intermediate_representation.rs @@ -2,7 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use crate::intermediate_representation::{EnumDef, FeatureDef, FeatureManifest, PropDef, TypeRef, VariantDef}; +use crate::intermediate_representation::{ + EnumDef, FeatureDef, FeatureManifest, PropDef, TypeRef, VariantDef, +}; use serde_json::json; pub(crate) fn get_simple_nimbus_validation_feature() -> FeatureManifest { @@ -71,3 +73,36 @@ pub(crate) fn get_simple_homescreen_feature() -> FeatureManifest { )], } } + +#[cfg(test)] +mod dump_to_file { + use std::path::PathBuf; + + use crate::error::Result; + + use super::*; + + fn write(fm: &FeatureManifest, nm: &str) -> Result<()> { + let root = std::env::var("CARGO_MANIFEST_DIR") + .expect("Missing $CARGO_MANIFEST_DIR, cannot write fixtures files"); + let fixtures_dir = "fixtures"; + let path: PathBuf = [&root, fixtures_dir, nm].iter().collect(); + + let contents = serde_json::to_string_pretty(fm)?; + + std::fs::write(path, contents)?; + + Ok(()) + } + + #[test] + fn write_to_fixtures_dir() -> Result<()> { + write(&get_simple_homescreen_feature(), "simple_homescreen.json")?; + write( + &get_simple_nimbus_validation_feature(), + "simple_nimbus_validation.json", + )?; + + Ok(()) + } +} diff --git a/components/support/nimbus-fml/src/intermediate_representation.rs b/components/support/nimbus-fml/src/intermediate_representation.rs index 663c77f892..9bf3b31aa0 100644 --- a/components/support/nimbus-fml/src/intermediate_representation.rs +++ b/components/support/nimbus-fml/src/intermediate_representation.rs @@ -16,7 +16,7 @@ use serde_json::Value; /// #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] -pub(crate) enum TypeRef { +pub enum TypeRef { // Current primitives. String, Int, @@ -45,7 +45,7 @@ pub(crate) enum TypeRef { pub(crate) type StringId = String; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub(crate) struct FeatureManifest { +pub struct FeatureManifest { pub enum_defs: Vec, pub obj_defs: Vec, // `hints` are useful for things that will be constructed from strings @@ -55,13 +55,14 @@ pub(crate) struct FeatureManifest { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub(crate) struct FeatureDef { +pub struct FeatureDef { name: String, doc: String, props: Vec, default: Option, } impl FeatureDef { + #[allow(dead_code)] pub fn new(name: &str, doc: &str, props: Vec, default: Option) -> Self { Self { name: name.into(), @@ -73,14 +74,14 @@ impl FeatureDef { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub(crate) struct EnumDef { +pub struct EnumDef { pub name: String, pub doc: String, pub variants: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub(crate) struct FromStringDef { +pub struct FromStringDef { pub name: String, pub doc: String, pub variants: Vec, @@ -92,6 +93,7 @@ pub struct VariantDef { doc: String, } impl VariantDef { + #[allow(dead_code)] pub fn new(name: &str, doc: &str) -> Self { Self { name: name.into(), @@ -101,12 +103,13 @@ impl VariantDef { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub(crate) struct ObjectDef { +pub struct ObjectDef { name: String, doc: String, props: Vec, } impl ObjectDef { + #[allow(dead_code)] pub fn new(name: &str, doc: &str, props: Vec) -> Self { Self { name: name.into(), @@ -117,7 +120,7 @@ impl ObjectDef { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub(crate) struct PropDef { +pub struct PropDef { pub name: String, pub doc: String, pub typ: TypeRef, diff --git a/components/support/nimbus-fml/src/main.rs b/components/support/nimbus-fml/src/main.rs index c8162c162c..dd67f809ce 100644 --- a/components/support/nimbus-fml/src/main.rs +++ b/components/support/nimbus-fml/src/main.rs @@ -2,28 +2,53 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use clap::{App, Arg}; +#[macro_use] +extern crate clap; +use clap::{App, ArgMatches}; +use nimbus_fml::error::{FMLError, Result}; +use nimbus_fml::intermediate_representation::FeatureManifest; use nimbus_fml::parser::Parser; -use std::fs::File; -fn main() -> anyhow::Result<()> { - let matches = App::new("Nimbus Feature Manifest") - .version("0.1.0") - .author("Nimbus SDK Engineering") - .about("A tool to generate code using an experiment feature manifest") - .arg( - Arg::with_name("manifest") - .short("m") - .long("manifest") - .value_name("FILE") - .help("Sets the manifest file to use") - .required(true) - .takes_value(true), - ) - .get_matches(); - let manifest_file_path = matches - .value_of("manifest") - .expect("Manifest path is required, but not found"); - let file = File::open(manifest_file_path)?; - let _parser = Parser::new(file); +use std::{fs::File, path::PathBuf}; + +fn main() -> Result<()> { + let yaml = load_yaml!("cli.yaml"); + let matches = App::from_yaml(yaml).get_matches(); + let cwd = std::env::current_dir()?; + if let Some(cmd) = matches.subcommand_matches("struct") { + let manifest_file_path = file_path("INPUT", &cmd, &cwd)?; + + let ir = if !cmd.is_present("ir") { + let file = File::open(manifest_file_path)?; + let _parser: Parser = Parser::new(file); + unimplemented!("No parser is available") + } else { + let string = slurp_file(&manifest_file_path)?; + serde_json::from_str::(&string)? + }; + + let output_path = file_path("output", cmd, &cwd)?; + match cmd.value_of("language") { + Some("ir") => { + let contents = serde_json::to_string_pretty(&ir)?; + std::fs::write(output_path, contents)?; + } + _ => unimplemented!("Language not implemented yet"), + }; + } Ok(()) } + +fn file_path(name: &str, args: &ArgMatches, cwd: &PathBuf) -> Result { + let mut abs = cwd.clone(); + match args.value_of(name) { + Some(suffix) => { + abs.push(suffix); + Ok(abs) + } + _ => Err(FMLError::InvalidPath(name.into())), + } +} + +fn slurp_file(file_name: &PathBuf) -> Result { + Ok(std::fs::read_to_string(file_name)?) +} From 18845bd083ad4498fc56ebd0a7e7e24822f8dc06 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Fri, 22 Oct 2021 22:42:13 +0100 Subject: [PATCH 5/6] cargo clippy --- components/support/nimbus-fml/src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/components/support/nimbus-fml/src/main.rs b/components/support/nimbus-fml/src/main.rs index dd67f809ce..0d55c2d550 100644 --- a/components/support/nimbus-fml/src/main.rs +++ b/components/support/nimbus-fml/src/main.rs @@ -8,6 +8,7 @@ use clap::{App, ArgMatches}; use nimbus_fml::error::{FMLError, Result}; use nimbus_fml::intermediate_representation::FeatureManifest; use nimbus_fml::parser::Parser; +use std::path::Path; use std::{fs::File, path::PathBuf}; fn main() -> Result<()> { @@ -15,7 +16,7 @@ fn main() -> Result<()> { let matches = App::from_yaml(yaml).get_matches(); let cwd = std::env::current_dir()?; if let Some(cmd) = matches.subcommand_matches("struct") { - let manifest_file_path = file_path("INPUT", &cmd, &cwd)?; + let manifest_file_path = file_path("INPUT", cmd, &cwd)?; let ir = if !cmd.is_present("ir") { let file = File::open(manifest_file_path)?; @@ -38,8 +39,8 @@ fn main() -> Result<()> { Ok(()) } -fn file_path(name: &str, args: &ArgMatches, cwd: &PathBuf) -> Result { - let mut abs = cwd.clone(); +fn file_path(name: &str, args: &ArgMatches, cwd: &Path) -> Result { + let mut abs = cwd.to_path_buf(); match args.value_of(name) { Some(suffix) => { abs.push(suffix); @@ -49,6 +50,6 @@ fn file_path(name: &str, args: &ArgMatches, cwd: &PathBuf) -> Result { } } -fn slurp_file(file_name: &PathBuf) -> Result { +fn slurp_file(file_name: &Path) -> Result { Ok(std::fs::read_to_string(file_name)?) } From f3b575dde4caf7eb4134e590949435802b09be40 Mon Sep 17 00:00:00 2001 From: James Hugman Date: Fri, 22 Oct 2021 23:56:15 +0100 Subject: [PATCH 6/6] Cargo.lock --- Cargo.lock | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7eaae3e383..c2dbf1a2cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,9 +61,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" +checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" [[package]] name = "arrayref" @@ -388,6 +388,7 @@ dependencies = [ "textwrap", "unicode-width", "vec_map", + "yaml-rust", ] [[package]] @@ -1797,6 +1798,9 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "serde", + "serde_json", + "thiserror", ] [[package]] @@ -3295,18 +3299,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -3853,6 +3857,12 @@ dependencies = [ "log", ] +[[package]] +name = "yaml-rust" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992" + [[package]] name = "zeitstempel" version = "0.1.1"