Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ authors = ["Justin Poehnelt"]
keywords = ["cli", "google-workspace", "google", "drive", "gmail"]
categories = ["command-line-utilities", "web-programming"]

[lib]
name = "gws"
path = "src/lib.rs"

[[bin]]
name = "gws"
path = "src/main.rs"
Expand Down
24 changes: 1 addition & 23 deletions src/auth_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,29 +92,7 @@ const READONLY_SCOPES: &[&str] = &[
];

pub fn config_dir() -> PathBuf {
if let Ok(dir) = std::env::var("GOOGLE_WORKSPACE_CLI_CONFIG_DIR") {
return PathBuf::from(dir);
}

// Use ~/.config/gws on all platforms for a consistent, user-friendly path.
let primary = dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".config")
.join("gws");
if primary.exists() {
return primary;
}

// Backward compat: fall back to OS-specific config dir for existing installs
// (e.g. ~/Library/Application Support/gws on macOS, %APPDATA%\gws on Windows).
let legacy = dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("gws");
if legacy.exists() {
return legacy;
}

primary
crate::config::config_dir()
}

fn plain_credentials_path() -> PathBuf {
Expand Down
54 changes: 54 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Configuration directory resolution.
//!
//! Provides [`config_dir()`] to locate the gws config directory, respecting
//! the `GOOGLE_WORKSPACE_CLI_CONFIG_DIR` environment variable and falling back
//! to `~/.config/gws` (or the OS-specific legacy path for existing installs).

use std::path::PathBuf;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To support returning a Result from config_dir and provide more specific error information, GwsError should be imported.

Suggested change
use std::path::PathBuf;
use crate::error::GwsError;
use std::path::PathBuf;


/// Returns the gws configuration directory.
///
/// Resolution order:
/// 1. `GOOGLE_WORKSPACE_CLI_CONFIG_DIR` environment variable (if set)
/// 2. `~/.config/gws` (if it exists — primary location)
/// 3. OS-specific config dir / `gws` (legacy fallback for existing installs)
/// 4. `~/.config/gws` (default for new installs)
pub fn config_dir() -> PathBuf {
if let Ok(dir) = std::env::var("GOOGLE_WORKSPACE_CLI_CONFIG_DIR") {
return PathBuf::from(dir);
}

// Use ~/.config/gws on all platforms for a consistent, user-friendly path.
let primary = dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join(".config")
.join("gws");
if primary.exists() {
return primary;
}

// Backward compat: fall back to OS-specific config dir for existing installs
// (e.g. ~/Library/Application Support/gws on macOS, %APPDATA%\gws on Windows).
let legacy = dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("gws");
if legacy.exists() {
return legacy;
}

primary
}
Comment on lines +30 to +54
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The function config_dir falls back to the current working directory (.) if the user's home or config directory cannot be found. This can lead to configuration files containing sensitive credentials being created in unexpected locations, such as a version-controlled directory or a world-readable temporary folder.

For a robust library API, it's better to return a Result and let the caller decide how to handle the error, rather than failing silently in a potentially insecure way. This change will require updating the call sites to handle the Result.

pub fn config_dir() -> Result<PathBuf, GwsError> {
    if let Ok(dir) = std::env::var("GOOGLE_WORKSPACE_CLI_CONFIG_DIR") {
        return Ok(PathBuf::from(dir));
    }

    let home_dir = dirs::home_dir().ok_or_else(|| {
        GwsError::Validation(
            "Could not determine home directory. Ensure $HOME is set.".to_string(),
        )
    })?;

    // Use ~/.config/gws on all platforms for a consistent, user-friendly path.
    let primary = home_dir.join(".config").join("gws");
    if primary.exists() {
        return Ok(primary);
    }

    // Backward compat: fall back to OS-specific config dir for existing installs
    // (e.g. ~/Library/Application Support/gws on macOS, %APPDATA%\gws on Windows).
    if let Some(config_dir) = dirs::config_dir() {
        let legacy = config_dir.join("gws");
        if legacy.exists() {
            return Ok(legacy);
        }
    }

    Ok(primary)
}

2 changes: 1 addition & 1 deletion src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ pub async fn fetch_discovery_document(
let version =
crate::validate::validate_api_identifier(version).map_err(|e| anyhow::anyhow!("{e}"))?;

let cache_dir = crate::auth_commands::config_dir().join("cache");
let cache_dir = crate::config::config_dir().join("cache");
std::fs::create_dir_all(&cache_dir)?;

let cache_file = cache_dir.join(format!("{service}_{version}.json"));
Expand Down
39 changes: 39 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Google Workspace CLI — library crate.
//!
//! This crate exposes a focused subset of `gws` internals for programmatic
//! access to Google Workspace APIs. Library consumers can introspect API
//! schemas, resolve service names, validate inputs, and build authenticated
//! HTTP clients without depending on any CLI-specific code (clap, ratatui,
//! interactive OAuth flows, etc.).
//!
//! # Exposed modules
//!
//! | Module | Purpose |
//! |---|---|
//! | [`discovery`] | `RestDescription`, `RestMethod`, `RestResource` — introspect Google APIs |
//! | [`error`] | `GwsError` — unified error type |
//! | [`config`] | `config_dir()` — resolve the gws configuration directory |
//! | [`services`] | `resolve_service()`, `SERVICES` — service name to API mapping |
//! | [`validate`] | `validate_api_identifier()`, input safety helpers |
//! | [`client`] | `build_client()`, `send_with_retry()` — HTTP with retry |

pub mod client;
pub mod config;
pub mod discovery;
pub mod error;
pub mod services;
pub mod validate;
13 changes: 8 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,27 @@

mod auth;
pub(crate) mod auth_commands;
mod client;
mod commands;
pub(crate) mod credential_store;
mod discovery;
mod error;
mod executor;
mod formatter;
mod fs_util;
mod generate_skills;
mod helpers;
mod oauth_config;
mod schema;
mod services;
mod setup;
mod setup_tui;
mod text;
mod token_storage;
pub(crate) mod validate;

// Re-use modules from the library crate
use gws::client;
use gws::config;
use gws::discovery;
use gws::error;
use gws::services;
use gws::validate;

use error::{print_error_json, GwsError};

Expand Down
Loading