Browse Source

refactor(core): split allowlist configuration per module (#1263)

* refactor(core): split allowlist configuration per module

* fix: build with all features

* fix(cli): run fmt

* fix(core): run fmt
Lucas Fernandes Nogueira 4 years ago
parent
commit
e0be59ea26

+ 5 - 0
.changes/refactor-allowlist.md

@@ -0,0 +1,5 @@
+---
+"tauri": minor
+---
+
+The `allowlist` configuration now has one object per module.

+ 1 - 1
.github/CONTRIBUTING.md

@@ -78,7 +78,7 @@ The code for the bundler is located in `[Tauri repo root]/cli/tauri-bundler`. Bu
 
 ### Developing Tauri Core
 
-The code for Tauri core is located in `[Tauri repo root]/tauri`. The easiest way to test your changes is to use the `[Tauri repo root]/tauri/examples/communication` app. It automatically rebuilds and uses your local codebase. Just run `yarn tauri build` or `yarn tauri dev` in the communication app directory after making changes to test them out. To use your local changes in another project, edit its `src-tauri/Cargo.toml` file so that the `tauri` key looks like `tauri = { path = "PATH", features = [ "all-api", "cli" ] }`, where `PATH` is the relative path to `[Tauri repo root]/tauri`.
+The code for Tauri core is located in `[Tauri repo root]/tauri`. The easiest way to test your changes is to use the `[Tauri repo root]/tauri/examples/communication` app. It automatically rebuilds and uses your local codebase. Just run `yarn tauri build` or `yarn tauri dev` in the communication app directory after making changes to test them out. To use your local changes in another project, edit its `src-tauri/Cargo.toml` file so that the `tauri` key looks like `tauri = { path = "PATH", features = [ "api-all", "cli" ] }`, where `PATH` is the relative path to `[Tauri repo root]/tauri`.
 
 ## Financial Contribution
 

+ 1 - 1
.github/workflows/core-lint-fmt.yml

@@ -62,7 +62,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        feature: [embedded-server, all-api]
+        feature: [embedded-server, api-all]
 
     steps:
       - uses: actions/checkout@v2

+ 0 - 7
cli/core/Cargo.lock

@@ -329,12 +329,6 @@ version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
 
-[[package]]
-name = "convert_case"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
-
 [[package]]
 name = "core-foundation"
 version = "0.9.1"
@@ -1999,7 +1993,6 @@ dependencies = [
  "anyhow",
  "clap 3.0.0-beta.2",
  "colored",
- "convert_case",
  "json-patch",
  "notify",
  "once_cell",

+ 0 - 1
cli/core/Cargo.toml

@@ -21,7 +21,6 @@ serde_json = "1.0"
 notify = "4.0"
 shared_child = "0.3"
 toml_edit = "0.2"
-convert_case = "0.4"
 json-patch = "0.2"
 schemars = "0.8"
 valico = "3.5"

+ 234 - 1
cli/core/config_definition.rs

@@ -250,6 +250,232 @@ pub struct SecurityConfig {
   csp: Option<String>,
 }
 
+trait Allowlist {
+  fn to_features(&self) -> Vec<&str>;
+}
+
+macro_rules! check_feature {
+  ($self:ident, $features:ident, $flag:ident, $feature_name: expr) => {
+    if $self.$flag {
+      $features.push($feature_name)
+    }
+  };
+}
+
+#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+struct FsAllowlistConfig {
+  #[serde(default)]
+  all: bool,
+  #[serde(default)]
+  read_text_file: bool,
+  #[serde(default)]
+  read_binary_file: bool,
+  #[serde(default)]
+  write_file: bool,
+  #[serde(default)]
+  write_binary_file: bool,
+  #[serde(default)]
+  read_dir: bool,
+  #[serde(default)]
+  copy_file: bool,
+  #[serde(default)]
+  create_dir: bool,
+  #[serde(default)]
+  remove_dir: bool,
+  #[serde(default)]
+  remove_file: bool,
+  #[serde(default)]
+  rename_file: bool,
+  #[serde(default)]
+  path: bool,
+}
+
+impl Allowlist for FsAllowlistConfig {
+  fn to_features(&self) -> Vec<&str> {
+    if self.all {
+      vec!["fs-all"]
+    } else {
+      let mut features = Vec::new();
+      check_feature!(self, features, read_text_file, "fs-read-text-file");
+      check_feature!(self, features, read_binary_file, "fs-read-binary-file");
+      check_feature!(self, features, write_file, "fs-write-file");
+      check_feature!(self, features, write_binary_file, "fs-write-binary-file");
+      check_feature!(self, features, read_dir, "fs-read-dir");
+      check_feature!(self, features, copy_file, "fs-copy-file");
+      check_feature!(self, features, create_dir, "fs-create-dir");
+      check_feature!(self, features, remove_dir, "fs-remove-dir");
+      check_feature!(self, features, remove_file, "fs-remove-file");
+      check_feature!(self, features, rename_file, "fs-rename-file");
+      check_feature!(self, features, path, "fs-path");
+      features
+    }
+  }
+}
+
+#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+struct WindowAllowlistConfig {
+  #[serde(default)]
+  all: bool,
+  #[serde(default)]
+  create: bool,
+}
+
+impl Allowlist for WindowAllowlistConfig {
+  fn to_features(&self) -> Vec<&str> {
+    if self.all {
+      vec!["window-all"]
+    } else {
+      let mut features = Vec::new();
+      check_feature!(self, features, create, "window-create");
+      features
+    }
+  }
+}
+
+#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+struct ShellAllowlistConfig {
+  #[serde(default)]
+  all: bool,
+  #[serde(default)]
+  execute: bool,
+  #[serde(default)]
+  open: bool,
+}
+
+impl Allowlist for ShellAllowlistConfig {
+  fn to_features(&self) -> Vec<&str> {
+    if self.all {
+      vec!["shell-all"]
+    } else {
+      let mut features = Vec::new();
+      check_feature!(self, features, execute, "shell-execute");
+      check_feature!(self, features, open, "shell-open");
+      features
+    }
+  }
+}
+
+#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+struct DialogAllowlistConfig {
+  #[serde(default)]
+  all: bool,
+  #[serde(default)]
+  open: bool,
+  #[serde(default)]
+  save: bool,
+}
+
+impl Allowlist for DialogAllowlistConfig {
+  fn to_features(&self) -> Vec<&str> {
+    if self.all {
+      vec!["dialog-all"]
+    } else {
+      let mut features = Vec::new();
+      check_feature!(self, features, open, "dialog-open");
+      check_feature!(self, features, save, "dialog-save");
+      features
+    }
+  }
+}
+
+#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+struct HttpAllowlistConfig {
+  #[serde(default)]
+  all: bool,
+  #[serde(default)]
+  request: bool,
+}
+
+impl Allowlist for HttpAllowlistConfig {
+  fn to_features(&self) -> Vec<&str> {
+    if self.all {
+      vec!["http-all"]
+    } else {
+      let mut features = Vec::new();
+      check_feature!(self, features, request, "http-request");
+      features
+    }
+  }
+}
+
+#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+struct NotificationAllowlistConfig {
+  #[serde(default)]
+  all: bool,
+}
+
+impl Allowlist for NotificationAllowlistConfig {
+  fn to_features(&self) -> Vec<&str> {
+    if self.all {
+      vec!["notification-all"]
+    } else {
+      vec![]
+    }
+  }
+}
+
+#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+struct GlobalShortcutAllowlistConfig {
+  #[serde(default)]
+  all: bool,
+}
+
+impl Allowlist for GlobalShortcutAllowlistConfig {
+  fn to_features(&self) -> Vec<&str> {
+    if self.all {
+      vec!["global-shortcut-all"]
+    } else {
+      vec![]
+    }
+  }
+}
+
+#[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+struct AllowlistConfig {
+  #[serde(default)]
+  all: bool,
+  #[serde(default)]
+  fs: FsAllowlistConfig,
+  #[serde(default)]
+  window: WindowAllowlistConfig,
+  #[serde(default)]
+  shell: ShellAllowlistConfig,
+  #[serde(default)]
+  dialog: DialogAllowlistConfig,
+  #[serde(default)]
+  http: HttpAllowlistConfig,
+  #[serde(default)]
+  notification: NotificationAllowlistConfig,
+  #[serde(default)]
+  global_shortcut: GlobalShortcutAllowlistConfig,
+}
+
+impl Allowlist for AllowlistConfig {
+  fn to_features(&self) -> Vec<&str> {
+    if self.all {
+      vec!["api-all"]
+    } else {
+      let mut features = Vec::new();
+      features.extend(self.fs.to_features());
+      features.extend(self.window.to_features());
+      features.extend(self.shell.to_features());
+      features.extend(self.dialog.to_features());
+      features.extend(self.http.to_features());
+      features.extend(self.notification.to_features());
+      features.extend(self.global_shortcut.to_features());
+      features
+    }
+  }
+}
+
 /// The Tauri configuration object.
 #[derive(Debug, Default, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]
@@ -265,10 +491,17 @@ pub struct TauriConfig {
   #[serde(default)]
   pub bundle: BundleConfig,
   #[serde(default)]
-  pub allowlist: HashMap<String, bool>,
+  allowlist: AllowlistConfig,
   pub security: Option<SecurityConfig>,
 }
 
+impl TauriConfig {
+  #[allow(dead_code)]
+  pub fn features(&self) -> Vec<&str> {
+    self.allowlist.to_features()
+  }
+}
+
 /// The Build configuration object.
 #[derive(Debug, PartialEq, Clone, Deserialize, Serialize, JsonSchema)]
 #[serde(rename_all = "camelCase", deny_unknown_fields)]

+ 322 - 6
cli/core/schema.json

@@ -31,7 +31,47 @@
     "tauri": {
       "description": "The Tauri configuration.",
       "default": {
-        "allowlist": {},
+        "allowlist": {
+          "all": false,
+          "dialog": {
+            "all": false,
+            "open": false,
+            "save": false
+          },
+          "fs": {
+            "all": false,
+            "copyFile": false,
+            "createDir": false,
+            "path": false,
+            "readBinaryFile": false,
+            "readDir": false,
+            "readTextFile": false,
+            "removeDir": false,
+            "removeFile": false,
+            "renameFile": false,
+            "writeBinaryFile": false,
+            "writeFile": false
+          },
+          "globalShortcut": {
+            "all": false
+          },
+          "http": {
+            "all": false,
+            "request": false
+          },
+          "notification": {
+            "all": false
+          },
+          "shell": {
+            "all": false,
+            "execute": false,
+            "open": false
+          },
+          "window": {
+            "all": false,
+            "create": false
+          }
+        },
         "bundle": {
           "active": false,
           "category": null,
@@ -76,6 +116,103 @@
   },
   "additionalProperties": false,
   "definitions": {
+    "AllowlistConfig": {
+      "type": "object",
+      "properties": {
+        "all": {
+          "default": false,
+          "type": "boolean"
+        },
+        "dialog": {
+          "default": {
+            "all": false,
+            "open": false,
+            "save": false
+          },
+          "allOf": [
+            {
+              "$ref": "#/definitions/DialogAllowlistConfig"
+            }
+          ]
+        },
+        "fs": {
+          "default": {
+            "all": false,
+            "copyFile": false,
+            "createDir": false,
+            "path": false,
+            "readBinaryFile": false,
+            "readDir": false,
+            "readTextFile": false,
+            "removeDir": false,
+            "removeFile": false,
+            "renameFile": false,
+            "writeBinaryFile": false,
+            "writeFile": false
+          },
+          "allOf": [
+            {
+              "$ref": "#/definitions/FsAllowlistConfig"
+            }
+          ]
+        },
+        "globalShortcut": {
+          "default": {
+            "all": false
+          },
+          "allOf": [
+            {
+              "$ref": "#/definitions/GlobalShortcutAllowlistConfig"
+            }
+          ]
+        },
+        "http": {
+          "default": {
+            "all": false,
+            "request": false
+          },
+          "allOf": [
+            {
+              "$ref": "#/definitions/HttpAllowlistConfig"
+            }
+          ]
+        },
+        "notification": {
+          "default": {
+            "all": false
+          },
+          "allOf": [
+            {
+              "$ref": "#/definitions/NotificationAllowlistConfig"
+            }
+          ]
+        },
+        "shell": {
+          "default": {
+            "all": false,
+            "execute": false,
+            "open": false
+          },
+          "allOf": [
+            {
+              "$ref": "#/definitions/ShellAllowlistConfig"
+            }
+          ]
+        },
+        "window": {
+          "default": {
+            "all": false,
+            "create": false
+          },
+          "allOf": [
+            {
+              "$ref": "#/definitions/WindowAllowlistConfig"
+            }
+          ]
+        }
+      },
+      "additionalProperties": false
+    },
     "BuildConfig": {
       "description": "The Build configuration object.",
       "type": "object",
@@ -524,6 +661,24 @@
       },
       "additionalProperties": false
     },
+    "DialogAllowlistConfig": {
+      "type": "object",
+      "properties": {
+        "all": {
+          "default": false,
+          "type": "boolean"
+        },
+        "open": {
+          "default": false,
+          "type": "boolean"
+        },
+        "save": {
+          "default": false,
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false
+    },
     "EmbeddedServerConfig": {
       "description": "The embeddedServer configuration object.",
       "type": "object",
@@ -556,6 +711,94 @@
       },
       "additionalProperties": false
     },
+    "FsAllowlistConfig": {
+      "type": "object",
+      "properties": {
+        "all": {
+          "default": false,
+          "type": "boolean"
+        },
+        "copyFile": {
+          "default": false,
+          "type": "boolean"
+        },
+        "createDir": {
+          "default": false,
+          "type": "boolean"
+        },
+        "path": {
+          "default": false,
+          "type": "boolean"
+        },
+        "readBinaryFile": {
+          "default": false,
+          "type": "boolean"
+        },
+        "readDir": {
+          "default": false,
+          "type": "boolean"
+        },
+        "readTextFile": {
+          "default": false,
+          "type": "boolean"
+        },
+        "removeDir": {
+          "default": false,
+          "type": "boolean"
+        },
+        "removeFile": {
+          "default": false,
+          "type": "boolean"
+        },
+        "renameFile": {
+          "default": false,
+          "type": "boolean"
+        },
+        "writeBinaryFile": {
+          "default": false,
+          "type": "boolean"
+        },
+        "writeFile": {
+          "default": false,
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false
+    },
+    "GlobalShortcutAllowlistConfig": {
+      "type": "object",
+      "properties": {
+        "all": {
+          "default": false,
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false
+    },
+    "HttpAllowlistConfig": {
+      "type": "object",
+      "properties": {
+        "all": {
+          "default": false,
+          "type": "boolean"
+        },
+        "request": {
+          "default": false,
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false
+    },
+    "NotificationAllowlistConfig": {
+      "type": "object",
+      "properties": {
+        "all": {
+          "default": false,
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false
+    },
     "OsxConfig": {
       "type": "object",
       "properties": {
@@ -619,16 +862,75 @@
       },
       "additionalProperties": false
     },
+    "ShellAllowlistConfig": {
+      "type": "object",
+      "properties": {
+        "all": {
+          "default": false,
+          "type": "boolean"
+        },
+        "execute": {
+          "default": false,
+          "type": "boolean"
+        },
+        "open": {
+          "default": false,
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false
+    },
     "TauriConfig": {
       "description": "The Tauri configuration object.",
       "type": "object",
       "properties": {
         "allowlist": {
-          "default": {},
-          "type": "object",
-          "additionalProperties": {
-            "type": "boolean"
-          }
+          "default": {
+            "all": false,
+            "dialog": {
+              "all": false,
+              "open": false,
+              "save": false
+            },
+            "fs": {
+              "all": false,
+              "copyFile": false,
+              "createDir": false,
+              "path": false,
+              "readBinaryFile": false,
+              "readDir": false,
+              "readTextFile": false,
+              "removeDir": false,
+              "removeFile": false,
+              "renameFile": false,
+              "writeBinaryFile": false,
+              "writeFile": false
+            },
+            "globalShortcut": {
+              "all": false
+            },
+            "http": {
+              "all": false,
+              "request": false
+            },
+            "notification": {
+              "all": false
+            },
+            "shell": {
+              "all": false,
+              "execute": false,
+              "open": false
+            },
+            "window": {
+              "all": false,
+              "create": false
+            }
+          },
+          "allOf": [
+            {
+              "$ref": "#/definitions/AllowlistConfig"
+            }
+          ]
         },
         "bundle": {
           "description": "The bundler configuration.",
@@ -708,6 +1010,20 @@
       },
       "additionalProperties": false
     },
+    "WindowAllowlistConfig": {
+      "type": "object",
+      "properties": {
+        "all": {
+          "default": false,
+          "type": "boolean"
+        },
+        "create": {
+          "default": false,
+          "type": "boolean"
+        }
+      },
+      "additionalProperties": false
+    },
     "WindowConfig": {
       "description": "The window configuration object.",
       "type": "object",

+ 5 - 14
cli/core/src/helpers/manifest.rs

@@ -1,6 +1,5 @@
 use super::{app_paths::tauri_dir, config::ConfigHandle};
 
-use convert_case::{Case, Casing};
 use toml_edit::{Array, Document, Value};
 
 use std::{
@@ -27,21 +26,13 @@ pub fn rewrite_manifest(config: ConfigHandle) -> crate::Result<()> {
   let config = config_guard.as_ref().unwrap();
 
   if let Some(tauri) = tauri {
-    let mut features: Array = Default::default();
-
-    let allowlist = &config.tauri.allowlist;
-    if *allowlist.get("all").unwrap_or(&false) {
-      features.push("all-api".to_string()).unwrap();
-    } else {
-      for (feature, enabled) in allowlist.iter() {
-        if *enabled {
-          features.push(feature.to_case(Case::Kebab)).unwrap();
-        }
-      }
+    let allowlist_features = config.tauri.features();
+    let mut features = Array::default();
+    for feature in allowlist_features {
+      features.push(feature).unwrap();
     }
-
     if config.tauri.cli.is_some() {
-      features.push("cli".to_string()).unwrap();
+      features.push("cli").unwrap();
     }
 
     match tauri {

+ 1 - 1
cli/tauri.js/test/jest/fixtures/app/src-tauri/Cargo.toml

@@ -24,7 +24,7 @@ icon = [
 serde_json = "1.0.62"
 serde = "1.0"
 serde_derive = "1.0"
-tauri = { path = "../../../../../../../tauri", features =["all-api"]}
+tauri = { path = "../../../../../../../tauri", features =["api-all"]}
 
 [features]
 embedded-server = [ "tauri/embedded-server" ]

+ 26 - 23
tauri/Cargo.toml

@@ -15,7 +15,7 @@ edition = "2018"
 exclude = [ "test/fixture/**" ]
 
 [package.metadata.docs.rs]
-features = [ "all-api" ]
+features = [ "api-all" ]
 
 [dependencies]
 serde_json = "1.0"
@@ -44,49 +44,52 @@ cfg_aliases = "0.1.1"
 [dev-dependencies]
 proptest = "0.10.1"
 serde_json = "1.0"
-tauri = { path = ".", features = [ "all-api" ] }
+tauri = { path = ".", features = [ "api-all" ] }
 serde = { version = "1.0", features = [ "derive" ] }
 
 [features]
 cli = [ "tauri-api/cli" ]
 embedded-server = [ "tiny_http" ]
-all-api = [ "tauri-api/notification", "tauri-api/global-shortcut" ]
+api-all = [ "tauri-api/notification", "tauri-api/global-shortcut" ]
 updater = [ ]
 
 # FS
-read-text-file = [ ]
-read-binary-file = [ ]
-write-file = [ ]
-write-binary-file = [ ]
-read-dir = [ ]
-copy-file = [ ]
-create-dir = [ ]
-remove-dir = [ ]
-remove-file = [ ]
-rename-file = [ ]
-path-api = [ ]
-event = [ ]
+fs-all = [ ]
+fs-read-text-file = [ ]
+fs-read-binary-file = [ ]
+fs-write-file = [ ]
+fs-write-binary-file = [ ]
+fs-read-dir = [ ]
+fs-copy-file = [ ]
+fs-create-dir = [ ]
+fs-remove-dir = [ ]
+fs-remove-file = [ ]
+fs-rename-file = [ ]
+fs-path-api = [ ]
 
 # window
-window = [ ]
-create-window = [ ]
+window-all = [ ]
+window-create = [ ]
 
 #shell
-execute = [ ]
-open = [ ]
+shell-all = [ ]
+shell-execute = [ ]
+shell-open = [ ]
 
 # dialog
-open-dialog = [ ]
-save-dialog = [ ]
+dialog-all = [ ]
+dialog-open = [ ]
+dialog-save = [ ]
 
 # HTTP
+http-all = [ ]
 http-request = [ ]
 
 # notification
-notification = [ "tauri-api/notification" ]
+notification-all = [ "tauri-api/notification" ]
 
 # global shortcut
-global-shortcut = [ "tauri-api/global-shortcut" ]
+global-shortcut-all = [ "tauri-api/global-shortcut" ]
 
 [[example]]
 name = "communication"

+ 25 - 23
tauri/build.rs

@@ -5,45 +5,47 @@ fn main() {
     embedded_server: { feature = "embedded-server" },
     dev: { not(feature = "embedded-server") },
 
-    all_api: { feature = "all-api" },
+    api_all: { feature = "api-all" },
 
     // fs
-    read_text_file: { any(all_api, feature = "read-text-file") },
-    read_binary_file: { any(all_api, feature = "read-binary-file") },
-    write_file: { any(all_api, feature = "write-file") },
-    write_binary_file: { any(all_api, feature = "write-binary-file") },
-    read_dir: { any(all_api, feature = "read-dir") },
-    copy_file: { any(all_api, feature = "copy-file") },
-    create_dir: { any(all_api, feature = "create_dir") },
-    remove_dir: { any(all_api, feature = "remove-dir") },
-    remove_file: { any(all_api, feature = "remove-file") },
-    rename_file: { any(all_api, feature = "rename-file") },
-
-    // js path api
-    path_api: { any(all_api, feature = "path-api") },
+    fs_all: { any(api_all, feature = "fs-all") },
+    fs_read_text_file: { any(fs_all, feature = "fs-read-text-file") },
+    fs_read_binary_file: { any(fs_all, feature = "fs-read-binary-file") },
+    fs_write_file: { any(fs_all, feature = "fs-write-file") },
+    fs_write_binary_file: { any(fs_all, feature = "fs-write-binary-file") },
+    fs_read_dir: { any(fs_all, feature = "fs-read-dir") },
+    fs_copy_file: { any(fs_all, feature = "fs-copy-file") },
+    fs_create_dir: { any(fs_all, feature = "fs-create_dir") },
+    fs_remove_dir: { any(fs_all, feature = "fs-remove-dir") },
+    fs_remove_file: { any(fs_all, feature = "fs-remove-file") },
+    fs_rename_file: { any(fs_all, feature = "fs-rename-file") },
+    fs_path: { any(fs_all, feature = "fs-path") },
 
     // window
-    window: { any(all_api, feature = "window") },
-    create_window: { any(all_api, feature = "create-window") },
+    window_all: { any(api_all, feature = "window-all") },
+    window_create: { any(window_all, feature = "window-create") },
 
     // shell
-    open: { any(all_api, feature = "open") },
-    execute: { any(all_api, feature = "execute") },
+    shell_all: { any(api_all, feature = "shell-all") },
+    shell_open: { any(shell_all, feature = "shell-open") },
+    shell_execute: { any(shell_all, feature = "shell-execute") },
 
     // dialog
-    open_dialog: { any(all_api, feature = "open-dialog") },
-    save_dialog: { any(all_api, feature = "save-dialog") },
+    dialog_all: { any(api_all, feature = "dialog-all") },
+    dialog_open: { any(dialog_all, feature = "dialog-open") },
+    dialog_save: { any(dialog_all, feature = "dialog-save") },
 
     // http
-    http_request: { any(all_api, feature = "http-request") },
+    http_all: { any(api_all, feature = "http-all") },
+    http_request: { any(http_all, feature = "http-request") },
 
     // cli
     cli: { feature = "cli" },
 
     // notification
-    notification: { any(all_api, feature = "notification") },
+    notification_all: { any(api_all, feature = "notification-all") },
 
     // global shortcut
-    global_shortcut: { any(all_api, feature = "global_shortcut" )},
+    global_shortcut_all: { any(api_all, feature = "global_shortcut-all") },
   }
 }

+ 1 - 1
tauri/examples/api/src-tauri/Cargo.toml

@@ -14,7 +14,7 @@ build = "src/build.rs"
 [dependencies]
 serde_json = "1.0"
 serde = { version = "1.0", features = [ "derive" ] }
-tauri = { path = "../../..", features =["all-api", "cli"]}
+tauri = { path = "../../..", features =["api-all", "cli"]}
 
 [target."cfg(windows)".build-dependencies]
 winres = "0.1"

+ 1 - 1
tauri/examples/communication/src-tauri/Cargo.toml

@@ -24,7 +24,7 @@ icon = [
 [dependencies]
 serde_json = "1.0"
 serde = { version = "1.0", features = [ "derive" ] }
-tauri = { path = "../../..", features =["all-api", "cli"]}
+tauri = { path = "../../..", features =["api-all", "cli"]}
 
 [target."cfg(windows)".build-dependencies]
 winres = "0.1"

+ 1 - 1
tauri/examples/multiwindow/src-tauri/Cargo.toml

@@ -22,7 +22,7 @@ icon = [
 ]
 
 [dependencies]
-tauri = { path = "../../..", features =["all-api"]}
+tauri = { path = "../../..", features =["api-all"]}
 
 [target."cfg(windows)".build-dependencies]
 winres = "0.1"

+ 0 - 3
tauri/src/endpoints.rs

@@ -3,12 +3,9 @@ mod dialog;
 mod event;
 #[allow(unused_imports)]
 mod file_system;
-#[cfg(global_shortcut)]
 mod global_shortcut;
-#[cfg(http_request)]
 mod http;
 mod internal;
-#[cfg(notification)]
 mod notification;
 mod shell;
 mod window;

+ 11 - 9
tauri/src/endpoints/dialog.rs

@@ -1,5 +1,7 @@
+#[cfg(any(dialog_open, dialog_save))]
+use crate::api::dialog::FileDialogBuilder;
 use crate::{
-  api::dialog::{ask as ask_dialog, message as message_dialog, AskResponse, FileDialogBuilder},
+  api::dialog::{ask as ask_dialog, message as message_dialog, AskResponse},
   app::InvokeResponse,
 };
 use serde::Deserialize;
@@ -66,16 +68,16 @@ impl Cmd {
   pub async fn run(self) -> crate::Result<InvokeResponse> {
     match self {
       Self::OpenDialog { options } => {
-        #[cfg(open_dialog)]
+        #[cfg(dialog_open)]
         return open(options);
-        #[cfg(not(open_dialog))]
-        Err(crate::Error::ApiNotAllowlisted("title".to_string()));
+        #[cfg(not(dialog_open))]
+        return Err(crate::Error::ApiNotAllowlisted("dialog > open".to_string()));
       }
       Self::SaveDialog { options } => {
-        #[cfg(save_dialog)]
+        #[cfg(dialog_save)]
         return save(options);
-        #[cfg(not(save_dialog))]
-        Err(crate::Error::ApiNotAllowlisted("saveDialog".to_string()));
+        #[cfg(not(dialog_save))]
+        return Err(crate::Error::ApiNotAllowlisted("dialog > save".to_string()));
       }
       Self::MessageDialog { message } => {
         let exe = std::env::current_exe()?;
@@ -106,7 +108,7 @@ impl Cmd {
 }
 
 /// Shows an open dialog.
-#[cfg(open_dialog)]
+#[cfg(dialog_open)]
 pub fn open(options: OpenDialogOptions) -> crate::Result<InvokeResponse> {
   let mut dialog_builder = FileDialogBuilder::new();
   if let Some(default_path) = options.default_path {
@@ -127,7 +129,7 @@ pub fn open(options: OpenDialogOptions) -> crate::Result<InvokeResponse> {
 }
 
 /// Shows a save dialog.
-#[cfg(save_dialog)]
+#[cfg(dialog_save)]
 pub fn save(options: SaveDialogOptions) -> crate::Result<InvokeResponse> {
   let mut dialog_builder = FileDialogBuilder::new();
   if let Some(default_path) = options.default_path {

+ 54 - 41
tauri/src/endpoints/file_system.rs

@@ -94,15 +94,17 @@ impl Cmd {
   pub async fn run(self) -> crate::Result<InvokeResponse> {
     match self {
       Self::ReadTextFile { path, options } => {
-        #[cfg(read_text_file)]
+        #[cfg(fs_read_text_file)]
         return read_text_file(path, options).await.map(Into::into);
-        #[cfg(not(read_text_file))]
-        Err(crate::Error::ApiNotAllowlisted("readTextFile".to_string()))
+        #[cfg(not(fs_read_text_file))]
+        Err(crate::Error::ApiNotAllowlisted(
+          "fs > readTextFile".to_string(),
+        ))
       }
       Self::ReadBinaryFile { path, options } => {
-        #[cfg(read_binary_file)]
+        #[cfg(fs_read_binary_file)]
         return read_binary_file(path, options).await.map(Into::into);
-        #[cfg(not(read_binary_file))]
+        #[cfg(not(fs_read_binary_file))]
         Err(crate::Error::ApiNotAllowlisted(
           "readBinaryFile".to_string(),
         ))
@@ -112,85 +114,95 @@ impl Cmd {
         contents,
         options,
       } => {
-        #[cfg(write_file)]
+        #[cfg(fs_write_file)]
         return write_file(path, contents, options).await.map(Into::into);
-        #[cfg(not(write_file))]
-        Err(crate::Error::ApiNotAllowlisted("writeFile".to_string()))
+        #[cfg(not(fs_write_file))]
+        Err(crate::Error::ApiNotAllowlisted(
+          "fs > writeFile".to_string(),
+        ))
       }
       Self::WriteBinaryFile {
         path,
         contents,
         options,
       } => {
-        #[cfg(write_binary_file)]
+        #[cfg(fs_write_binary_file)]
         return write_binary_file(path, contents, options)
           .await
           .map(Into::into);
-        #[cfg(not(write_binary_file))]
+        #[cfg(not(fs_write_binary_file))]
         Err(crate::Error::ApiNotAllowlisted(
           "writeBinaryFile".to_string(),
         ))
       }
       Self::ReadDir { path, options } => {
-        #[cfg(read_dir)]
+        #[cfg(fs_read_dir)]
         return read_dir(path, options).await.map(Into::into);
-        #[cfg(not(read_dir))]
-        Err(crate::Error::ApiNotAllowlisted("readDir".to_string()))
+        #[cfg(not(fs_read_dir))]
+        Err(crate::Error::ApiNotAllowlisted("fs > readDir".to_string()))
       }
       Self::CopyFile {
         source,
         destination,
         options,
       } => {
-        #[cfg(copy_file)]
+        #[cfg(fs_copy_file)]
         return copy_file(source, destination, options)
           .await
           .map(Into::into);
-        #[cfg(not(copy_file))]
-        Err(crate::Error::ApiNotAllowlisted("copyFile".to_string()))
+        #[cfg(not(fs_copy_file))]
+        Err(crate::Error::ApiNotAllowlisted("fs > copyFile".to_string()))
       }
       Self::CreateDir { path, options } => {
-        #[cfg(create_dir)]
+        #[cfg(fs_create_dir)]
         return create_dir(path, options).await.map(Into::into);
-        #[cfg(not(create_dir))]
-        Err(crate::Error::ApiNotAllowlisted("createDir".to_string()))
+        #[cfg(not(fs_create_dir))]
+        Err(crate::Error::ApiNotAllowlisted(
+          "fs > createDir".to_string(),
+        ))
       }
       Self::RemoveDir { path, options } => {
-        #[cfg(remove_dir)]
+        #[cfg(fs_remove_dir)]
         return remove_dir(path, options).await.map(Into::into);
-        #[cfg(not(remove_dir))]
-        Err(crate::Error::ApiNotAllowlisted("removeDir".to_string()))
+        #[cfg(not(fs_remove_dir))]
+        Err(crate::Error::ApiNotAllowlisted(
+          "fs > removeDir".to_string(),
+        ))
       }
       Self::RemoveFile { path, options } => {
-        #[cfg(remove_file)]
+        #[cfg(fs_remove_file)]
         return remove_file(path, options).await.map(Into::into);
-        #[cfg(not(remove_file))]
-        Err(crate::Error::ApiNotAllowlisted("removeFile".to_string()))
+        #[cfg(not(fs_remove_file))]
+        Err(crate::Error::ApiNotAllowlisted(
+          "fs > removeFile".to_string(),
+        ))
       }
       Self::RenameFile {
         old_path,
         new_path,
         options,
       } => {
-        #[cfg(rename_file)]
+        #[cfg(fs_rename_file)]
         return rename_file(old_path, new_path, options)
           .await
           .map(Into::into);
-        #[cfg(not(rename_file))]
-        Err(crate::Error::ApiNotAllowlisted("renameFile".to_string()))
+        #[cfg(not(fs_rename_file))]
+        Err(crate::Error::ApiNotAllowlisted(
+          "fs > renameFile".to_string(),
+        ))
       }
       Self::ResolvePath { path, directory } => {
-        #[cfg(path_api)]
+        #[cfg(fs_path)]
         return resolve_path_handler(path, directory).await.map(Into::into);
-        #[cfg(not(path_api))]
-        Err(crate::Error::ApiNotAllowlisted("pathApi".to_string()))
+        #[cfg(not(fs_path))]
+        Err(crate::Error::ApiNotAllowlisted("fs > pathApi".to_string()))
       }
     }
   }
 }
 
 /// Reads a directory.
-#[cfg(read_dir)]
+#[cfg(fs_read_dir)]
 pub async fn read_dir(
   path: PathBuf,
   options: Option<DirOperationOptions>,
@@ -204,7 +216,7 @@ pub async fn read_dir(
 }
 
 /// Copies a file.
-#[cfg(copy_file)]
+#[cfg(fs_copy_file)]
 pub async fn copy_file(
   source: PathBuf,
   destination: PathBuf,
@@ -222,7 +234,7 @@ pub async fn copy_file(
 }
 
 /// Creates a directory.
-#[cfg(create_dir)]
+#[cfg(fs_create_dir)]
 pub async fn create_dir(path: PathBuf, options: Option<DirOperationOptions>) -> crate::Result<()> {
   let (recursive, dir) = if let Some(options_value) = options {
     (options_value.recursive, options_value.dir)
@@ -240,7 +252,7 @@ pub async fn create_dir(path: PathBuf, options: Option<DirOperationOptions>) ->
 }
 
 /// Removes a directory.
-#[cfg(remove_dir)]
+#[cfg(fs_remove_dir)]
 pub async fn remove_dir(path: PathBuf, options: Option<DirOperationOptions>) -> crate::Result<()> {
   let (recursive, dir) = if let Some(options_value) = options {
     (options_value.recursive, options_value.dir)
@@ -258,7 +270,7 @@ pub async fn remove_dir(path: PathBuf, options: Option<DirOperationOptions>) ->
 }
 
 /// Removes a file
-#[cfg(remove_file)]
+#[cfg(fs_remove_file)]
 pub async fn remove_file(
   path: PathBuf,
   options: Option<FileOperationOptions>,
@@ -269,7 +281,7 @@ pub async fn remove_file(
 }
 
 /// Renames a file.
-#[cfg(rename_file)]
+#[cfg(fs_rename_file)]
 pub async fn rename_file(
   old_path: PathBuf,
   new_path: PathBuf,
@@ -286,7 +298,7 @@ pub async fn rename_file(
 }
 
 /// Writes a text file.
-#[cfg(write_file)]
+#[cfg(fs_write_file)]
 pub async fn write_file(
   path: PathBuf,
   contents: String,
@@ -299,7 +311,7 @@ pub async fn write_file(
 }
 
 /// Writes a binary file.
-#[cfg(write_binary_file)]
+#[cfg(fs_write_binary_file)]
 pub async fn write_binary_file(
   path: PathBuf,
   contents: String,
@@ -316,7 +328,7 @@ pub async fn write_binary_file(
 }
 
 /// Reads a text file.
-#[cfg(read_text_file)]
+#[cfg(fs_read_text_file)]
 pub async fn read_text_file(
   path: PathBuf,
   options: Option<FileOperationOptions>,
@@ -326,7 +338,7 @@ pub async fn read_text_file(
 }
 
 /// Reads a binary file.
-#[cfg(read_binary_file)]
+#[cfg(fs_read_binary_file)]
 pub async fn read_binary_file(
   path: PathBuf,
   options: Option<FileOperationOptions>,
@@ -335,6 +347,7 @@ pub async fn read_binary_file(
     .map_err(crate::Error::FailedToExecuteApi)
 }
 
+#[cfg(fs_path)]
 pub async fn resolve_path_handler(
   path: String,
   directory: Option<BaseDirectory>,

+ 8 - 4
tauri/src/endpoints/global_shortcut.rs

@@ -1,5 +1,6 @@
+#[cfg(global_shortcut_all)]
+use crate::api::shortcuts::ShortcutManager;
 use crate::{
-  api::shortcuts::ShortcutManager,
   app::{InvokeResponse, WebviewDispatcher},
   async_runtime::Mutex,
 };
@@ -8,8 +9,10 @@ use serde::Deserialize;
 
 use std::sync::Arc;
 
+#[cfg(global_shortcut_all)]
 type ShortcutManagerHandle = Arc<Mutex<ShortcutManager>>;
 
+#[cfg(global_shortcut_all)]
 pub fn manager_handle() -> &'static ShortcutManagerHandle {
   static MANAGER: Lazy<ShortcutManagerHandle> = Lazy::new(Default::default);
   &MANAGER
@@ -34,6 +37,7 @@ pub enum Cmd {
   IsRegistered { shortcut: String },
 }
 
+#[cfg(global_shortcut_all)]
 fn register_shortcut<A: crate::ApplicationDispatcherExt + 'static>(
   dispatcher: WebviewDispatcher<A>,
   manager: &mut ShortcutManager,
@@ -55,11 +59,11 @@ impl Cmd {
     self,
     webview_manager: &crate::WebviewManager<A>,
   ) -> crate::Result<InvokeResponse> {
-    #[cfg(not(global_shortcut))]
+    #[cfg(not(global_shortcut_all))]
     return Err(crate::Error::ApiNotAllowlisted(
-      "globalShortcut".to_string(),
+      "globalShortcut > all".to_string(),
     ));
-    #[cfg(global_shortcut)]
+    #[cfg(global_shortcut_all)]
     match self {
       Self::Register { shortcut, handler } => {
         let dispatcher = webview_manager.current_webview().await?.clone();

+ 4 - 1
tauri/src/endpoints/http.rs

@@ -48,13 +48,16 @@ impl Cmd {
         #[cfg(http_request)]
         return make_request(client, *options).await.map(Into::into);
         #[cfg(not(http_request))]
-        Err(crate::Error::ApiNotAllowlisted("httpRequest".to_string()))
+        Err(crate::Error::ApiNotAllowlisted(
+          "http > request".to_string(),
+        ))
       }
     }
   }
 }
 
 /// Makes an HTTP request and resolves the response to the webview
+#[cfg(http_request)]
 pub async fn make_request(
   client_id: ClientId,
   options: HttpRequestBuilder,

+ 10 - 6
tauri/src/endpoints/notification.rs

@@ -1,5 +1,6 @@
 use crate::app::InvokeResponse;
 use serde::Deserialize;
+#[cfg(notification_all)]
 use tauri_api::{config::Config, notification::Notification};
 
 /// The options for the notification API.
@@ -29,27 +30,28 @@ impl Cmd {
   pub async fn run(self, context: &crate::app::Context) -> crate::Result<InvokeResponse> {
     match self {
       Self::Notification { options } => {
-        #[cfg(notification)]
+        #[cfg(notification_all)]
         return send(options, &context.config).await.map(Into::into);
-        #[cfg(not(notification))]
+        #[cfg(not(notification_all))]
         Err(crate::Error::ApiNotAllowlisted("notification".to_string()))
       }
       Self::IsNotificationPermissionGranted => {
-        #[cfg(notification)]
+        #[cfg(notification_all)]
         return is_permission_granted().await.map(Into::into);
-        #[cfg(not(notification))]
+        #[cfg(not(notification_all))]
         Err(crate::Error::ApiNotAllowlisted("notification".to_string()))
       }
       Self::RequestNotificationPermission => {
-        #[cfg(notification)]
+        #[cfg(notification_all)]
         return request_permission().map(Into::into);
-        #[cfg(not(notification))]
+        #[cfg(not(notification_all))]
         Err(crate::Error::ApiNotAllowlisted("notification".to_string()))
       }
     }
   }
 }
 
+#[cfg(notification_all)]
 pub async fn send(options: NotificationOptions, config: &Config) -> crate::Result<InvokeResponse> {
   let identifier = config.tauri.bundle.identifier.clone();
   let mut notification = Notification::new(identifier).title(options.title);
@@ -63,6 +65,7 @@ pub async fn send(options: NotificationOptions, config: &Config) -> crate::Resul
   Ok(().into())
 }
 
+#[cfg(notification_all)]
 pub async fn is_permission_granted() -> crate::Result<InvokeResponse> {
   let settings = crate::settings::read_settings()?;
   if let Some(allow_notification) = settings.allow_notification {
@@ -72,6 +75,7 @@ pub async fn is_permission_granted() -> crate::Result<InvokeResponse> {
   }
 }
 
+#[cfg(notification_all)]
 pub fn request_permission() -> crate::Result<String> {
   let mut settings = crate::settings::read_settings()?;
   let granted = "granted".to_string();

+ 10 - 8
tauri/src/endpoints/shell.rs

@@ -18,28 +18,30 @@ impl Cmd {
         command: _,
         args: _,
       } => {
-        #[cfg(execute)]
+        #[cfg(shell_execute)]
         {
           //TODO
           Ok(().into())
         }
-        #[cfg(not(execute))]
-        Err(crate::Error::ApiNotAllowlisted("execute".to_string()))
+        #[cfg(not(shell_execute))]
+        Err(crate::Error::ApiNotAllowlisted(
+          "shell > execute".to_string(),
+        ))
       }
       Self::Open { uri } => {
-        #[cfg(open)]
+        #[cfg(shell_open)]
         {
           open_browser(uri);
           Ok(().into())
         }
-        #[cfg(not(open))]
-        Err(crate::Error::ApiNotAllowlisted("open".to_string()))
+        #[cfg(not(shell_open))]
+        Err(crate::Error::ApiNotAllowlisted("shell > open".to_string()))
       }
     }
   }
 }
 
-#[cfg(open)]
+#[cfg(shell_open)]
 pub fn open_browser(uri: String) {
   #[cfg(test)]
   assert!(uri.contains("http://"));
@@ -53,7 +55,7 @@ mod test {
   use proptest::prelude::*;
   // Test the open func to see if proper uris can be opened by the browser.
   proptest! {
-    #[cfg(open)]
+    #[cfg(shell_open)]
     #[test]
     fn check_open(uri in r"(http://)([\\w\\d\\.]+([\\w]{2,6})?)") {
       super::open_browser(uri);

+ 8 - 6
tauri/src/endpoints/window.rs

@@ -84,7 +84,7 @@ pub enum Cmd {
   },
 }
 
-#[cfg(create_window)]
+#[cfg(window_create)]
 #[derive(Clone, serde::Serialize)]
 struct WindowCreatedEvent {
   label: String,
@@ -95,15 +95,17 @@ impl Cmd {
     self,
     webview_manager: &crate::WebviewManager<A>,
   ) -> crate::Result<InvokeResponse> {
-    if cfg!(not(window)) {
-      Err(crate::Error::ApiNotAllowlisted("setTitle".to_string()))
+    if cfg!(not(window_all)) {
+      Err(crate::Error::ApiNotAllowlisted("window > all".to_string()))
     } else {
       let current_webview = webview_manager.current_webview().await?;
       match self {
         Self::CreateWebview { options } => {
-          #[cfg(not(create_window))]
-          return Err(crate::Error::ApiNotAllowlisted("createWindow".to_string()));
-          #[cfg(create_window)]
+          #[cfg(not(window_create))]
+          return Err(crate::Error::ApiNotAllowlisted(
+            "window > create".to_string(),
+          ));
+          #[cfg(window_create)]
           {
             let label = options.label.to_string();
             webview_manager

+ 1 - 1
tauri/src/settings.rs

@@ -13,7 +13,7 @@ use tauri_api::{
 #[derive(Default, Deserialize, Serialize)]
 pub struct Settings {
   /// Whether the user allows notifications or not.
-  #[cfg(notification)]
+  #[cfg(notification_all)]
   pub allow_notification: Option<bool>,
 }