Repository: tauri-apps/tauri
File: core/tauri-utils/src/acl/capability.rs (Rust)

Code before:
    let capability_file = std::fs::read_to_string(path).map_err(super::Error::ReadFile)?;
    let ext = path.extension().unwrap().to_string_lossy().to_string();
    let file: Self = match ext.as_str() {
      "toml" => toml::from_str(&capability_file)?,
      "json" => serde_json::from_str(&capability_file)?,
      _ => return Err(super::Error::UnknownCapabilityFormat(ext)),
    };
    Ok(file)
  }
}

#[derive(Deserialize, Debug)]
struct CapabilityNamedList {
  capabilities: Vec<Capability>,
}

impl<'de> Deserialize<'de> for CapabilityFile {
  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  where
    D: Deserializer<'de>,
  {
    UntaggedEnumVisitor::new()
      .seq(|seq| seq.deserialize::<Vec<Capability>>().map(Self::List))
      .map(|map| {
        if let Some(len) = map.size_hint() {
          if len == 1 {
            let named_list = map.deserialize::<CapabilityNamedList>()?;
            return Ok(Self::NamedList {
              capabilities: named_list.capabilities,
            });
          }
        }
        map.deserialize::<Capability>().map(Self::Capability)
      })
      .deserialize(deserializer)
  }
}

impl FromStr for CapabilityFile {
  type Err = super::Error;

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    serde_json::from_str(s)
      .or_else(|_| toml::from_str(s))
      .map_err(Into::into)
  }
}

#[cfg(feature = "build")]
mod build {
  use std::convert::identity;

Review comment (bug):
instead of checking the length, we could just try to deserialize as `CapabilityNamedList` and if it fails, we fallback to deserializing as `Capability`? I know this is less performant but should cover the case where `map.size_hint()` is None.

Code after:
  ///
  /// ## Example
  ///
  /// `["main"]`
  #[serde(default, skip_serializing_if = "Vec::is_empty")]
  pub windows: Vec<String>,
  /// List of webviews that are affected by this capability. Can be a glob pattern.
  ///
  /// This is only required when using on multiwebview contexts, by default
  /// all child webviews of a window that matches [`Self::windows`] are linked.
  ///
  /// ## Example
  ///
  /// `["sub-webview-one", "sub-webview-two"]`
  #[serde(default, skip_serializing_if = "Vec::is_empty")]
  pub webviews: Vec<String>,
  /// List of permissions attached to this capability.
  ///
  /// Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.
  /// For commands directly implemented in the application itself only `${permission-name}`
  /// is required.
  ///
  /// ## Example
  ///
  /// ```json
  /// [
  ///  "path:default",
  ///  "event:default",
  ///  "window:default",
  ///  "app:default",
  ///  "image:default",
  ///  "resources:default",
  ///  "menu:default",
  ///  "tray:default",
  ///  "shell:allow-open",
  ///  "dialog:open",
  ///  {
  ///    "identifier": "fs:allow-write-text-file",
  ///    "allow": [{ "path": "$HOME/test.txt" }]
  ///  }
  /// ```
  pub permissions: Vec<PermissionEntry>,
  /// Limit which target platforms this capability applies to.
  ///
  /// By default all platforms are targeted.
  ///
  /// ## Example
  ///
  /// `["macOS","windows"]`
  #[serde(skip_serializing_if = "Option::is_none")]
  pub platforms: Option<Vec<Target>>,