Skip to content

feat(api): refactor Google Sheets client layer#100

Merged
archiewood merged 34 commits intoevidence-dev:mainfrom
mharrisb1:feat/sheets-client
Feb 12, 2026
Merged

feat(api): refactor Google Sheets client layer#100
archiewood merged 34 commits intoevidence-dev:mainfrom
mharrisb1:feat/sheets-client

Conversation

@mharrisb1
Copy link
Collaborator

@mharrisb1 mharrisb1 commented Jan 13, 2026

Problem

The current implementation has authentication, HTTP transport, and API logic tightly coupled:

  • OpenSSL HTTP calls are scattered throughout gsheets_requests.cpp
  • JWT signing, token exchange, and API calls are intermingled
  • No way to unit test without hitting real Google servers
  • Adding new auth methods requires touching multiple files
  • Deprecated OpenSSL APIs cause compiler warnings

Solution

Introduce clean separation of concerns with three distinct layers:

  1. Transport Layer - HTTP client abstraction (IHttpClient)
  2. Auth Layer - Authentication providers (IAuthProvider)
  3. Service Layer - Google Sheets API client (GoogleSheetsClient)

This enables:

  • Unit testing with MockHttpClient
  • Easy addition of new auth methods
  • Swap HTTP implementations (httplib → DuckDB HTTPUtil)
  • Code that mirrors Google's official client libraries

What Changed

  • Transport layer: IHttpClient, HttpLibClient, MockHttpClient
  • Auth layer: BearerTokenAuth, OAuthAuth, ServiceAccountAuth
  • Service layer: GoogleSheetsClient, SpreadsheetResource, ValuesResource
  • A1Range validation with state machine (replaces incomplete regex)
  • Auth factory: CreateAuthFromSecret() shared between read and copy
  • Secret utility: GetGSheetSecret() reusable helper
  • Integrated new client into gsheets_read.cpp
  • Integrated new client into gsheets_copy.cpp
  • Removed dead code (old sheet lookups, JSON parsing helpers, SheetData struct)
  • 66 unit tests with mock HTTP client (161 assertions)
  • CI workflow for unit tests

CI Pipeline

Restructured the CI pipeline to gate builds behind tests:

graph LR
    A[push / pull_request] --> B[Unit Tests]
    B --> C[SQL Tests]
    C --> D[Build next]
    C --> E[Build stable]
Loading
  • Unit tests run as a reusable workflow (UnitTests.yml), called by the main pipeline
  • SQL tests run as a reusable workflow (SQLTests.yml), using a service account key file to authenticate against the Google Sheets API (both TOKEN and KEY_FILE_PATH tests)
  • Distribution builds only start after both test stages pass
  • Removed add_subdirectory(test/unit) from root CMakeLists.txt so unit tests only build in their own workflow (fixes WASM build failure)
  • Guarded GCC/Clang warning flags with MSVC check (fixes Windows build failure)
  • Added scripts/test_sql.sh for running SQL tests locally and in CI
  • Replaced legacy generate_google_token.py with minimal token generator
  • Trimmed Python dependencies to just google-auth + requests

Client API

#include "sheets/client.hpp"
#include "sheets/auth/bearer_token_auth.hpp"
#include "sheets/transport/httplib_client.hpp"

// Setup
HttpLibClient http;
BearerTokenAuth auth("your-access-token");
GoogleSheetsClient client(http, auth);

// Read values from a range
auto values = client.Spreadsheets("spreadsheet_id")
                    .Values()
                    .Get(A1Range("Sheet1!A1:D10"));

// Update values
ValueRange newData;
newData.values = {{"Hello", "World"}, {"Foo", "Bar"}};
client.Spreadsheets("spreadsheet_id")
      .Values()
      .Update(A1Range("Sheet1!A1"), newData);

// Append rows
client.Spreadsheets("spreadsheet_id")
      .Values()
      .Append(A1Range("Sheet1!A1"), newData);

// Clear a range
client.Spreadsheets("spreadsheet_id")
      .Values()
      .Clear(A1Range("Sheet1!A1:D10"));

// Get spreadsheet metadata
auto metadata = client.Spreadsheets("spreadsheet_id").Get();

// Find a specific sheet
auto sheet = client.Spreadsheets("spreadsheet_id").GetSheetByName("Data");

Architecture

flowchart TB
    subgraph "Extension Layer"
        EXT[gsheets_extension.cpp]
        READ[gsheets_read.cpp]
        COPY[gsheets_copy.cpp]
    end

    subgraph "Service Layer"
        CLIENT[GoogleSheetsClient]
        SPREAD[SpreadsheetResource]
        VAL[ValuesResource]
    end

    subgraph "Auth Layer"
        AUTH[IAuthProvider]
        BEARER[BearerTokenAuth]
        OAUTH[OAuthAuth]
        SA[ServiceAccountAuth]

        AUTH --> BEARER
        AUTH --> OAUTH
        AUTH --> SA
    end

    subgraph "Transport Layer"
        HTTP[IHttpClient]
        HTTPLIB[HttpLibClient]
        MOCK[MockHttpClient]
        DUCKDB["DuckDBHttpClient"]

        HTTP --> HTTPLIB
        HTTP --> MOCK
        HTTP --> DUCKDB
    end

    EXT --> READ
    EXT --> COPY
    READ --> CLIENT
    COPY --> CLIENT
    CLIENT --> SPREAD
    SPREAD --> VAL
    CLIENT --> AUTH
    VAL --> HTTP
    SA --> HTTP

    HTTPLIB --> |HTTPS| GOOGLE[Google APIs]
Loading

Next Steps

  • Migrate gsheets_get_token.cpp - Replace legacy JWT/token code with ServiceAccountAuth in gsheets_auth.cpp (CREATE SECRET path)
  • DuckDB HTTP Client - Implement DuckDBHttpClient using DuckDB's HTTPUtil to enable WASM support and leverage DuckDB's built-in HTTP handling
  • Query Parameters - Add proper HttpQueryParams support to the transport layer for cleaner URL building (currently hardcoded in path strings)

Related Issues & PRs

Directly Addresses

Issue Title Resolution
#85 Auth Issues throw unhelpful error Error messages now include response body for debugging
#35 Abstract perform_https_request for WASM IHttpClient interface enables swappable HTTP implementations

Enables / Unblocks

Issue/PR Title How This Helps
PR #90 Add WASM support DuckDBHttpClient stub ready for HTTPUtil implementation
PR #92 HTTPS proxy support Clean extension point in HttpLibClient or via decorator pattern
PR #64 OAuth: No token copy-paste Isolated OAuthAuth class simplifies OAuth flow changes
#99 Support HTTPS proxy Transport abstraction allows proxy config without touching auth/API code
#87 Access key via parameter IAuthProvider interface enables new auth methods
#73 No secret for public sheets Can add NoAuthProvider returning empty header
#61 Browser Auth in Python Notebook Auth layer isolation enables platform-specific OAuth handling

Foundation for Future Work

Issue Title Future Path
#95 Auto-create sheets SpreadsheetResource will expose Create() method
#74 Optional query params Query parameters can be added to API methods
#68 Avoid append-only writes ValuesResource provides both Append() and Update()

@mharrisb1 mharrisb1 self-assigned this Jan 13, 2026
@mharrisb1 mharrisb1 added the enhancement New feature or request label Jan 13, 2026
mharrisb1 and others added 23 commits January 16, 2026 11:58
Guard GCC/Clang warning flags from MSVC and skip unit tests on Emscripten.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Unit tests run as reusable workflow, gating all downstream jobs
- SQL tests run after unit tests using service account key file auth
- Distribution builds only start after both test stages pass
- Remove unit test subdirectory from extension build (standalone only)
- Guard compiler warning flags for MSVC compatibility
- Add test_sql.sh script for local and CI SQL test execution
- Replace legacy generate_google_token.py with minimal token generator
- Organize .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
mharrisb1 and others added 3 commits January 30, 2026 12:37
Fork PRs don't have access to repository secrets, so SQL tests
now skip with a warning instead of failing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add concurrency cancel-in-progress to SQL tests and deploy docs
- SQL tests support workflow_call, pull_request_target, and workflow_dispatch
- pull_request_target requires integration-tests environment approval
- workflow_dispatch allows maintainers to manually test fork PR refs
- Gracefully skip when credentials unavailable

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Windows headers define DELETE as a macro, causing MSVC build failures.
Also update development docs with repo layout, testing guide, and CI
pipeline overview. Add CLAUDE.md for quick reference.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mharrisb1 mharrisb1 marked this pull request as ready for review January 30, 2026 21:16
@mharrisb1 mharrisb1 requested a review from archiewood January 30, 2026 21:17
@archiewood archiewood merged commit bff3053 into evidence-dev:main Feb 12, 2026
24 checks passed
@archiewood
Copy link
Member

Thank you @mharrisb1 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants