Skip to content

Conversation

@mateoHernandez123
Copy link

@mateoHernandez123 mateoHernandez123 commented Oct 20, 2025

Summary by CodeRabbit

  • Chores

    • Broadened and standardized contextual error messages and input validation across users, groups, roles, events, transfers and services — clearer diagnostics and stricter argument checks (e.g., email format, non-empty/different IDs).
    • Refined logging and non-fatal error behavior to reduce noisy prefixes and improve clarity.
  • New Features

    • Centralized API error wrapping that surfaces rate‑limit details and returns typed, context-rich errors for improved troubleshooting.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Oct 20, 2025

Walkthrough

Adds two Google-API-aware error helpers and systematically replaces many raw errors with context-rich, typed wrappers across connector setup, action handlers, feeds, and resource handlers; one method signature in role.go was updated.

Changes

Cohort / File(s) Summary
Error helpers
pkg/connector/error_helpers.go
Added wrapGoogleApiErrorWithContext and wrapGoogleApiErrorWithRateLimitInfo to map *googleapi.Error HTTP codes → gRPC codes.Code, attach rate-limit metadata when present, and produce contextual status errors.
Actions & data-transfer
pkg/connector/actions.go
Replaced plain errors with uhttp.WrapErrors and contextual wrappers, added validation messages (emails, IDs, booleans, privacy/release params), and wrapped downstream Google API failures with contextual helpers.
Core connector & registration
pkg/connector/connector.go
Switched service/HTTP/JWT/client and action registration failures to contextual wrapped errors; improved cache/service error messages and registration error contexts.
Admin & usage feeds
pkg/connector/admin_event_feed.go, pkg/connector/usage_event_feed.go
Standardized contextual error wrapping for report/service retrieval, page-token marshal/unmarshal, activity listing, event/time parsing, cursor handling, and event construction.
Resource handlers — groups
pkg/connector/group.go
Unified error wrapping in List/Get/Grants/Grant/Revoke flows: pagination token handling, API call failures, resource creation, member operations, and next-page token generation now return contextual wrapped errors.
Resource handlers — roles
pkg/connector/role.go
Standardized wrapped errors across List/Get/Grants/Grant/Revoke; adjusted not-found handling and role assignment flows. Method signature changed: Get(ctx context.Context, resourceId *v2.ResourceId, p *pagination.Token)Get(ctx context.Context, resourceId *v2.ResourceId, parentResourceId *v2.ResourceId).
Resource handlers — users
pkg/connector/user.go
Wrapped pagination, list/get API errors, extraction/build failures, create/delete flows with contextual messages; removed some "google-workspace" prefixes in error text.
Imports / modules
...pkg/...
Added/updated imports across files (e.g., github.com/conductorone/baton-sdk/pkg/uhttp, google.golang.org/grpc/codes, fmt, zap packages, google API packages) to support new error helpers and wrappers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Inspect pkg/connector/error_helpers.go for correct HTTP→gRPC mappings and safe attachment of rate-limit metadata.
  • Verify the role.go Get signature change's call sites and build implications.
  • Check consistent semantics for NotFound/Unavailable mappings across consumers (groups, roles, users, feeds).
  • Review pagination token marshal/unmarshal changes for backward compatibility.
  • Validate action registration error handling and chosen gRPC codes.

Possibly related PRs

Suggested reviewers

  • laurenleach

Poem

🐇
I hop through logs with careful ears,
I stitch new context into errors and fears.
Codes and messages, tidy and bright,
I nudge each bug toward the light.
A rabbit cheers for clearer sights.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main objective: wrapping errors with gRPC codes across the Google Workspace connector, which aligns with the primary changes throughout the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mateoHernandez123/wrap-errors-with-grpc

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15b397a and a2491e2.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum and included by none
📒 Files selected for processing (8)
  • pkg/connector/actions.go (16 hunks)
  • pkg/connector/admin_event_feed.go (20 hunks)
  • pkg/connector/connector.go (10 hunks)
  • pkg/connector/error_helpers.go (1 hunks)
  • pkg/connector/group.go (12 hunks)
  • pkg/connector/role.go (13 hunks)
  • pkg/connector/usage_event_feed.go (9 hunks)
  • pkg/connector/user.go (10 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/connector/usage_event_feed.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-05T20:15:11.628Z
Learnt from: btipling
Repo: ConductorOne/baton-google-workspace PR: 56
File: pkg/connector/error_helpers.go:40-41
Timestamp: 2025-11-05T20:15:11.628Z
Learning: In the baton-google-workspace repository and baton-sdk, HTTP 429 (Too Many Requests) should be mapped to codes.Unavailable, not codes.ResourceExhausted, as per the project's established error handling conventions.

Applied to files:

  • pkg/connector/error_helpers.go
🔇 Additional comments (20)
pkg/connector/admin_event_feed.go (2)

17-18: LGTM - Error wrapping follows established patterns.

The imports and error handling in ListEvents are well-structured:

  • wrapGoogleApiErrorWithContext is used directly for Google API errors (line 68), preserving gRPC status codes
  • fmt.Errorf is used for internal errors (pagination, parsing) which is appropriate per project guidelines since these represent connector bugs that should alert

Also applies to: 42-68


366-450: LGTM - Lookup functions handle errors appropriately.

Both lookupUser and lookupGroup correctly:

  • Use wrapGoogleApiErrorWithContext for Google API errors (lines 391, 434)
  • Handle NotFound gracefully by returning nil and logging
  • Wrap internal service retrieval errors with fmt.Errorf
pkg/connector/actions.go (3)

23-32: LGTM - Input validation with proper gRPC codes.

The validation pattern correctly uses uhttp.WrapErrors(codes.InvalidArgument, ...) for missing required fields, ensuring clients receive meaningful gRPC status codes.


48-50: LGTM - Google API errors wrapped correctly.

The wrapGoogleApiErrorWithContext is returned directly without additional fmt.Errorf wrapping, ensuring the gRPC status code is preserved for clients.


353-391: LGTM - Privacy level parsing with comprehensive validation.

The parseDrivePrivacyLevels function provides clear, actionable error messages with appropriate codes.InvalidArgument status codes for all validation failures.

pkg/connector/role.go (3)

39-84: LGTM - Role List follows error handling conventions.

The List method correctly:

  • Uses fmt.Errorf for pagination/internal errors (lines 44, 75, 81)
  • Returns wrapGoogleApiErrorWithContext directly for API errors (line 59)

167-200: LGTM - Grant method with proper error handling.

The Grant method appropriately uses:

  • uhttp.WrapErrors(codes.FailedPrecondition, ...) for missing service
  • uhttp.WrapErrors(codes.InvalidArgument, ...) for wrong principal type
  • wrapGoogleApiErrorWithContext directly for API errors

230-253: LGTM - Get method implementation.

The Get method correctly wraps the API retrieval error and resource creation error with appropriate context.

pkg/connector/error_helpers.go (2)

66-94: LGTM - Rate limit info wrapper follows baton-sdk pattern.

The wrapGoogleApiErrorWithRateLimitInfo function:

  • Correctly prepends context message to preserve it in gRPC status
  • Safely handles nil rate limit data (line 84)
  • Preserves the original error chain using errors.Join (lines 88-93)

This aligns with the baton-sdk WrapErrorsWithRateLimitInfo implementation.


14-64: The premise of this review is incorrect.

The Google API client library documentation confirms that googleapi.Error is only constructed for non-2xx status codes (with the exception of 304 Not Modified). The CheckResponse function returns nil for 2xx responses and only creates googleapi.Error for error statuses. Therefore, the fallback path at lines 52–63 handling 2xx/3xx codes will not be triggered by successful responses in normal operation—it exists as defensive programming for truly unexpected edge cases or future unforeseen scenarios. This is not a library bug indicator and the code is functioning correctly.

pkg/connector/connector.go (3)

298-336: LGTM - Service creation with typed gRPC errors.

The newGWSAdminServiceForScopes function uses appropriate gRPC codes:

  • codes.Internal for HTTP client creation failure
  • codes.InvalidArgument for JWT config parsing failure
  • codes.Unavailable for service creation failure

This enables proper error classification at the gRPC layer.


447-467: LGTM - fetchDomains error handling.

Line 450 correctly propagates the error from getDirectoryService without additional wrapping (the service already returns wrapped errors). Line 455 uses wrapGoogleApiErrorWithContext directly for API errors.


632-665: LGTM - Action registration with contextual errors.

Each action registration failure is wrapped with fmt.Errorf to provide context about which specific action failed. This is appropriate for internal registration errors.

pkg/connector/group.go (3)

40-109: LGTM - Group List with proper error handling.

The List method follows the established patterns:

  • fmt.Errorf for pagination and resource creation errors
  • wrapGoogleApiErrorWithContext directly for API errors

200-226: LGTM - Grant with idempotent conflict handling.

The Grant method correctly handles the HTTP 409 Conflict case by fetching the existing member (line 214) using the member-scoped provisioning service. This ensures idempotent behavior while preserving proper error wrapping for other failures.


256-287: LGTM - Get method implementation.

The Get method correctly uses wrapGoogleApiErrorWithContext for API retrieval errors and fmt.Errorf for resource creation errors.

pkg/connector/user.go (4)

54-108: LGTM - User List with proper error handling.

The List method correctly:

  • Uses fmt.Errorf for pagination and resource creation errors
  • Returns wrapGoogleApiErrorWithContext directly for the API call (line 85)

227-272: LGTM - User Get with domain validation.

The Get method properly handles:

  • API errors with wrapGoogleApiErrorWithContext
  • Internal extraction errors with fmt.Errorf
  • Domain/customer validation with informational logging

396-479: LGTM - CreateAccount with comprehensive error handling.

The CreateAccount method provides clear validation errors and correctly uses wrapGoogleApiErrorWithContext for the Google API call (line 467).


481-492: LGTM - Delete method error handling fixed.

The error message correctly says "failed to delete user" (line 488), addressing the concern from a previous review. The wrapGoogleApiErrorWithContext is returned directly, preserving the gRPC status code.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (6)
pkg/connector/role.go (4)

115-118: List role assignments: decide on NotFound semantics

If the role can be deleted between discovery and listing assignments, Directory API may return 404. Previously this was treated as empty. Confirm desired behavior; otherwise, convert 404 to an empty page to keep reconciliation resilient.

-  if err != nil {
-    return nil, "", nil, wrapError(err, fmt.Sprintf("google-workspace: failed to list role assignments for role %s", resource.Id.Resource))
-  }
+  if err != nil {
+    if gerr := new(googleapi.Error); errors.As(err, &gerr) && gerr.Code == http.StatusNotFound {
+      // Role disappeared; treat as empty.
+      return nil, "", nil, nil
+    }
+    return nil, "", nil, wrapError(wrapGoogleApiError(err), fmt.Sprintf("google-workspace-v2: failed to list role assignments for role %s", resource.Id.Resource))
+  }

Please confirm whether returning empty on 404 matches the connector’s contract for other list endpoints.


175-186: Grant: preserve idempotency and attach gRPC/rate‑limit details

Keeping 409 as a no‑op is fine for idempotency. For other errors, route through wrapGoogleApiError before wrapError so callers get gRPC codes and rate‑limit details.

-    return nil, nil, wrapError(err, "google-workspace-v2: failed to grant role assignment")
+    return nil, nil, wrapError(wrapGoogleApiError(err), "google-workspace-v2: failed to grant role assignment")

If possible, consider returning an existing grant on 409 by attempting a lookup; if not feasible, current behavior is acceptable.


205-216: Revoke: attach gRPC/rate‑limit details and minor log nit

  • Wrap via wrapGoogleApiError to surface codes/details.
  • Log fields use "group_id" in a role path; consider "role_id" for clarity.
-    return nil, wrapError(err, "google-workspace-v2: failed to revoke role assignment")
+    return nil, wrapError(wrapGoogleApiError(err), "google-workspace-v2: failed to revoke role assignment")

Rename log key from "group_id" to "role_id" (Lines 209–211).


225-228: Get: attach gRPC/rate‑limit details and align prefix

Align prefix with other messages and route through wrapGoogleApiError.

-  return nil, nil, wrapError(err, fmt.Sprintf("google-workspace: failed to retrieve role: %s", resourceId.Resource))
+  return nil, nil, wrapError(wrapGoogleApiError(err), fmt.Sprintf("google-workspace-v2: failed to retrieve role: %s", resourceId.Resource))
pkg/connector/error_helpers.go (2)

23-36: Map 429 to ResourceExhausted and add common client‑error mappings

Improve signal to callers and retry logic by using more specific gRPC codes.

 switch e.Code {
 case http.StatusRequestTimeout:
   return wrapGoogleApiErrorWithRateLimitInfo(codes.DeadlineExceeded, e, err)
-case http.StatusTooManyRequests, http.StatusServiceUnavailable:
-  return wrapGoogleApiErrorWithRateLimitInfo(codes.Unavailable, e, err)
+case http.StatusTooManyRequests:
+  return wrapGoogleApiErrorWithRateLimitInfo(codes.ResourceExhausted, e, err)
+case http.StatusServiceUnavailable, http.StatusBadGateway:
+  return wrapGoogleApiErrorWithRateLimitInfo(codes.Unavailable, e, err)
 case http.StatusNotFound:
   return wrapGoogleApiErrorWithRateLimitInfo(codes.NotFound, e, err)
 case http.StatusUnauthorized:
   return wrapGoogleApiErrorWithRateLimitInfo(codes.Unauthenticated, e, err)
 case http.StatusForbidden:
   return wrapGoogleApiErrorWithRateLimitInfo(codes.PermissionDenied, e, err)
+case http.StatusBadRequest:
+  return wrapGoogleApiErrorWithRateLimitInfo(codes.InvalidArgument, e, err)
+case http.StatusConflict:
+  return wrapGoogleApiErrorWithRateLimitInfo(codes.AlreadyExists, e, err)
+case http.StatusPreconditionFailed:
+  return wrapGoogleApiErrorWithRateLimitInfo(codes.FailedPrecondition, e, err)
+case 422: // Unprocessable Entity
+  return wrapGoogleApiErrorWithRateLimitInfo(codes.InvalidArgument, e, err)
 case http.StatusNotImplemented:
   return wrapGoogleApiErrorWithRateLimitInfo(codes.Unimplemented, e, err)
 }

42-49: Avoid nested errors.Join and keep status first in the chain

Pass component errors directly rather than pre‑joining to reduce nesting depth.

-  return wrapGoogleApiErrorWithRateLimitInfo(codes.Unknown, e, errors.Join(fmt.Errorf("unexpected status code: %d", e.Code), err))
+  return wrapGoogleApiErrorWithRateLimitInfo(codes.Unknown, e, fmt.Errorf("unexpected status code: %d", e.Code), err)

Also consider including e.Message/e.Details in msg construction for Unknown to aid debugging.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce5ce21 and ae979bc.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum and included by none
📒 Files selected for processing (10)
  • go.mod (1 hunks)
  • pkg/connector/actions.go (1 hunks)
  • pkg/connector/admin_event_feed.go (3 hunks)
  • pkg/connector/connector.go (1 hunks)
  • pkg/connector/error_helpers.go (1 hunks)
  • pkg/connector/group.go (5 hunks)
  • pkg/connector/helpers.go (1 hunks)
  • pkg/connector/role.go (5 hunks)
  • pkg/connector/usage_event_feed.go (1 hunks)
  • pkg/connector/user.go (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
pkg/connector/error_helpers.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/ratelimit/http.go (1)
  • ExtractRateLimitData (66-130)
🔇 Additional comments (21)
go.mod (1)

9-9: Benign dependency declaration change.

The addition of googleapis/gax-go/v2 to the require block with its subsequent removal from indirect dependencies results in no net functional change. This appears to be a dependency resolution reordering.

pkg/connector/usage_event_feed.go (1)

149-149: Good contextual error wrapping.

The error from the Google API request is now wrapped with operation-specific context, making it easier to identify the failure point. The message clearly indicates the operation that failed.

pkg/connector/connector.go (1)

218-218: Consistent error wrapping for domain listing.

The error is now wrapped with clear context about the failed operation, following the same pattern established across the codebase.

pkg/connector/actions.go (1)

37-37: Excellent error context with resource identifier.

The error message includes the specific user ID, making it easy to trace which operation failed. This is valuable for debugging and operations.

pkg/connector/helpers.go (2)

54-65: Clean nil-check and error wrapping logic.

The implementation correctly handles the nil case and provides flexible message prefixing. The pattern of wrapping first, then optionally prefixing with context, ensures consistent error handling throughout the codebase.


52-65: No action required — error_helpers.go exists and wrapGoogleApiError is properly defined.

The verification confirms that error_helpers.go is present in the repository at pkg/connector/error_helpers.go and contains the wrapGoogleApiError function (lines 17-50) with the correct signature. The call in helpers.go (line 58) is valid and will compile without errors.

Likely an incorrect or invalid review comment.

pkg/connector/group.go (5)

68-68: Consistent error wrapping for group listing.

The error is now wrapped with clear operation context, replacing the previous raw error return.


137-137: Good context with resource identifier.

The error message includes the specific group ID, making it easy to identify which group's member listing failed.


196-209: Clear error differentiation in grant flow.

The three error paths (get existing member, insert member, unexpected error) each have distinct, descriptive messages. This makes troubleshooting grant operations much easier.


239-239: Clear revoke failure context.

The error message clearly indicates a membership revocation failure, making it easy to understand the operation that failed.


251-251: Error message includes resource identifier.

Including the specific group resource ID in the error message aids in debugging and troubleshooting.

pkg/connector/admin_event_feed.go (3)

69-69: Clear context for activity listing failures.

The error is now wrapped with specific operation context, making it easier to identify failures in the admin event feed.


384-384: User lookup error includes email for traceability.

Including the email address in the error message makes it easy to identify which user lookup failed during event feed processing.


427-427: Group lookup error includes email for traceability.

Including the email address in the error message makes it easy to identify which group lookup failed during event feed processing.

pkg/connector/user.go (5)

82-82: Clear error context for user listing.

The error is now wrapped with operation-specific context, making it easier to identify failures in user listing operations.


230-230: Get user error includes resource identifier.

Including the specific user resource ID in the error message aids in debugging and makes it easy to identify which user retrieval failed.


390-390: User fetch error includes identifier for deletion flow.

The error message clearly indicates which user fetch failed during the deletion process, making troubleshooting easier.


403-403: Suspend operation error includes user identifier.

The error message clearly indicates which user suspension failed, providing good context for debugging.


406-406: Correct return value for successful deletion.

Returning nil, nil on successful user suspension (deletion) is correct and matches the function signature (annotations.Annotations, error).

pkg/connector/error_helpers.go (1)

52-71: Looks solid: attaches rate‑limit details as grpc-status details

The helper builds a status with details and preserves the original error via errors.Join. Good ordering (status first) for errors.As on status.

Would you like a small unit test table for HTTP→gRPC mapping and presence of RateLimitDescription on 429/503? I can generate it quickly.

pkg/connector/role.go (1)

55-58: Update error message prefix for consistency

The prefix standardization concern is valid, but the suggestion to wrap with wrapGoogleApiError is incorrect. The wrapError function already calls wrapGoogleApiError internally (line 58 in helpers.go), so adding it again would be redundant.

Only update the prefix from "google-workspace" to "google-workspace-v2" to match the pattern used at nearby lines 185 and 215:

-    return nil, "", nil, wrapError(err, "google-workspace: failed to list roles")
+    return nil, "", nil, wrapError(err, "google-workspace-v2: failed to list roles")

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
pkg/connector/group.go (1)

206-216: Bug: using the wrong service for Members.Get after 409

On conflict, code calls o.groupService.Members.Get(...). This likely lacks member read scope — should use o.groupMemberService.

- assignment, err = o.groupService.Members.Get(entitlement.Resource.Id.Resource, principal.GetId().GetResource()).Context(ctx).Do()
+ assignment, err = o.groupMemberService.Members.Get(entitlement.Resource.Id.Resource, principal.GetId().GetResource()).Context(ctx).Do()
pkg/connector/actions.go (2)

291-299: Fix status comparison: "IN_PROGRESS" not matched by "inProgress".

API returns "IN_PROGRESS"; current check misses underscore variant and can create duplicate transfers.

Apply robust normalization:

-                if strings.EqualFold(t.OverallTransferStatusCode, "new") || strings.EqualFold(t.OverallTransferStatusCode, "inProgress") {
+                status := strings.ToUpper(strings.ReplaceAll(t.OverallTransferStatusCode, " ", "_"))
+                if status == "NEW" || status == "IN_PROGRESS" {

236-236: Use trimmed IDs when inserting transfers.

You validate trimmed src/dst but pass the untrimmed field values to Insert. This can cause API errors when inputs contain whitespace.

-    return c.dataTransferInsert(ctx, appIdGoogleDocsAndGoogleDrive, sourceField.StringValue, targetField.StringValue, params)
+    return c.dataTransferInsert(ctx, appIdGoogleDocsAndGoogleDrive, src, dst, params)
-    return c.dataTransferInsert(ctx, appIdGoogleCalendar, sourceField.StringValue, targetField.StringValue, params)
+    return c.dataTransferInsert(ctx, appIdGoogleCalendar, src, dst, params)

Also applies to: 267-267

🧹 Nitpick comments (20)
pkg/connector/role.go (2)

44-45: Consider InvalidArgument for bad pagination tokens

Decoding a client-supplied pagination token failing is typically an InvalidArgument, not Internal. Aligning here and in similar paths improves signal to callers.

Also applies to: 102-103


59-60: Map googleapi HTTP errors to precise gRPC codes (avoid blanket Unknown)

Instead of codes.Unknown, map common HTTP statuses:

  • 404 → NotFound
  • 401/403 → PermissionDenied
  • 429 → ResourceExhausted
  • 500/502/503/504 → Unavailable

Recommend a small helper and use it at these sites.

- return nil, "", nil, uhttp.WrapErrors(codes.Unknown, "google-workspace: failed to list roles", err)
+ return nil, "", nil, uhttp.WrapErrors(mapGoogleToGRPC(err), "google-workspace: failed to list roles", err)

Add once in this package:

func mapGoogleToGRPC(err error) codes.Code {
  var gerr *googleapi.Error
  if errors.As(err, &gerr) {
    switch gerr.Code {
    case http.StatusNotFound:
      return codes.NotFound
    case http.StatusUnauthorized, http.StatusForbidden:
      return codes.PermissionDenied
    case http.StatusTooManyRequests:
      return codes.ResourceExhausted
    case http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout, http.StatusInternalServerError:
      return codes.Unavailable
    default:
      return codes.Unknown
    }
  }
  return codes.Unknown
}

Also applies to: 126-127, 194-195, 224-225, 236-237

pkg/connector/admin_event_feed.go (4)

57-60: Token marshal/unmarshal: InvalidArgument may fit better

These are client-cursor issues; consider codes.InvalidArgument instead of Internal.

Also applies to: 128-131


69-72: List activities: map googleapi HTTP to gRPC codes

Same mapping guidance as elsewhere; avoid blanket Unknown on API failures.


268-271: Avoid wrapping an already wrapped error again

lookupGroup/lookupUser already wrap with uhttp; re-wrapping here produces joined statuses with duplicated context. Prefer propagating the original (or add context via fmt.Errorf("%w")).

- return nil, uhttp.WrapErrors(codes.Internal, fmt.Sprintf("google-workspace: failed to lookup group: %s", groupEmail), err)
+ return nil, fmt.Errorf("google-workspace: failed to lookup group: %s: %w", groupEmail, err)

Also applies to: 282-285


371-374: Directory service and get failures: map HTTP->gRPC codes

For getDirectoryService()/Users.Get()/Groups.Get() failures, apply the centralized HTTP→gRPC mapping to produce PermissionDenied/NotFound/Unavailable where appropriate.

Also applies to: 416-417, 386-387, 429-430

pkg/connector/group.go (2)

222-224: Use groupMemberEntitlement for clarity

Both constants resolve to "member", but using roleMemberEntitlement here is confusing. Swap to groupMemberEntitlement.

- grant := sdkGrant.NewGrant(entitlement.Resource, roleMemberEntitlement, principal.GetId())
+ grant := sdkGrant.NewGrant(entitlement.Resource, groupMemberEntitlement, principal.GetId())

70-71: Adopt centralized HTTP→gRPC mapping

Replace several codes.Unknown returns with a helper that maps googleapi.Error HTTP statuses to precise gRPC codes (NotFound/PermissionDenied/ResourceExhausted/Unavailable). See proposed helper in role.go comment.

Also applies to: 147-148, 212-219, 249-250, 259-262

pkg/connector/usage_event_feed.go (4)

175-177: Don’t abort the entire feed on userTrait build failure

This is per-event data; log and continue like you already do for newV2Event errors.

- if err != nil {
-   return nil, nil, nil, uhttp.WrapErrors(codes.Internal, "google-workspace: failed to create user trait", err)
- }
+ if err != nil {
+   l.Error("google-workspace-event-feed: failed to create user trait", zap.Error(err))
+   continue
+ }

209-215: Missing parameters should skip, not raise Internal

Events lacking client_id or app_name are malformed upstream; prefer skipping with debug log to keep the stream healthy.

- return nil, uhttp.WrapErrors(codes.Internal, "google-workspace: no client_id in event parameters", nil)
+ return nil, nil

Apply similarly for app_name. Optionally add a debug log at the call site.


86-91: Pagination token decode/marshal: InvalidArgument fits better

Same guidance as other files: treat malformed cursors as InvalidArgument for clearer client feedback.

Also applies to: 115-116


149-151: ListEvents failures: map HTTP errors; parsing errors already OK

Map req.Do() errors via HTTP→gRPC; keep internal for time parse errors.

Also applies to: 156-156, 129-130

pkg/connector/user.go (3)

58-59: Consider InvalidArgument for pagination token unmarshal

Consistent with other resources.


84-85: Replace codes.Unknown with mapped gRPC codes on API errors

Adopt the shared HTTP→gRPC mapping helper noted earlier for list/get/update failures.

Also applies to: 232-233, 392-393, 405-406


158-190: Optional: dedupe helpers via extractFromInterface

extractRelations/extractOrganizations duplicate extractFromInterface logic. Consider refactoring to use the generic helper for consistency.

pkg/connector/actions.go (4)

26-26: Call WrapErrors without a trailing nil.

Passing a literal nil to a variadic errs parameter is unnecessary and confusing; WrapErrors already handles zero errs.

Example change (apply consistently across the listed lines):

- return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: missing resource ID", nil)
+ return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: missing resource ID")

Also applies to: 31-31, 67-67, 110-110, 153-153, 157-157, 211-211, 215-215, 222-222, 225-225, 243-243, 247-247, 254-254, 257-257, 345-345, 366-366, 373-373, 380-380, 388-388


78-81: Map Google HTTP errors to precise gRPC codes (e.g., 404 → NotFound).

Currently these paths wrap as Unknown. Improve UX by translating googleapi.Error HTTP codes to gRPC (NotFound, PermissionDenied, DeadlineExceeded, etc.).

Minimal example for one call (repeat for similar sites):

- u, err := userService.Users.Get(userId).Context(ctx).Do()
- if err != nil {
-     return nil, nil, uhttp.WrapErrors(codes.Unknown, fmt.Sprintf("google-workspace: failed to get user %s for disableUser", userId), err)
- }
+ u, err := userService.Users.Get(userId).Context(ctx).Do()
+ if err != nil {
+     var gerr *googleapi.Error
+     if errors.As(err, &gerr) && gerr.Code == http.StatusNotFound {
+         return nil, nil, uhttp.WrapErrors(codes.NotFound, fmt.Sprintf("google-workspace: user not found: %s", userId), err)
+     }
+     return nil, nil, uhttp.WrapErrors(codes.Unknown, fmt.Sprintf("google-workspace: failed to get user %s for disableUser", userId), err)
+ }

Add imports:

+   "net/http"
+   "google.golang.org/api/googleapi"

Also applies to: 121-124, 196-197, 325-326


163-171: Validate new_primary_email is within verified domains.

Schema says "must be within a verified domain", but code only checks RFC format. Enforce domain membership to fail fast with InvalidArgument.

   newPrimary := newEmailField.StringValue

   // Validate that newPrimary is a valid email address
   if _, err := mail.ParseAddress(newPrimary); err != nil {
     return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, fmt.Sprintf("google-workspace: invalid email address: %s", newPrimary), err)
   }
+  // Enforce verified domain
+  domains, derr := c.getDomains(ctx)
+  if derr != nil {
+    return nil, nil, uhttp.WrapErrors(codes.Unknown, "google-workspace: failed to get domains", derr)
+  }
+  parts := strings.SplitN(strings.ToLower(newPrimary), "@", 2)
+  if len(parts) != 2 {
+    return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: invalid email address format", nil)
+  }
+  nd := parts[1]
+  okDomain := false
+  for _, d := range domains {
+    if strings.EqualFold(nd, d) {
+      okDomain = true
+      break
+    }
+  }
+  if !okDomain {
+    return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, fmt.Sprintf("google-workspace: new_primary_email domain '%s' is not among verified domains", nd))
+  }

136-136: Typo in comment.

- // This is needed becasuse the SDK would omit any field that has the field type default value (false).
+ // This is needed because the SDK omits fields with their type's default value (false).
pkg/connector/connector.go (1)

503-504: Improve cache type-mismatch message and drop nil arg.

Include the expected type to aid debugging and avoid passing nil to WrapErrors.

-        return nil, uhttp.WrapErrors(codes.Internal, fmt.Sprintf("google-workspace: cache entry for scope %s exists, but is not of type %s", scope, reflect.TypeOf(service)), nil)
+        var expected *T
+        return nil, uhttp.WrapErrors(
+            codes.Internal,
+            fmt.Sprintf("google-workspace: cache entry for scope %s exists, but is %T; expected %T", scope, service, expected),
+        )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae979bc and 0778452.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum and included by none
📒 Files selected for processing (7)
  • pkg/connector/actions.go (16 hunks)
  • pkg/connector/admin_event_feed.go (19 hunks)
  • pkg/connector/connector.go (13 hunks)
  • pkg/connector/group.go (12 hunks)
  • pkg/connector/role.go (13 hunks)
  • pkg/connector/usage_event_feed.go (9 hunks)
  • pkg/connector/user.go (10 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
pkg/connector/actions.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/group.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • AdminDirectoryGroupMemberScope (144-144)
pkg/connector/user.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/role.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/admin_event_feed.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/connector.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • AdminDirectoryDomainReadonlyScope (138-138)
pkg/connector/usage_event_feed.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
🔇 Additional comments (11)
pkg/connector/role.go (2)

123-124: Grants NotFound vs empty result — confirm contract

Comment says “Return no grants if the role no longer exists,” but this now returns NotFound. Ensure the caller expects an error (to prune the resource) rather than an empty page. If empty page is preferred, return nils instead.


75-76: LGTM on internal error wrapping and validations

Resource-build, next-token generation, precondition checks, and arg validation are wrapped consistently.

Also applies to: 81-82, 136-137, 145-146, 169-170, 172-173, 178-178, 250-251

pkg/connector/admin_event_feed.go (1)

74-77: LGTM: parsing latest event time is correctly wrapped and guarded

pkg/connector/group.go (2)

143-147: Grants NotFound vs empty result — confirm contract

Same note as roles: decide whether a missing group should yield NotFound (to prune) or an empty page. Align across connectors.


95-96: LGTM: internal failures are wrapped with context

Resource creation, pagination, and Get build paths look consistent.

Also applies to: 101-102, 165-166, 277-278

pkg/connector/usage_event_feed.go (1)

198-206: LGTM: stream state management and cursor advancement

pkg/connector/user.go (2)

235-239: LGTM: wrapping extraction failure improves diagnosability


302-318: LGTM: posix/external IDs extraction now error-aware

Internal wrap is appropriate; calling sites already wrap with context.

pkg/connector/connector.go (2)

302-308: Good use of structured wrapping in service init.

Also applies to: 333-334


575-607: Nice: action registrations wrapped with context and logs.

Consistent Internal wrapping and logging per action. No changes requested.

pkg/connector/actions.go (1)

350-350: No changes required — RELEASE_RESOURCES value is correct.

The API expects "TRUE" (string) for RELEASE_RESOURCES, which matches the current implementation. The code already uses the correct uppercase literal.

@mateoHernandez123 mateoHernandez123 force-pushed the mateoHernandez123/wrap-errors-with-grpc branch from 0778452 to 25aaae7 Compare October 24, 2025 18:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
pkg/connector/usage_event_feed.go (1)

132-139: Guard against nil pToken before using pToken.Size

ListEvents dereferences pToken (Line 133) before unmarshal; if a caller passes nil, this will panic.

Apply:

-req := s.Activities.List("all", "token")
-req.MaxResults(int64(pToken.Size))
+req := s.Activities.List("all", "token")
+if pToken != nil {
+	req.MaxResults(int64(pToken.Size))
+}
pkg/connector/admin_event_feed.go (2)

376-387: Add .Context(ctx) to user lookup

Ensure requests honor cancellation/timeouts.

- user, err := userService.Users.Get(email).Do()
+ user, err := userService.Users.Get(email).Context(ctx).Do()

419-430: Add .Context(ctx) to group lookup

Same rationale as user lookup.

- group, err := groupService.Groups.Get(email).Do()
+ group, err := groupService.Groups.Get(email).Context(ctx).Do()
🧹 Nitpick comments (9)
pkg/connector/usage_event_feed.go (4)

84-91: Use InvalidArgument for malformed client cursors

Corrupt/invalid cursors are client-provided artifacts; returning codes.InvalidArgument is clearer than Internal.

- return nil, uhttp.WrapErrors(codes.Internal, "google-workspace: failed to decode page token", err)
+ return nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: failed to decode page token", err)
@@
- return nil, uhttp.WrapErrors(codes.Internal, "google-workspace: failed to unmarshal page token JSON", err)
+ return nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: failed to unmarshal page token JSON", err)

Also applies to: 89-91


23-23: Anchor privateAppIDRegex to exact 21 digits

Avoid false positives on longer strings containing 21 digits.

-var privateAppIDRegex = regexp.MustCompile("[0-9]{21}")
+var privateAppIDRegex = regexp.MustCompile(`^\d{21}$`)

Also applies to: 220-223


55-63: Remove unnecessary shadowing in parameter helpers

p := p inside the loops is redundant.

- for _, p := range parameters {
-  p := p
+ for _, p := range parameters {
     if p.Name == name {
       return p.Value
     }
   }

(and similarly in hasParameter)

Also applies to: 65-72


118-121: Nit: variable name typo

basedToken likely meant base64Token for clarity.

- basedToken := base64.StdEncoding.EncodeToString(data)
- return basedToken, nil
+ base64Token := base64.StdEncoding.EncodeToString(data)
+ return base64Token, nil
pkg/connector/actions.go (1)

339-346: Input parsing is solid; consider a small helper to DRY field extraction

You repeatedly parse string/bool fields from args. A tiny helper reduces duplication and centralizes type errors.

Example helper:

func mustStringArg(args *structpb.Struct, key, msg string) (string, error) {
  v, ok := args.Fields[key]
  if !ok || v == nil {
    return "", uhttp.WrapErrors(codes.InvalidArgument, fmt.Sprintf("google-workspace: missing %s", key))
  }
  sv, ok := v.GetKind().(*structpb.Value_StringValue)
  if !ok || strings.TrimSpace(sv.StringValue) == "" {
    return "", uhttp.WrapErrors(codes.InvalidArgument, fmt.Sprintf("google-workspace: %s must be a non-empty string", key))
  }
  return sv.StringValue, nil
}

Use in update/enable/disable/transfer handlers.

Also applies to: 356-389

pkg/connector/role.go (1)

216-223: Logging key nit: use role_id instead of group_id

This log refers to role member deletion but logs group_id.

- l.Info("google-workspace-v2: role member is being deleted but doesn't exist",
-   zap.String("group_id", grant.Entitlement.Resource.Id.Resource),
+ l.Info("google-workspace-v2: role member is being deleted but doesn't exist",
+   zap.String("role_id", grant.Entitlement.Resource.Id.Resource),
    zap.String("user_id", grant.Principal.GetId().GetResource()))
pkg/connector/connector.go (2)

313-324: Wrap non-401 token fetch failures for consistency

Unauthorized is handled specially; other token errors should be wrapped for uniform error surface.

-    return nil, err
+    return nil, uhttp.WrapErrors(codes.Unknown, "google-workspace: failed to fetch OAuth token", err)

298-336: Optional: add short per-call deadlines

External Google Admin/Reports/DataTransfer calls rely on upstream timeouts. Consider context.WithTimeout around high-latency calls (e.g., 10–30s) to fail fast and improve resilience.

Also applies to: 511-555

pkg/connector/admin_event_feed.go (1)

109-111: Prefer evt.Name in debug logs

Type is generic; Name is more actionable when triaging.

- l.Debug("google-workspace-event-feed: skipping event", zap.String("event", evt.Name), zap.String("type", evt.Type))
+ l.Debug("google-workspace-event-feed: skipping event", zap.String("name", evt.Name), zap.String("type", evt.Type))

(and similarly for the USER branch)

Also applies to: 214-215

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0778452 and 25aaae7.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum and included by none
📒 Files selected for processing (7)
  • pkg/connector/actions.go (16 hunks)
  • pkg/connector/admin_event_feed.go (19 hunks)
  • pkg/connector/connector.go (13 hunks)
  • pkg/connector/group.go (12 hunks)
  • pkg/connector/role.go (13 hunks)
  • pkg/connector/usage_event_feed.go (9 hunks)
  • pkg/connector/user.go (10 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/connector/group.go
🧰 Additional context used
🧬 Code graph analysis (6)
pkg/connector/usage_event_feed.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/user.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • UserOrganization (5072-5114)
pkg/connector/admin_event_feed.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/connector.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • AdminDirectoryDomainReadonlyScope (138-138)
pkg/connector/actions.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/role.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • AdminDirectoryRolemanagementScope (165-165)
🔇 Additional comments (10)
pkg/connector/actions.go (1)

229-235: Good validation UX and messages

Nice job on clear, actionable validation errors for privacy_levels and release_resources.

Also applies to: 260-265

pkg/connector/role.go (2)

117-127: Graceful NotFound handling looks good

Returning codes.NotFound for missing roles prevents hard failures during sync.


39-84: Consistent wrapping and pagination handling

List/Get paths now wrap errors consistently with context; pagination token handling is solid.

Also applies to: 230-251

pkg/connector/connector.go (1)

338-347: Consistent error wrapping across connector paths looks good

Service creation, metadata, domain fetch, and action registration now return contextual, coded errors. Nice.

Also applies to: 389-399, 425-436, 575-607

pkg/connector/admin_event_feed.go (1)

146-167: Solid contextual wrapping

Event construction errors are wrapped with precise messages; continuing on per-event errors is appropriate here.

Also applies to: 172-190, 202-216

pkg/connector/user.go (5)

13-13: LGTM! Imports added correctly.

The new imports support the standardized error-wrapping pattern being introduced across the connector.

Also applies to: 17-17


53-107: LGTM! Error wrapping is consistent and well-structured.

The error handling in the List method properly distinguishes between internal processing errors (codes.Internal for pagination/marshaling) and external API errors (codes.Unknown for Google API calls). All messages provide clear context.


225-270: LGTM! Error handling is appropriate.

The Get method correctly applies codes.Unknown for external API calls and codes.Internal for data processing errors. Including the resource ID in error messages aids debugging.


302-343: LGTM! Data extraction errors properly wrapped.

The error handling for extracting POSIX accounts and external IDs consistently uses codes.Internal, which is appropriate for internal data processing failures.


387-409: LGTM! Error wrapping is correct, with a minor note.

The error handling in the Delete method is consistent with the rest of the file. The change at line 408 from return nil, err to return nil, nil is a minor improvement in code clarity—at that point, err is guaranteed to be nil.

Note: The comment on line 395 says "return true" but the method signature returns (annotations.Annotations, error). This is a pre-existing documentation issue unrelated to this PR.

@mateoHernandez123 mateoHernandez123 force-pushed the mateoHernandez123/wrap-errors-with-grpc branch from 25aaae7 to 095b619 Compare November 5, 2025 14:51
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (4)
pkg/connector/actions.go (3)

236-236: Pass the trimmed user IDs into dataTransferInsert

After trimming and validating src/dst, Lines 236 and 267 still forward the raw StringValues (which may include leading/trailing whitespace) to dataTransferInsert. That defeats the validation and can make the API reject the IDs. Use the trimmed src and dst values instead in both Drive and Calendar transfers.

-	return c.dataTransferInsert(ctx, appIdGoogleDocsAndGoogleDrive, sourceField.StringValue, targetField.StringValue, params)
+	return c.dataTransferInsert(ctx, appIdGoogleDocsAndGoogleDrive, src, dst, params)
-	return c.dataTransferInsert(ctx, appIdGoogleCalendar, sourceField.StringValue, targetField.StringValue, params)
+	return c.dataTransferInsert(ctx, appIdGoogleCalendar, src, dst, params)

Also applies to: 267-267


305-309: Handle nil transfers before dereferencing NextPageToken

Transfers.List().Do() may succeed with a nil response body; Line 305 dereferences transfers.NextPageToken regardless, which panics in that scenario. Add an explicit nil check before reading NextPageToken (and skip assigning the page token when nil) so the loop exits safely.

-		if transfers.NextPageToken == "" {
+		if transfers == nil || transfers.NextPageToken == "" {
 			break
 		}
 		pageToken = transfers.NextPageToken

24-32: Guard structpb field lookups before calling GetKind

Line 24 (and again on Line 29) dereferences args.Fields["…"] before checking if the map contained a value, so a missing argument yields a nil *structpb.Value, GetKind() panics, and the new Wrapped error is never returned. Please grab the map value first, ensure it’s present and non-nil, and only then call GetKind(). The same pattern needs to be applied to user_id, new_primary_email, and the drive/calendar target fields in the other action handlers.

-	guidField, ok := args.Fields["resource_id"].GetKind().(*structpb.Value_StringValue)
-	if !ok {
+	resourceIDValue, present := args.Fields["resource_id"]
+	if !present || resourceIDValue == nil {
+		return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: missing resource ID")
+	}
+	guidField, ok := resourceIDValue.GetKind().(*structpb.Value_StringValue)
+	if !ok {
 		return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: missing resource ID")
 	}
 
-	isSuspendedField, ok := args.Fields["is_suspended"].GetKind().(*structpb.Value_BoolValue)
-	if !ok {
+	isSuspendedValue, present := args.Fields["is_suspended"]
+	if !present || isSuspendedValue == nil {
+		return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: missing is_suspended")
+	}
+	isSuspendedField, ok := isSuspendedValue.GetKind().(*structpb.Value_BoolValue)
+	if !ok {
 		return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: missing is_suspended")
 	}
pkg/connector/admin_event_feed.go (1)

55-55: Guard pToken before calling Size

ListEvents still dereferences pToken without a nil check, so the first page (where callers pass a nil token) will panic. Please guard the MaxResults call before touching pToken.Size(), mirroring the earlier fix from usage_event_feed.

 req := s.Activities.List("all", "admin")
-req.MaxResults(int64(pToken.Size))
+if pToken != nil {
+	req.MaxResults(int64(pToken.Size))
+}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25aaae7 and 095b619.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum and included by none
📒 Files selected for processing (8)
  • pkg/connector/actions.go (16 hunks)
  • pkg/connector/admin_event_feed.go (19 hunks)
  • pkg/connector/connector.go (13 hunks)
  • pkg/connector/error_helpers.go (1 hunks)
  • pkg/connector/group.go (11 hunks)
  • pkg/connector/role.go (13 hunks)
  • pkg/connector/usage_event_feed.go (10 hunks)
  • pkg/connector/user.go (10 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-16T18:24:02.512Z
Learnt from: agustin-conductor
Repo: ConductorOne/baton-google-workspace PR: 55
File: pkg/connector/actions.go:232-233
Timestamp: 2025-10-16T18:24:02.512Z
Learning: In Google Workspace Data Transfer API (admin/datatransfer/v1), the PRIVACY_LEVEL parameter values must be lowercase ("private", "shared"). Using uppercase values causes the API to ignore the parameter and transfer all privacy levels regardless of what was specified.

Applied to files:

  • pkg/connector/actions.go
🧬 Code graph analysis (8)
pkg/connector/connector.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • AdminDirectoryDomainReadonlyScope (138-138)
pkg/connector/group.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (3)
  • AdminDirectoryGroupMemberScope (144-144)
  • Members (3259-3282)
  • Member (3211-3252)
pkg/connector/actions.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/admin_event_feed.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/user.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • UserOrganization (5072-5114)
pkg/connector/usage_event_feed.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/error_helpers.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/ratelimit/http.go (1)
  • ExtractRateLimitData (66-130)
pkg/connector/role.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • AdminDirectoryRolemanagementScope (165-165)

@mateoHernandez123 mateoHernandez123 force-pushed the mateoHernandez123/wrap-errors-with-grpc branch from 095b619 to 44b5d37 Compare November 5, 2025 15:18
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 095b619 and 44b5d37.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum and included by none
📒 Files selected for processing (8)
  • pkg/connector/actions.go (16 hunks)
  • pkg/connector/admin_event_feed.go (19 hunks)
  • pkg/connector/connector.go (13 hunks)
  • pkg/connector/error_helpers.go (1 hunks)
  • pkg/connector/group.go (11 hunks)
  • pkg/connector/role.go (13 hunks)
  • pkg/connector/usage_event_feed.go (10 hunks)
  • pkg/connector/user.go (10 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-16T18:24:02.512Z
Learnt from: agustin-conductor
Repo: ConductorOne/baton-google-workspace PR: 55
File: pkg/connector/actions.go:232-233
Timestamp: 2025-10-16T18:24:02.512Z
Learning: In Google Workspace Data Transfer API (admin/datatransfer/v1), the PRIVACY_LEVEL parameter values must be lowercase ("private", "shared"). Using uppercase values causes the API to ignore the parameter and transfer all privacy levels regardless of what was specified.

Applied to files:

  • pkg/connector/actions.go
🧬 Code graph analysis (8)
pkg/connector/usage_event_feed.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/user.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/role.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • AdminDirectoryRolemanagementScope (165-165)
pkg/connector/admin_event_feed.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/actions.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/error_helpers.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/ratelimit/http.go (1)
  • ExtractRateLimitData (66-130)
pkg/connector/connector.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • AdminDirectoryDomainReadonlyScope (138-138)
pkg/connector/group.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (3)
  • AdminDirectoryGroupMemberScope (144-144)
  • Members (3259-3282)
  • Member (3211-3252)

err := bag.Unmarshal(pt.Token)
if err != nil {
return nil, "", nil, err
return nil, "", nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: failed to unmarshal pagination token in group Grants", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not wrap any of these pagination bag errors, as per Luisina's guidelines we should just do it when returning erros from an http request, sdk library, or custom errors regarding permissions for example where a GRPC makes sense. The point of wrapping these errors it to alert less. A pagination bag error should 100% alert us as it is a connector bug.

I also noticed that we're being inconsistent here codes.InvalidArgument here and codes.Internal, but this 100% should not be a codes.InvalidArgument as this has nothing to do with how the customer configured the connector. Please just remove all these wrapped errors that don't align with Luisina's guidance. Also be sure you are syncing the connector after making changes to make sure this doesn't impact the connector's ability to sync.

Wrap custom errors into uhttp errors using uhttp.WrapErrors (with status code), then the SDK is able to check status and decide how to proceed with the given error. This support is needed when returning errors from an HTTP request (no uhttp), SDK library, or custom errors inferred by the developer

@mateoHernandez123 mateoHernandez123 force-pushed the mateoHernandez123/wrap-errors-with-grpc branch from 44b5d37 to f872b97 Compare November 7, 2025 13:53
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (3)
pkg/connector/usage_event_feed.go (1)

147-150: Preserve the gRPC status when listing usage activities fails

Wrapping wrapGoogleApiError(err) with fmt.Errorf again hides the helper’s GRPCStatus, so callers only see codes.Unknown. Emit any context via logging, then return the helper error directly (e.g., log with zap and return wrapGoogleApiError(err)) so the original status propagates.

pkg/connector/role.go (1)

57-60: Keep role-related Google API errors returning their gRPC status

Across these role paths you’re still wrapping wrapGoogleApiError(err) with fmt.Errorf, which hides the helper’s GRPCStatus implementation and collapses everything to codes.Unknown. Return the helper error directly (log context separately or extend the helper to format it) so callers receive the correct gRPC code and can react appropriately.

Also applies to: 117-120, 185-188, 205-218, 227-230

pkg/connector/admin_event_feed.go (1)

68-69: Preserve the gRPC status returned by wrapGoogleApiError.

Wrapping the helper’s result with fmt.Errorf drops the embedded GRPCStatus, so callers now only see codes.Unknown instead of the Google→gRPC mapping. Return the helper’s error directly (after adding context before calling it) and update the other fmt.Errorf(... wrapGoogleApiError(err)) sites in this file accordingly.

-		return nil, nil, nil, fmt.Errorf("google-workspace: failed to list admin activities: %w", wrapGoogleApiError(err))
+		return nil, nil, nil, wrapGoogleApiError(fmt.Errorf("google-workspace: failed to list admin activities: %w", err))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44b5d37 and f872b97.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum and included by none
📒 Files selected for processing (8)
  • pkg/connector/actions.go (16 hunks)
  • pkg/connector/admin_event_feed.go (19 hunks)
  • pkg/connector/connector.go (12 hunks)
  • pkg/connector/error_helpers.go (1 hunks)
  • pkg/connector/group.go (11 hunks)
  • pkg/connector/role.go (13 hunks)
  • pkg/connector/usage_event_feed.go (9 hunks)
  • pkg/connector/user.go (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/connector/actions.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: btipling
Repo: ConductorOne/baton-google-workspace PR: 56
File: pkg/connector/error_helpers.go:40-41
Timestamp: 2025-11-05T20:15:11.628Z
Learning: In the baton-google-workspace repository and baton-sdk, HTTP 429 (Too Many Requests) should be mapped to codes.Unavailable, not codes.ResourceExhausted, as per the project's established error handling conventions.
📚 Learning: 2025-11-05T20:15:11.628Z
Learnt from: btipling
Repo: ConductorOne/baton-google-workspace PR: 56
File: pkg/connector/error_helpers.go:40-41
Timestamp: 2025-11-05T20:15:11.628Z
Learning: In the baton-google-workspace repository and baton-sdk, HTTP 429 (Too Many Requests) should be mapped to codes.Unavailable, not codes.ResourceExhausted, as per the project's established error handling conventions.

Applied to files:

  • pkg/connector/error_helpers.go
🧬 Code graph analysis (6)
pkg/connector/role.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • AdminDirectoryRolemanagementScope (165-165)
pkg/connector/connector.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/user.go (1)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • UserOrganization (5072-5114)
pkg/connector/admin_event_feed.go (1)
vendor/google.golang.org/api/admin/reports/v1/admin-gen.go (1)
  • Activities (223-248)
pkg/connector/group.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (3)
  • AdminDirectoryGroupMemberScope (144-144)
  • Members (3259-3282)
  • Member (3211-3252)
pkg/connector/error_helpers.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/ratelimit/http.go (1)
  • ExtractRateLimitData (66-130)

@mateoHernandez123 mateoHernandez123 force-pushed the mateoHernandez123/wrap-errors-with-grpc branch 2 times, most recently from 4ed160e to 10d8a0b Compare November 7, 2025 14:19
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
pkg/connector/admin_event_feed.go (3)

141-151: Preserve gRPC status: don’t re-wrap helper errors in handleGroupEvent

Errors from newGroupChangedEvent/newGroupMemberGrantEvent may already carry GRPCStatus. Wrapping with fmt.Errorf masks it as codes.Unknown. Return err directly here.

Apply:

-        if err != nil {
-            return nil, fmt.Errorf("google-workspace: failed to create group changed event: %w", err)
-        }
+        if err != nil {
+            return nil, err
+        }
...
-        if err != nil {
-            return nil, fmt.Errorf("google-workspace: failed to create group changed event (group email): %w", err)
-        }
+        if err != nil {
+            return nil, err
+        }
...
-        if err != nil {
-            return nil, fmt.Errorf("google-workspace: failed to create group changed event (new value): %w", err)
-        }
+        if err != nil {
+            return nil, err
+        }
...
-        if err != nil {
-            return nil, fmt.Errorf("google-workspace: failed to create group member grant event: %w", err)
-        }
+        if err != nil {
+            return nil, err
+        }
...
-        if err != nil {
-            return nil, fmt.Errorf("google-workspace: failed to create group changed event (update member): %w", err)
-        }
+        if err != nil {
+            return nil, err
+        }

Also applies to: 151-161, 161-169, 169-178, 178-187


199-206: Preserve gRPC status in handleUserEvent

Same masking via fmt.Errorf; return err directly so the mapped code propagates.

-        if err != nil {
-            return nil, fmt.Errorf("google-workspace: failed to create user changed event: %w", err)
-        }
+        if err != nil {
+            return nil, err
+        }

265-283: Avoid masking helper errors in newGroupMemberGrantEvent / newUserChangedEvent

lookupGroup/lookupUser already wrap API errors with GRPCStatus; re-wrapping masks it. Return err directly. Keep fmt.Errorf for local resource-build errors.

-    group, err := f.lookupGroup(ctx, groupEmail)
+    group, err := f.lookupGroup(ctx, groupEmail)
     if err != nil {
-        return nil, fmt.Errorf("google-workspace: failed to lookup group %s: %w", groupEmail, err)
+        return nil, err
     }
...
-    user, err := f.lookupUser(ctx, userEmail)
+    user, err := f.lookupUser(ctx, userEmail)
     if err != nil {
-        return nil, fmt.Errorf("google-workspace: failed to lookup user %s in newGroupMemberGrantEvent: %w", userEmail, err)
+        return nil, err
     }
-    user, err := f.lookupUser(ctx, userEmail)
+    user, err := f.lookupUser(ctx, userEmail)
     if err != nil {
-        return nil, fmt.Errorf("google-workspace: failed to lookup user %s in newUserChangedEvent: %w", userEmail, err)
+        return nil, err
     }

Also applies to: 333-338

pkg/connector/group.go (1)

209-214: Build break: undefined identifier roleMemberEntitlement

Constant defined here is groupMemberEntitlement; replace to fix compile and keep naming consistent with Entitlements/Grants.

-    grant := sdkGrant.NewGrant(entitlement.Resource, roleMemberEntitlement, principal.GetId())
+    grant := sdkGrant.NewGrant(entitlement.Resource, groupMemberEntitlement, principal.GetId())
♻️ Duplicate comments (2)
pkg/connector/actions.go (1)

24-32: Defend against nil structpb.Value entries before calling GetKind()

These handlers still call args.Fields["…"].GetKind() directly. If the key is absent (or explicitly set to nil), that expression dereferences a nil pointer and panics the action execution path. We previously flagged this; it’s still a release blocker. Please first retrieve the *structpb.Value, confirm it’s present and non-nil, and only then call GetKind(). Apply the same guard to each touched handler (resource_id, is_suspended, user_id, new_primary_email, target_resource_id, etc.).

-	guidField, ok := args.Fields["resource_id"].GetKind().(*structpb.Value_StringValue)
-	if !ok {
+	resourceVal, present := args.Fields["resource_id"]
+	if !present || resourceVal == nil {
+		return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: missing resource ID")
+	}
+	guidField, ok := resourceVal.GetKind().(*structpb.Value_StringValue)
+	if !ok {
 		return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: missing resource ID")
 	}
@@
-	isSuspendedField, ok := args.Fields["is_suspended"].GetKind().(*structpb.Value_BoolValue)
-	if !ok {
+	suspendedVal, present := args.Fields["is_suspended"]
+	if !present || suspendedVal == nil {
+		return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: missing is_suspended")
+	}
+	isSuspendedField, ok := suspendedVal.GetKind().(*structpb.Value_BoolValue)
+	if !ok {
 		return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "google-workspace: missing is_suspended")
 	}

Replicate this pattern for the other argument lookups called out above.

Also applies to: 65-68, 108-111, 151-158, 209-216, 241-248

pkg/connector/admin_event_feed.go (1)

52-53: pToken nil guard was previously flagged (acknowledging prior thread)

Dereferencing pToken.Size can panic if pToken is nil. Not blocking this PR per earlier scope decision. Tagging as duplicate for tracking.

🧹 Nitpick comments (2)
pkg/connector/error_helpers.go (1)

66-87: Preserve the original errors when returning the status

wrapGoogleApiErrorWithRateLimitInfo accepts errs ...error, but the current implementation drops them. That strips out the original googleapi.Error (and any additional context) from the chain, making troubleshooting and errors.Is/As checks impossible once the helper returns. Please join the base status error with the extras so the full context survives.

 func wrapGoogleApiErrorWithRateLimitInfo(preferredCode codes.Code, contextMsg string, e *googleapi.Error, errs ...error) error {
@@
-	return st.Err()
+	baseErr := st.Err()
+	if len(errs) == 0 {
+		return baseErr
+	}
+
+	combined := []error{baseErr}
+	for _, extra := range errs {
+		if extra != nil {
+			combined = append(combined, extra)
+		}
+	}
+
+	return errors.Join(combined...)
 }
pkg/connector/admin_event_feed.go (1)

358-399: Reduce lock scope to avoid holding mutex across network calls

lookupUser/lookupGroup hold mutex while calling Google APIs. Narrow the critical section: check cache under lock; on miss, release lock, fetch from API, then re-acquire to write. Improves concurrency and avoids head-of-line blocking.

Also applies to: 401-442

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f872b97 and 10d8a0b.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum and included by none
📒 Files selected for processing (8)
  • pkg/connector/actions.go (16 hunks)
  • pkg/connector/admin_event_feed.go (19 hunks)
  • pkg/connector/connector.go (12 hunks)
  • pkg/connector/error_helpers.go (1 hunks)
  • pkg/connector/group.go (11 hunks)
  • pkg/connector/role.go (13 hunks)
  • pkg/connector/usage_event_feed.go (9 hunks)
  • pkg/connector/user.go (9 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: btipling
Repo: ConductorOne/baton-google-workspace PR: 56
File: pkg/connector/error_helpers.go:40-41
Timestamp: 2025-11-05T20:15:11.628Z
Learning: In the baton-google-workspace repository and baton-sdk, HTTP 429 (Too Many Requests) should be mapped to codes.Unavailable, not codes.ResourceExhausted, as per the project's established error handling conventions.
📚 Learning: 2025-10-16T18:24:02.512Z
Learnt from: agustin-conductor
Repo: ConductorOne/baton-google-workspace PR: 55
File: pkg/connector/actions.go:232-233
Timestamp: 2025-10-16T18:24:02.512Z
Learning: In Google Workspace Data Transfer API (admin/datatransfer/v1), the PRIVACY_LEVEL parameter values must be lowercase ("private", "shared"). Using uppercase values causes the API to ignore the parameter and transfer all privacy levels regardless of what was specified.

Applied to files:

  • pkg/connector/actions.go
📚 Learning: 2025-11-05T20:15:11.628Z
Learnt from: btipling
Repo: ConductorOne/baton-google-workspace PR: 56
File: pkg/connector/error_helpers.go:40-41
Timestamp: 2025-11-05T20:15:11.628Z
Learning: In the baton-google-workspace repository and baton-sdk, HTTP 429 (Too Many Requests) should be mapped to codes.Unavailable, not codes.ResourceExhausted, as per the project's established error handling conventions.

Applied to files:

  • pkg/connector/error_helpers.go
🧬 Code graph analysis (7)
pkg/connector/role.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • AdminDirectoryRolemanagementScope (165-165)
pkg/connector/user.go (1)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (1)
  • UserOrganization (5072-5114)
pkg/connector/actions.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/error_helpers.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/ratelimit/http.go (1)
  • ExtractRateLimitData (66-130)
pkg/connector/connector.go (1)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
pkg/connector/group.go (2)
vendor/github.com/conductorone/baton-sdk/pkg/uhttp/wrapper.go (1)
  • WrapErrors (316-325)
vendor/google.golang.org/api/admin/directory/v1/admin-gen.go (3)
  • AdminDirectoryGroupMemberScope (144-144)
  • Members (3259-3282)
  • Member (3211-3252)
pkg/connector/admin_event_feed.go (1)
vendor/google.golang.org/api/admin/reports/v1/admin-gen.go (1)
  • Activities (223-248)
🔇 Additional comments (5)
pkg/connector/user.go (2)

300-316: Internal extraction errors: context is fine

Using fmt.Errorf for posix/external ID extraction is appropriate (non-HTTP). No changes requested.


80-84: No improper error wrapping found
Scan results show no occurrences of fmt.Errorf wrapping wrapGoogleApiError or wrapGoogleApiErrorWithContext. Please manually verify that no masked error helpers remain elsewhere.

pkg/connector/group.go (2)

68-71: Good: helper used for Google API errors; uhttp for precondition/arg

You’re returning helper/uhttp errors directly without fmt.Errorf re-wrapping, preserving GRPCStatus. LGTM.

Also applies to: 137-141, 247-251, 216-223


119-123: Pagination token errors: plain errors are fine per guidance

Using fmt.Errorf (not gRPC-wrapped) for Bag/NextToken errors aligns with “wrap only HTTP/SDK/custom permission errors.” No change requested. As per coding guidelines.

Also applies to: 154-159, 98-102, 40-45

pkg/connector/admin_event_feed.go (1)

66-69: No action needed: HTTP 429 correctly maps to codes.Unavailable

userService, err := c.getDirectoryService(ctx, directoryAdmin.AdminDirectoryUserScope)
if err != nil {
return nil, nil, err
return nil, nil, fmt.Errorf("google-workspace: failed to get directory service for updateUserStatus: %w", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: connector name in error (again, fine in logs, but imo not in errors)

@mateoHernandez123 mateoHernandez123 force-pushed the mateoHernandez123/wrap-errors-with-grpc branch from 10d8a0b to 4d3e538 Compare November 26, 2025 12:45
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
pkg/connector/actions.go (2)

218-226: Validation uses trimmed values but API call uses untrimmed values.

In transferUserDriveFiles (line 236) and transferUserCalendar (line 267), you validate the trimmed src and dst but pass sourceField.StringValue and targetField.StringValue to dataTransferInsert. If the original values contain leading/trailing whitespace, the validation passes but the API receives untrimmed IDs.

This was flagged as out of scope for this PR, but it's worth noting for a follow-up fix:

-return c.dataTransferInsert(ctx, appIdGoogleDocsAndGoogleDrive, sourceField.StringValue, targetField.StringValue, params)
+return c.dataTransferInsert(ctx, appIdGoogleDocsAndGoogleDrive, src, dst, params)

Also applies to: 250-258


23-32: Nil pointer dereference risk confirmed, but commit reference is incorrect.

The concern is valid: when args.Fields["key"] returns nil (missing key), calling .GetKind().(*structpb.Value_StringValue) will panic during type assertion. However, commit 095b619 does not exist in this repository's history—the last two commits are 4d3e538 and 2ee36ee.

The unsafe pattern exists in 10 locations across 6 functions (lines 24, 29, 65, 108, 151, 155, 209, 213, 241, 245), while the correct pattern is already used in buildReleaseResourcesParam (line 339-340) and parseDrivePrivacyLevels (line 360-361), confirming inconsistent application.

Apply the safe pattern to all updateUserStatus, disableUserActionHandler, enableUserActionHandler, changeUserPrimaryEmail, transferUserDriveFiles, and transferUserCalendar functions:

-guidField, ok := args.Fields["resource_id"].GetKind().(*structpb.Value_StringValue)
-if !ok {
+v, present := args.Fields["resource_id"]
+if !present || v == nil {
+  return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "missing resource ID")
+}
+guidField, ok := v.GetKind().(*structpb.Value_StringValue)
+if !ok {
   return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "missing resource ID")
 }
🧹 Nitpick comments (2)
pkg/connector/error_helpers.go (1)

52-64: Unreachable code path for 2xx status codes.

If e.Code is in the 200-299 range, the condition on line 52 (e.Code < 200 || e.Code >= 300) is false, so control falls through to lines 56-63. However, Google API errors with 2xx status codes are extremely unlikely—googleapi.Error is typically only returned for non-success responses. This dead code path could confuse future maintainers.

Consider removing the unreachable fallthrough or adding a comment explaining this is defensive coding:

 	if e.Code < 200 || e.Code >= 300 {
 		return wrapGoogleApiErrorWithRateLimitInfo(codes.Unknown, contextMsg, e, errors.Join(fmt.Errorf("unexpected status code: %d", e.Code), err))
 	}

-	contextErr := err
-	if contextMsg != "" {
-		contextErr = errors.Join(fmt.Errorf("%s", contextMsg), err)
-	}
-	return errors.Join(
-		fmt.Errorf("unexpected status code: %d", e.Code),
-		contextErr,
-	)
+	// Defensive: googleapi.Error with 2xx should not occur, but handle gracefully
+	return wrapGoogleApiErrorWithRateLimitInfo(codes.OK, contextMsg, e, err)
pkg/connector/usage_event_feed.go (1)

208-214: Consider returning nil instead of error for missing parameters.

These errors indicate malformed event data from Google's API rather than a connector failure. Returning an error causes the entire event to be logged as an error (line 179), but the function already returns nil, nil for private apps (lines 219-222). Consider returning nil, nil here too for consistency, or change the log level in the caller.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10d8a0b and 4d3e538.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum and included by none
📒 Files selected for processing (8)
  • pkg/connector/actions.go (16 hunks)
  • pkg/connector/admin_event_feed.go (20 hunks)
  • pkg/connector/connector.go (13 hunks)
  • pkg/connector/error_helpers.go (1 hunks)
  • pkg/connector/group.go (12 hunks)
  • pkg/connector/role.go (13 hunks)
  • pkg/connector/usage_event_feed.go (9 hunks)
  • pkg/connector/user.go (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/connector/user.go
  • pkg/connector/connector.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-05T20:15:11.628Z
Learnt from: btipling
Repo: ConductorOne/baton-google-workspace PR: 56
File: pkg/connector/error_helpers.go:40-41
Timestamp: 2025-11-05T20:15:11.628Z
Learning: In the baton-google-workspace repository and baton-sdk, HTTP 429 (Too Many Requests) should be mapped to codes.Unavailable, not codes.ResourceExhausted, as per the project's established error handling conventions.

Applied to files:

  • pkg/connector/error_helpers.go
🔇 Additional comments (17)
pkg/connector/role.go (3)

39-84: LGTM!

Error handling in the List function is well-structured:

  • Pagination unmarshalling errors are wrapped with context
  • Google API errors use wrapGoogleApiErrorWithContext to preserve gRPC status
  • Internal resource creation errors use fmt.Errorf appropriately

160-193: LGTM!

The Grant function properly uses:

  • uhttp.WrapErrors with codes.FailedPrecondition for missing service scope
  • uhttp.WrapErrors with codes.InvalidArgument for invalid principal type and conversion errors
  • wrapGoogleApiErrorWithContext for Google API errors
  • Correct handling of HTTP 409 Conflict (existing assignment) returning nil

195-221: LGTM!

The Revoke function correctly:

  • Uses uhttp.WrapErrors for precondition and validation errors
  • Handles HTTP 404 gracefully with logging (idempotent delete)
  • Wraps other Google API errors with context
pkg/connector/usage_event_feed.go (1)

122-206: LGTM!

The ListEvents function demonstrates good error handling:

  • Internal service/parsing errors use fmt.Errorf with context
  • Google API error at line 149 correctly uses wrapGoogleApiErrorWithContext
  • Individual event creation failures are logged but don't fail the entire feed (lines 178-182)
  • Cursor marshalling errors are properly wrapped
pkg/connector/actions.go (2)

270-334: LGTM!

The dataTransferInsert function has proper error handling:

  • Service retrieval errors wrapped with fmt.Errorf
  • Google API errors use wrapGoogleApiErrorWithContext
  • Idempotency check for existing transfers is well-implemented

353-391: LGTM!

The parseDrivePrivacyLevels function has thorough validation:

  • Type checks for list and string values
  • Validation against allowed values
  • Deduplication of normalized values
  • Clear error messages with uhttp.WrapErrors
pkg/connector/admin_event_feed.go (3)

42-135: LGTM!

The ListEvents function has well-structured error handling:

  • Service and token errors use fmt.Errorf with descriptive context
  • Google API error at line 68 correctly uses wrapGoogleApiErrorWithContext
  • Event handler errors are logged but don't fail the entire feed (lines 93-96, 100-103)
  • Cursor marshalling errors are properly wrapped

358-399: LGTM!

The lookupUser function properly:

  • Wraps service retrieval errors with context
  • Handles HTTP 404 gracefully by returning nil (user no longer exists)
  • Uses wrapGoogleApiErrorWithContext for other Google API errors
  • Maintains cache integrity by deleting entries for non-existent users

401-442: LGTM!

The lookupGroup function mirrors lookupUser with consistent error handling patterns and proper cache management.

pkg/connector/group.go (8)

40-45: LGTM - Pagination error handling is appropriate.

Plain fmt.Errorf wrapping (without gRPC codes) is correct here per guidance that pagination bag errors should alert as connector bugs rather than being suppressed with gRPC status codes.


68-71: LGTM - API error wrapping is correct.

Using wrapGoogleApiErrorWithContext for the Google API call properly maps HTTP errors to gRPC codes while preserving context.


93-96: LGTM - SDK error handling is appropriate.

Plain error wrapping for SDK resource creation failures is correct since these are not external API calls requiring gRPC status code mapping.


99-102: Consistent pagination error handling.


118-160: LGTM - Grants method error handling follows correct patterns.

  • Pagination errors use plain fmt.Errorf (lines 122, 157)
  • API call uses wrapGoogleApiErrorWithContext (line 139)
  • SDK resource creation uses plain fmt.Errorf (line 149)

This correctly distinguishes external API errors from internal processing errors.


188-214: LGTM - Grant method error handling is well-structured.

  • FailedPrecondition for missing service scope (line 190) and InvalidArgument for wrong principal type (line 193) are appropriate gRPC codes
  • Conflict handling (lines 200-208) correctly implements idempotency by fetching existing member
  • Uses groupMemberProvisioningService for the member lookup (line 202), which has the correct scope

216-242: LGTM - Revoke method maintains consistency with Grant.

Error handling mirrors Grant method patterns appropriately:

  • Same precondition checks with correct gRPC codes
  • 404/NotFound case logs and returns nil for idempotent behavior
  • Other API errors properly wrapped

244-270: LGTM - Get method error handling is correct.

  • API retrieval error properly wrapped with resource ID context (line 250)
  • SDK resource creation uses plain error wrapping (line 266)
  • Edge case of group with no ID is logged and handled gracefully

Copy link
Contributor

@btipling btipling left a comment

Choose a reason for hiding this comment

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

Has conflicts and this connector needs to be containerized. The ci.yaml is out of date.

)
}

func wrapGoogleApiErrorWithRateLimitInfo(preferredCode codes.Code, contextMsg string, e *googleapi.Error, errs ...error) error {

Choose a reason for hiding this comment

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

we should follow baton-sdk implementation for this function here, and use it from baton-sdk in case it's possible, otherwise, this should be refactored here

func WrapErrorsWithRateLimitInfo(preferredCode codes.Code, resp *http.Response, errs ...error) error {
	st := status.New(preferredCode, resp.Status)

	description, err := ratelimit.ExtractRateLimitData(resp.StatusCode, &resp.Header)
	// Ignore any error extracting rate limit data
	if err == nil {
		st, _ = st.WithDetails(description)
	}

	if len(errs) == 0 {
		return st.Err()
	}

	allErrs := append([]error{st.Err()}, errs...)
	return errors.Join(allErrs...)
}

https://github.com/ConductorOne/baton-sdk/blob/main/pkg/uhttp/wrapper.go#L327-L342

Copy link
Author

Choose a reason for hiding this comment

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

Done!

}
}
return nil, "", nil, fmt.Errorf("google-workspace: can't get role assignment: %w", err)
return nil, "", nil, wrapGoogleApiErrorWithContext(err, "failed to list role assignments")

Choose a reason for hiding this comment

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

we should still check by status not found here

Copy link
Author

Choose a reason for hiding this comment

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

Done!

@mateoHernandez123 mateoHernandez123 force-pushed the mateoHernandez123/wrap-errors-with-grpc branch from 4d3e538 to c4adebc Compare December 1, 2025 19:46
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/connector/actions.go (1)

285-309: Potential nil dereference when checking transfers.NextPageToken.

If the API returns nil for transfers, the code at line 305 will panic when accessing transfers.NextPageToken. The nil check at line 289 only guards the iteration loop, not the pagination check.

-		if transfers != nil {
+		if transfers == nil {
+			break
+		}
+		{
 			for _, t := range transfers.DataTransfers {
 				// ... existing logic ...
 			}
 		}
 		if transfers.NextPageToken == "" {
 			break
 		}

Or more simply:

+		if transfers == nil || transfers.NextPageToken == "" {
-		if transfers.NextPageToken == "" {
 			break
 		}
♻️ Duplicate comments (1)
pkg/connector/error_helpers.go (1)

66-88: The errs variadic parameter is accepted but never used.

The function signature accepts errs ...error but the implementation discards them entirely, returning only st.Err(). This breaks the error chain—callers passing the original error won't be able to use errors.Is/errors.As for introspection.

Per the past review comment from luisina-santos, this should follow the baton-sdk pattern:

-	return st.Err()
+	if len(errs) == 0 {
+		return st.Err()
+	}
+	allErrs := append([]error{st.Err()}, errs...)
+	return errors.Join(allErrs...)

This preserves the gRPC status at the top level while maintaining the original error chain.

🧹 Nitpick comments (1)
pkg/connector/connector.go (1)

491-498: Consider using uhttp.WrapErrors for invalid domain configuration.

The domain validation error at line 497 indicates a configuration issue. Using codes.InvalidArgument would provide better gRPC semantics:

-		return nil, fmt.Errorf("domain '%s' is not a valid domain for customer '%s'", c.domain, c.customerID)
+		return nil, uhttp.WrapErrors(codes.InvalidArgument, fmt.Sprintf("domain '%s' is not a valid domain for customer '%s'", c.domain, c.customerID))

This aligns with similar validation errors elsewhere in the codebase.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d3e538 and c4adebc.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum and included by none
📒 Files selected for processing (8)
  • pkg/connector/actions.go (16 hunks)
  • pkg/connector/admin_event_feed.go (20 hunks)
  • pkg/connector/connector.go (13 hunks)
  • pkg/connector/error_helpers.go (1 hunks)
  • pkg/connector/group.go (12 hunks)
  • pkg/connector/role.go (13 hunks)
  • pkg/connector/usage_event_feed.go (9 hunks)
  • pkg/connector/user.go (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/connector/usage_event_feed.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-05T20:15:11.628Z
Learnt from: btipling
Repo: ConductorOne/baton-google-workspace PR: 56
File: pkg/connector/error_helpers.go:40-41
Timestamp: 2025-11-05T20:15:11.628Z
Learning: In the baton-google-workspace repository and baton-sdk, HTTP 429 (Too Many Requests) should be mapped to codes.Unavailable, not codes.ResourceExhausted, as per the project's established error handling conventions.

Applied to files:

  • pkg/connector/error_helpers.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: connector-test
🔇 Additional comments (16)
pkg/connector/error_helpers.go (1)

40-41: HTTP 429 correctly maps to codes.Unavailable.

This aligns with the baton-sdk convention where HTTP 429 (Too Many Requests) is mapped to codes.Unavailable rather than codes.ResourceExhausted. Based on learnings from this repository.

pkg/connector/role.go (2)

39-84: Error handling in List follows correct patterns.

  • Pagination errors use fmt.Errorf (internal connector issues that should alert)
  • API errors use wrapGoogleApiErrorWithContext (preserves gRPC codes)
  • SDK resource creation errors use fmt.Errorf (internal issues)

This aligns with the guidance that only HTTP request errors need gRPC wrapping.


160-193: Grant method error handling is well-structured.

The implementation correctly:

  • Uses uhttp.WrapErrors with appropriate codes for validation/precondition failures
  • Handles HTTP 409 (conflict) specially for idempotency
  • Falls back to wrapGoogleApiErrorWithContext for other API errors
pkg/connector/connector.go (1)

298-336: Service initialization error handling is well-structured.

The function appropriately maps errors to gRPC codes:

  • codes.Internal for HTTP client creation failures
  • codes.InvalidArgument for credential parsing issues
  • codes.Unavailable for service creation failures

This provides meaningful error semantics to callers.

pkg/connector/group.go (2)

40-109: Error handling in List correctly follows project guidelines.

Per btipling's guidance, pagination bag errors are left as plain errors (should alert as connector bugs), while API errors use wrapGoogleApiErrorWithContext. This is the correct pattern.


193-219: Grant method correctly handles conflict case with proper service.

The implementation uses groupMemberProvisioningService for both insert and conflict-recovery get operations, addressing the previous review concern about scope mismatches.

pkg/connector/actions.go (2)

23-61: Error handling in updateUserStatus follows correct patterns.

  • Validation errors use uhttp.WrapErrors(codes.InvalidArgument, ...)
  • Service retrieval uses fmt.Errorf (internal error)
  • API errors use wrapGoogleApiErrorWithContext

This is consistent with the PR's error wrapping approach.


353-391: Privacy levels validation is thorough and well-structured.

The function handles:

  • Missing argument (returns defaults)
  • Type validation (must be list)
  • Value validation (only "private" and "shared" allowed)
  • Deduplication
  • Empty list after filtering

All validation errors use codes.InvalidArgument appropriately.

pkg/connector/admin_event_feed.go (5)

17-18: LGTM on imports.

The added ctxzap and zap imports support the structured logging changes throughout this file.


48-68: Error wrapping patterns look correct.

The implementation correctly differentiates between internal errors (using fmt.Errorf for service initialization, token handling) and Google API errors (using wrapGoogleApiErrorWithContext at line 68). This preserves gRPC status codes for API failures while still providing context for internal errors.


265-268: Note: Double-wrapping may mask gRPC status, but impact is limited.

lookupGroup returns a gRPC-wrapped error from wrapGoogleApiErrorWithContext at line 434. Wrapping it again with fmt.Errorf here could strip the GRPCStatus. However, since this error is ultimately only logged at line 94 (and not propagated to gRPC clients), the impact is minimal for this use case.


378-391: Error handling in lookupUser is well-structured.

Good pattern: internal errors (directory service initialization) use fmt.Errorf, while Google API errors use wrapGoogleApiErrorWithContext. The 404 handling correctly avoids wrapping the not-found case as an error.


419-434: Consistent error handling in lookupGroup.

Mirrors the lookupUser pattern appropriately.

pkg/connector/user.go (3)

54-107: LGTM on List method error handling.

Error wrapping is consistent: wrapGoogleApiErrorWithContext for the Google API call (line 85), fmt.Errorf for internal operations (pagination, resource building). This correctly preserves gRPC status codes for API errors.


227-269: LGTM on Get method error handling.

Correctly uses wrapGoogleApiErrorWithContext for the API call at line 234 and fmt.Errorf for internal operations.


304-319: LGTM on extraction error handling.

Appropriate use of fmt.Errorf for internal JSON extraction errors.

Comment on lines 486 to 489
err := o.userProvisioningService.Users.Delete(resourceId.Resource).Context(ctx).Do()
if err != nil {
return nil, err
return nil, wrapGoogleApiErrorWithContext(err, fmt.Sprintf("failed to get user in Delete: %s", resourceId.Resource))
}

_, err = o.userService.Users.Update(resourceId.Resource, &admin.User{
Suspended: true,
}).Context(ctx).Do()

if err != nil {
return nil, wrapGoogleApiErrorWithContext(err, fmt.Sprintf("failed to suspend user in Delete: %s", resourceId.Resource))
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Incorrect error message and suspicious delete-then-suspend logic.

Two issues in the Delete method:

  1. Wrong error message (line 488): The message says "failed to get user" but the operation is Delete(). Should say "failed to delete user".

  2. Suspicious logic (lines 491-496): After successfully deleting a user, the code attempts to suspend them. Suspending a deleted user will likely fail or is a no-op. Was the intended order suspend-then-delete, or should the suspend call be removed entirely?

Apply this diff to fix the error message (the logic question requires clarification):

 	err := o.userProvisioningService.Users.Delete(resourceId.Resource).Context(ctx).Do()
 	if err != nil {
-		return nil, wrapGoogleApiErrorWithContext(err, fmt.Sprintf("failed to get user in Delete: %s", resourceId.Resource))
+		return nil, wrapGoogleApiErrorWithContext(err, fmt.Sprintf("failed to delete user: %s", resourceId.Resource))
 	}

Please clarify the intended behavior: should the user be suspended before deletion, or is the suspend call unnecessary?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
err := o.userProvisioningService.Users.Delete(resourceId.Resource).Context(ctx).Do()
if err != nil {
return nil, err
return nil, wrapGoogleApiErrorWithContext(err, fmt.Sprintf("failed to get user in Delete: %s", resourceId.Resource))
}
_, err = o.userService.Users.Update(resourceId.Resource, &admin.User{
Suspended: true,
}).Context(ctx).Do()
if err != nil {
return nil, wrapGoogleApiErrorWithContext(err, fmt.Sprintf("failed to suspend user in Delete: %s", resourceId.Resource))
}
err := o.userProvisioningService.Users.Delete(resourceId.Resource).Context(ctx).Do()
if err != nil {
return nil, wrapGoogleApiErrorWithContext(err, fmt.Sprintf("failed to delete user: %s", resourceId.Resource))
}
_, err = o.userService.Users.Update(resourceId.Resource, &admin.User{
Suspended: true,
}).Context(ctx).Do()
if err != nil {
return nil, wrapGoogleApiErrorWithContext(err, fmt.Sprintf("failed to suspend user in Delete: %s", resourceId.Resource))
}
🤖 Prompt for AI Agents
In pkg/connector/user.go around lines 486 to 497, the Delete method has an
incorrect error message and questionable suspend-after-delete logic: change the
error returned when the Delete() call fails to read "failed to delete user"
(including the resourceId) instead of "failed to get user"; then decide and
apply one of two fixes for the suspend call — either (A) if the intent is to
suspend before removing, move the Users.Update(...) suspend call to run before
Users.Delete(...) and keep its error handling (adjust messages to "failed to
suspend user in Delete"), or (B) if suspension is unnecessary prior to deletion,
remove the suspend call entirely; update corresponding error messages to match
the actual operations and ensure ctx is used consistently; confirm which
behavior (suspend-then-delete or no suspend) is intended.

@mateoHernandez123 mateoHernandez123 force-pushed the mateoHernandez123/wrap-errors-with-grpc branch from c4adebc to 17f4658 Compare December 1, 2025 20:04
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
pkg/connector/connector.go (1)

338-347: Return the original error from newGWSAdminServiceForScopes without wrapping

newGWSAdminServiceForScopes returns status-bearing errors (via uhttp.WrapErrors). Wrapping with fmt.Errorf("failed to create report service: %w", err) masks the gRPC status code, causing callers to receive codes.Unknown instead of the intended status. The getService function already follows the correct pattern of returning such errors directly.

Change line 344 from:

return nil, fmt.Errorf("failed to create report service: %w", err)

to:

return nil, err

This preserves the status code information while still propagating the error message from newGWSAdminServiceForScopes.

pkg/connector/group.go (1)

193-219: Fix entitlement constant in Grant method (line 216)

The grant is constructed with roleMemberEntitlement, but should use groupMemberEntitlement to match the entitlement defined at line 24, and to align with the entitlements used in GroupMembers (line 116) and Revoke (line 156). Replace roleMemberEntitlement with groupMemberEntitlement on line 216.

♻️ Duplicate comments (1)
pkg/connector/actions.go (1)

304-309: Previous nil-check concern remains.

The check at line 289 (if transfers != nil) protects the iteration, but line 305 accesses transfers.NextPageToken outside that guard. This was flagged in a previous review and marked out of scope for this PR.

🧹 Nitpick comments (3)
pkg/connector/usage_event_feed.go (1)

208-214: Consider using uhttp.WrapErrors for parameter validation.

For consistency with the pattern in actions.go, these validation errors could use uhttp.WrapErrors(codes.InvalidArgument, ...) to provide structured gRPC codes. However, since these errors are logged and don't halt the feed (line 179-181), the current approach is acceptable.

pkg/connector/error_helpers.go (1)

56-64: Defensive handling for unexpected 2xx codes.

This fallback handles the edge case where a googleapi.Error has a 2xx status code (which shouldn't normally occur). The "unexpected status code" message clearly signals this abnormal condition. Consider logging this case for debugging.

+// This path should rarely be hit - a googleapi.Error with a 2xx code is unusual
 contextErr := err
 if contextMsg != "" {
     contextErr = errors.Join(fmt.Errorf("%s", contextMsg), err)
 }
pkg/connector/user.go (1)

396-479: CreateAccount: consider mapping obvious input/precondition errors to specific gRPC codes

The structure here is good overall:

  • The Google API Users.Insert failure is wrapped with wrapGoogleApiErrorWithContext(err, "failed to create account").
  • userResource construction failures are fmt.Errorf("failed to create user resource: %w", err), correctly treated as connector bugs.

A few of the earlier failures are clearly client/configuration errors but are currently plain fmt.Errorf:

  • Missing email, given_name, or family_name in accountInfo.Profile.
  • credentialOptions == nil.
  • o.userProvisioningService == nil (missing required scope).

If you want the SDK to distinguish these from internal errors and treat them as non-alerting client/config problems, you might consider using uhttp.WrapErrors with more specific codes:

@@
 	email, ok := pMap["email"].(string)
 	if !ok || email == "" {
-		return nil, nil, nil, fmt.Errorf("email not found in profile")
+		return nil, nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "email not found in profile", nil)
 	}
@@
 	givenName, ok := pMap["given_name"].(string)
 	if !ok || givenName == "" {
-		return nil, nil, nil, fmt.Errorf("given_name not found in profile")
+		return nil, nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "given_name not found in profile", nil)
 	}
@@
 	familyName, ok := pMap["family_name"].(string)
 	if !ok || familyName == "" {
-		return nil, nil, nil, fmt.Errorf("family_name not found in profile")
+		return nil, nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "family_name not found in profile", nil)
 	}
@@
 	if credentialOptions == nil {
-		return nil, nil, nil, fmt.Errorf("credentialOptions cannot be nil")
+		return nil, nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "credentialOptions cannot be nil", nil)
 	}
@@
 	if o.userProvisioningService == nil {
-		return nil, nil, nil, fmt.Errorf("user provisioning service not available - requires %s scope", admin.AdminDirectoryUserScope)
+		return nil, nil, nil, uhttp.WrapErrors(
+			codes.FailedPrecondition,
+			fmt.Sprintf("user provisioning service not available - requires %s scope", admin.AdminDirectoryUserScope),
+			nil,
+		)
 	}

This would align CreateAccount’s behavior with the patterns you’ve already established in group.Grant/Revoke for similar precondition and argument issues.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4adebc and 17f4658.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum and included by none
📒 Files selected for processing (8)
  • pkg/connector/actions.go (16 hunks)
  • pkg/connector/admin_event_feed.go (20 hunks)
  • pkg/connector/connector.go (13 hunks)
  • pkg/connector/error_helpers.go (1 hunks)
  • pkg/connector/group.go (12 hunks)
  • pkg/connector/role.go (13 hunks)
  • pkg/connector/usage_event_feed.go (9 hunks)
  • pkg/connector/user.go (10 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-05T20:15:11.628Z
Learnt from: btipling
Repo: ConductorOne/baton-google-workspace PR: 56
File: pkg/connector/error_helpers.go:40-41
Timestamp: 2025-11-05T20:15:11.628Z
Learning: In the baton-google-workspace repository and baton-sdk, HTTP 429 (Too Many Requests) should be mapped to codes.Unavailable, not codes.ResourceExhausted, as per the project's established error handling conventions.

Applied to files:

  • pkg/connector/error_helpers.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: connector-test
🔇 Additional comments (23)
pkg/connector/usage_event_feed.go (1)

147-155: LGTM on Google API error wrapping.

The wrapGoogleApiErrorWithContext is correctly used here without additional fmt.Errorf wrapping, preserving the gRPC status code as intended by this PR.

pkg/connector/admin_event_feed.go (2)

66-69: LGTM on Google API error wrapping.

The wrapGoogleApiErrorWithContext is correctly used for the admin activities API call, preserving the gRPC status code.


381-392: Appropriate handling of not-found vs other errors.

The pattern correctly distinguishes between expected 404s (user/group deleted) and actual API errors, using wrapGoogleApiErrorWithContext only for unexpected failures.

pkg/connector/actions.go (2)

23-32: Consistent use of uhttp.WrapErrors for input validation.

The structured error wrapping with codes.InvalidArgument provides proper gRPC semantics for validation failures.

Note: The defensive nil check for args.Fields["resource_id"] before calling GetKind() was discussed in a previous review and marked as out of scope for this PR.


47-50: LGTM on Google API error wrapping.

The wrapGoogleApiErrorWithContext correctly wraps the API error with operation-specific context.

pkg/connector/error_helpers.go (3)

14-21: Well-designed error helper with proper gRPC status preservation.

The helper correctly:

  1. Uses errors.As to safely extract googleapi.Error
  2. Returns original error unchanged for non-Google API errors
  3. Maps HTTP codes to appropriate gRPC codes

40-41: 429 mapping follows project conventions.

The mapping of HTTP 429 to codes.Unavailable aligns with the baton-sdk's established error handling pattern. Based on learnings from this PR.


82-93: Correctly follows baton-sdk pattern for rate limit handling.

The implementation properly:

  1. Guards against nil description before calling WithDetails
  2. Uses errors.Join to preserve the original error chain (addressing the past review concern)
  3. Returns st.Err() directly when no additional errors are provided
pkg/connector/role.go (3)

117-127: Appropriate handling of NotFound in role assignments.

The code correctly returns an empty list for 404 (role not found) and wraps other errors with wrapGoogleApiErrorWithContext. This aligns with the expected behavior for listing grants on potentially deleted roles.


167-178: Good validation pattern with proper gRPC codes.

The use of uhttp.WrapErrors with codes.FailedPrecondition for service availability and codes.InvalidArgument for principal validation provides clear, actionable error semantics.


234-237: LGTM on role retrieval error wrapping.

The wrapGoogleApiErrorWithContext correctly wraps the API error with the role ID context for debugging.

pkg/connector/connector.go (4)

298-336: Good use of uhttp.WrapErrors with explicit gRPC codes in newGWSAdminServiceForScopes

The three failure branches here (HTTP client creation, JWT config parsing, service construction) are now consistently wrapped with uhttp.WrapErrors and mapped to codes.Internal, codes.InvalidArgument, and codes.Unavailable, which matches the intent of surfacing HTTP/SDK failures to the SDK with machine-checkable status while still carrying the underlying error.

No changes needed from my side.


552-562: Cache type-mismatch error messaging looks good

The new error returned when a cached entry exists but has the wrong type:

return nil, fmt.Errorf("cache entry for scope %s exists, but is not of type %s", scope, reflect.TypeOf(service))

is appropriate as a plain error (not uhttp-wrapped) since it indicates an internal connector bug. It should surface loudly and not be down-classified as a client or transient issue. No changes needed.


568-612: Service cache error wrapping is appropriate

The new wrappers in getService:

  • fmt.Errorf("failed to get service from cache: %w", err)
  • fmt.Errorf("failed to get upgraded service from cache: %w", err)

only wrap the internal cache-type errors produced by getFromCache. These are connector bugs rather than HTTP/SDK failures, so it’s correct to keep them as plain errors (no gRPC code mapping) and let them alert.

The recursive upgrade path (getServiceupgradeScopegetService with upgraded scope) still terminates safely and doesn’t introduce new concurrency issues beyond what already existed.


632-665: Action registration error context improvements

Wrapping RegisterAction failures with contextual messages like:

fmt.Errorf("failed to register update_user_status action: %w", err)

for each action manager registration point makes startup failures much easier to trace, while the original error is preserved via %w. Since these are not HTTP/SDK errors surfaced to gRPC clients but rather initialization failures, treating them as plain errors is consistent with the rest of the file.

Looks good.

pkg/connector/group.go (4)

40-109: List: clean separation between connector bugs and Google API failures

  • Pagination token unmarshalling and next-token generation now return plain fmt.Errorf errors with clear context, which is appropriate since those indicate connector bugs.
  • The Google API call error is returned via wrapGoogleApiErrorWithContext(err, "failed to list groups") without an outer fmt.Errorf wrapper, so the status-bearing helper remains the top-level error and gRPC sees the intended code.
  • Logging for groups with missing IDs is kept at log level (no error returned), which avoids spurious sync failures on bad items.

This matches the error-handling guidelines you’ve been applying elsewhere.


123-166: Grants: consistent error classification and Google API wrapping

  • Pagination token unmarshal and next-page token errors now use descriptive fmt.Errorf messages, correctly treated as connector issues.
  • Member listing failures are wrapped with wrapGoogleApiErrorWithContext(err, "failed to list group members"), keeping the helper’s gRPC status outermost.
  • Failures to build user resource IDs (NewResourceID) are surfaced as plain internal errors, which is appropriate.

Overall, this function now cleanly distinguishes between Google API failures and internal pagination/ID-building bugs.


221-247: Revoke: appropriate precondition handling and Google API wrapping

  • Missing provisioning service and wrong principal type now return uhttp.WrapErrors with codes.FailedPrecondition and codes.InvalidArgument, respectively, which is consistent with Grant.
  • For delete failures, non-404 Google API errors are surfaced via wrapGoogleApiErrorWithContext(err, "failed to delete group member"), keeping the Google API-aware status intact.
  • The 404 path still logs and returns success to maintain idempotent semantics on double-revokes.

This is a solid improvement in both correctness and observability.


249-279: Get: preserves Google API status while isolating connector bugs

  • Failures from o.groupService.Groups.Get(...).Do() are now wrapped with wrapGoogleApiErrorWithContext(err, fmt.Sprintf("failed to retrieve group: %s", resourceId.Resource)), so callers see a status-aware error.
  • A group with an empty ID is logged and treated as non-existent, which avoids returning malformed resources.
  • NewGroupResource failures are surfaced via plain fmt.Errorf("failed to create group resource in Get: %w", err), correctly indicating an internal connector issue rather than an API problem.

No changes needed here.

pkg/connector/user.go (4)

54-108: List: correct split between connector bugs and Google API failures

  • Pagination token unmarshal and next-page token failures now use fmt.Errorf with clear context, correctly treating them as connector bugs.
  • The user list API call failure is returned via wrapGoogleApiErrorWithContext(err, "failed to list users") without additional wrapping, preserving the helper’s gRPC status.
  • Logging for users with missing IDs is kept as a log-and-skip behavior instead of failing the whole page.

This matches the intended error-handling pattern.


227-272: Get: status-preserving API errors and reasonable filter behavior

  • Failures from Users.Get(...).Do() now go through wrapGoogleApiErrorWithContext(err, fmt.Sprintf("failed to retrieve user: %s", resourceId.Resource)), so gRPC-aware status codes are preserved.
  • Domain and customerId mismatches are handled by logging an info message and returning (nil, nil, nil), which is a reasonable way to treat out-of-scope users.
  • JSON-shape issues when extracting organizations and user resource build failures are surfaced via fmt.Errorf, correctly indicating internal connector issues rather than API problems.

Looks solid.


274-383: userResource: internal extraction failures correctly treated as connector bugs

Failures when extracting POSIX accounts or external IDs now return plain fmt.Errorf with clear messages:

  • "failed to extract posix accounts: %w"
  • "failed to extract external IDs: %w"

Given these come from local JSON marshal/unmarshal of user.PosixAccounts and user.ExternalIds, treating them as hard connector errors (rather than downgrading them via uhttp) is appropriate—they indicate a schema/assumption mismatch, not a user or transient API issue.

No changes needed.


481-492: Delete: improved error semantics and status preservation

  • The missing provisioning service case now returns a clear fmt.Errorf("user provisioning service not available - requires %s scope", ...), correctly marking it as a configuration/precondition failure.
  • The Google API Users.Delete failure is routed through wrapGoogleApiErrorWithContext(err, fmt.Sprintf("failed to delete user: %s", resourceId.Resource)), with a corrected and specific message, so gRPC sees the appropriate status code for API errors.

This is a clear improvement over the prior “failed to get user” wording and non-status-aware error.

Comment on lines 369 to 377
func (c *GoogleWorkspace) Metadata(ctx context.Context) (*v2.ConnectorMetadata, error) {
_, err := c.Validate(ctx)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to validate connector: %w", err)
}
primaryDomain, err := c.getPrimaryDomain(ctx)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to get primary domain: %w", err)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the connector.go file
fd -t f "connector.go" pkg/connector/

# Get line count to determine readability
wc -l pkg/connector/connector.go

Repository: ConductorOne/baton-google-workspace

Length of output: 136


🏁 Script executed:

# Read the specific line ranges mentioned in the review
echo "=== Lines 369-377 (Metadata function) ==="
sed -n '369,377p' pkg/connector/connector.go

echo -e "\n=== Lines 433-445 ==="
sed -n '433,445p' pkg/connector/connector.go

echo -e "\n=== Lines 447-457 ==="
sed -n '447,457p' pkg/connector/connector.go

echo -e "\n=== Lines 469-483 ==="
sed -n '469,483p' pkg/connector/connector.go

echo -e "\n=== Lines 485-501 ==="
sed -n '485,501p' pkg/connector/connector.go

# Also look for context around these functions
echo -e "\n=== Searching for function definitions ==="
rg -n "func \(c \*GoogleWorkspace\)" pkg/connector/connector.go | head -20

Repository: ConductorOne/baton-google-workspace

Length of output: 3403


🏁 Script executed:

# Search for wrapGoogleApiErrorWithContext definition
echo "=== Searching for wrapGoogleApiErrorWithContext ==="
rg -n "wrapGoogleApiErrorWithContext|func wrapGoogleApiError" pkg/connector/

# Search for getDirectoryService implementation
echo -e "\n=== getDirectoryService implementation ==="
sed -n '350,365p' pkg/connector/connector.go

# Search for newGWSAdminServiceForScopes
echo -e "\n=== Searching for newGWSAdminServiceForScopes ==="
rg -n "newGWSAdminServiceForScopes" pkg/connector/

# Search for uhttp usage
echo -e "\n=== Searching for uhttp package ==="
rg -n "uhttp" pkg/connector/

Repository: ConductorOne/baton-google-workspace

Length of output: 10526


🏁 Script executed:

# Read error_helpers.go to see wrapGoogleApiErrorWithContext
echo "=== error_helpers.go content ==="
cat -n pkg/connector/error_helpers.go

# Read newGWSAdminServiceForScopes implementation
echo -e "\n=== newGWSAdminServiceForScopes (lines 298-335) ==="
sed -n '298,335p' pkg/connector/connector.go

Repository: ConductorOne/baton-google-workspace

Length of output: 5583


🏁 Script executed:

# Search for how errors are handled in gRPC context
echo "=== Searching for error handling patterns ==="
rg -n "status.FromError|GRPCStatus" pkg/

# Look for similar error wrapping patterns in other connectors or SDK
echo -e "\n=== Searching for error chain unwrapping ==="
rg -n "errors.Unwrap|errors.As.*Code|errors.Is" pkg/connector/ | head -20

# Check if there's a test file that shows the error wrapping behavior
echo -e "\n=== Looking for test files ==="
fd -t f "*_test.go" pkg/connector/

Repository: ConductorOne/baton-google-workspace

Length of output: 600


🏁 Script executed:

# Let's understand the error flow better by looking at how errors are returned
echo "=== Checking fetchDomains full implementation ==="
sed -n '447,468p' pkg/connector/connector.go

# Check if there are any comments or documentation about error handling
echo -e "\n=== Searching for comments about error wrapping ==="
rg -n "fmt.Errorf|error.*wrap" pkg/connector/connector.go | head -30

Repository: ConductorOne/baton-google-workspace

Length of output: 2345


🌐 Web query:

gRPC Go status.FromError fmt.Errorf wrapping error chain preservation

💡 Result:

Short answer: status.FromError does not break Go's %w wrapping — use fmt.Errorf("%w", err) to preserve the error chain (so errors.Is / errors.As work). However, FromError treats wrapped gRPC status-bearing errors specially: if any error in the chain implements GRPCStatus() *status.Status it returns that Status and the message it reports will include the full err.Error() (i.e., the whole wrapped message chain), not only the inner Status message. [1][2]

Example (preserves chain):

  • wrap: wrapped := fmt.Errorf("handler: %w", grpcErr)
  • convert: st, ok := status.FromError(wrapped) // ok true if grpcErr (or a wrapped error) had GRPCStatus()
  • errors.Is(wrapped, grpcErr) and errors.As(wrapped, &SomeType) still work because %w preserves Unwrap(). [2][1]

References

  1. google.golang.org/grpc/status package docs (FromError behavior). [1]
  2. Go 1.13 errors blog (fmt.Errorf %w and errors.Is/As / Unwrap). [2]

🏁 Script executed:

# Search for how errors are handled at the gRPC boundary
echo "=== Looking for gRPC error handling ==="
rg -n "status\.|codes\." pkg/connector/ | head -20

# Check if there are integration tests
echo -e "\n=== Looking for test or integration files ==="
fd -type f -name "*.go" | grep -i test | head -10

# Check the actual error flow - look for where errors are returned to gRPC
echo -e "\n=== Checking ResourceSyncers and other entry points ==="
sed -n '507,530p' pkg/connector/connector.go

Repository: ConductorOne/baton-google-workspace

Length of output: 4189


🏁 Script executed:

# Check the full context around the error wrapping pattern
echo "=== Lines 14-20 in error_helpers.go with context ==="
sed -n '14,20p' pkg/connector/error_helpers.go

# Check lines 74-78 where the comment about "ONLY place" appears
echo -e "\n=== Lines 74-78 (critical comment) ==="
sed -n '74,78p' pkg/connector/error_helpers.go

# Search for any README or documentation
echo -e "\n=== Looking for README or docs ==="
fd -type f \( -name "README*" -o -name "*.md" \) | head -10

# Check git history to understand intent
echo -e "\n=== Recent commits mentioning error or status ==="
git log --oneline --all -20 2>/dev/null || echo "Git history unavailable"

Repository: ConductorOne/baton-google-workspace

Length of output: 2091


🏁 Script executed:

# Verify the comment about "ONLY place" context
echo "=== Full error_helpers.go relevant sections ==="
sed -n '66,94p' pkg/connector/error_helpers.go

# Check if there are other connectors in baton-sdk that follow this pattern
echo -e "\n=== Searching for similar patterns in other files ==="
rg -n "This is the ONLY place" pkg/

# Look at how uhttp.WrapErrors is designed
echo -e "\n=== Checking uhttp usage pattern ==="
rg -B2 "uhttp.WrapErrors" pkg/connector/connector.go | head -20

Repository: ConductorOne/baton-google-workspace

Length of output: 1818


Remove unnecessary fmt.Errorf wrapping layers that bypass designed error context points

The codebase explicitly documents in error_helpers.go line 75: "This is the ONLY place we should add context, to ensure the gRPC status is preserved." However, several functions in Metadata, getPrimaryDomain, getDomains, fetchDomains, and Validate re-wrap errors from wrapGoogleApiErrorWithContext and uhttp.WrapErrors (which already carry gRPC status codes) with additional fmt.Errorf layers. This violates the established pattern and creates redundant error message nesting.

Since wrapGoogleApiErrorWithContext and uhttp.WrapErrors are designed as the single context-adding points where gRPC status is properly preserved, subsequent layers should propagate errors directly:

  • Metadata (lines 372, 376): Remove fmt.Errorf wrapping; return errors directly from Validate and getPrimaryDomain
  • getPrimaryDomain (line 442): Return error directly from fetchDomains
  • fetchDomains (line 450): Return error directly from getDirectoryService
  • getDomains (line 479): Return error directly from fetchDomains
  • Validate (line 488): Return error directly from getDomains
  • Validate (line 497): The domain validation error may benefit from uhttp.WrapErrors(codes.InvalidArgument, ...) for consistency

Applies to: 369-377, 433-445, 447-457, 469-483, 485-501

🤖 Prompt for AI Agents
In pkg/connector/connector.go around lines 369-377, 433-445, 447-457, 469-483,
and 485-501: the code wraps errors returned from wrapGoogleApiErrorWithContext
and uhttp.WrapErrors with additional fmt.Errorf layers which breaks the
single-point-of-context rule in error_helpers.go; remove the fmt.Errorf wrapping
and return errors directly from Validate and getPrimaryDomain (lines ~372,
~376), from getPrimaryDomain returning fetchDomains (line ~442), from
fetchDomains returning getDirectoryService (line ~450), from getDomains
returning fetchDomains (lines ~479), and from Validate returning getDomains
(lines ~488); keep the single context-addition points only
(wrapGoogleApiErrorWithContext / uhttp.WrapErrors) and, where domain validation
is performed (around line ~497), replace the current error creation with
uhttp.WrapErrors(codes.InvalidArgument, ...) to preserve consistent gRPC status
semantics.

gerr := &googleapi.Error{}
if errors.As(err, &gerr) {
// Return no grants if the group no longer exists. This might happen if the group is deleted during a sync.
if gerr.Code == http.StatusNotFound {
Copy link
Contributor

Choose a reason for hiding this comment

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

we should keep this.

Copy link
Author

Choose a reason for hiding this comment

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

Done!

@mateoHernandez123 mateoHernandez123 force-pushed the mateoHernandez123/wrap-errors-with-grpc branch from 17f4658 to 15b397a Compare December 2, 2025 14:33
@mateoHernandez123 mateoHernandez123 force-pushed the mateoHernandez123/wrap-errors-with-grpc branch from 15b397a to a2491e2 Compare December 2, 2025 14:40
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
pkg/connector/actions.go (1)

23-32: Defensively guard args.Fields[...] before calling GetKind()

Across these handlers you still call args.Fields["key"].GetKind() directly; if the key is absent or the value is nil, this will panic instead of returning a clean InvalidArgument error. This is the same issue previously noted and is worth fixing here.

A safe pattern (example for "resource_id") would be:

- guidField, ok := args.Fields["resource_id"].GetKind().(*structpb.Value_StringValue)
- if !ok {
-   return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "missing resource ID")
- }
+ v, present := args.Fields["resource_id"]
+ if !present || v == nil {
+   return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "missing resource ID")
+ }
+ guidField, ok := v.GetKind().(*structpb.Value_StringValue)
+ if !ok {
+   return nil, nil, uhttp.WrapErrors(codes.InvalidArgument, "missing resource ID")
+ }

and similarly for is_suspended, user_id, new_primary_email, target_resource_id.

Also applies to: 63-68, 107-111, 151-158, 209-216, 241-248

🧹 Nitpick comments (2)
pkg/connector/connector.go (1)

447-501: Consider surfacing invalid configured domain as a typed InvalidArgument

fetchDomains correctly propagates directory‑service errors and wraps the Google API List failure via wrapGoogleApiErrorWithContext, and getDomains/caching logic look fine. For the Validate failure case:

return nil, fmt.Errorf("domain '%s' is not a valid domain for customer '%s'", c.domain, c.customerID)

you might consider using uhttp.WrapErrors(codes.InvalidArgument, ...) so misconfigured domains are distinguishable from transient/internal failures at the gRPC layer, similar to how other configuration/permission issues are surfaced elsewhere in this PR. The new cache/type‑mismatch and action‑registration error messages via fmt.Errorf look good as plain connector errors.

Also applies to: 552-584, 632-662

pkg/connector/user.go (1)

396-479: Optional: surface CreateAccount profile validation issues as InvalidArgument

In CreateAccount, missing or empty email, given_name, and family_name currently return plain fmt.Errorf(...) errors, which will likely surface as generic gRPC failures rather than structured InvalidArgument. Given these are client/profile issues rather than connector bugs, you may want to switch them to uhttp.WrapErrors(codes.InvalidArgument, "...") (and similarly for the “credentialOptions cannot be nil” case if that can be triggered by callers) to match how other validation errors are handled in actions.go.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 17f4658 and 15b397a.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum and included by none
📒 Files selected for processing (8)
  • pkg/connector/actions.go (16 hunks)
  • pkg/connector/admin_event_feed.go (20 hunks)
  • pkg/connector/connector.go (10 hunks)
  • pkg/connector/error_helpers.go (1 hunks)
  • pkg/connector/group.go (12 hunks)
  • pkg/connector/role.go (13 hunks)
  • pkg/connector/usage_event_feed.go (9 hunks)
  • pkg/connector/user.go (10 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/connector/error_helpers.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: connector-test
🔇 Additional comments (7)
pkg/connector/usage_event_feed.go (1)

7-7: Error wrapping and context in usage event feed look consistent and safe

The added fmt.Errorf contexts for token (un)marshalling, latest-event parsing, user trait creation, and cursor marshaling are appropriate for internal bugs, while the Google API Do() path correctly uses wrapGoogleApiErrorWithContext so external call failures carry the expected status and context; newV2Event’s parameter checks also fail per‑event rather than taking down the whole feed. No issues spotted here.

Also applies to: 80-115, 122-200, 208-214

pkg/connector/admin_event_feed.go (1)

17-23: Admin event feed error handling is consistent with the new helpers

ListEvents now cleanly distinguishes between internal issues (token parsing, cursor marshaling, time parsing) using contextual fmt.Errorf, and Google API failures using wrapGoogleApiErrorWithContext; lookup helpers correctly special‑case 404s while routing other API errors through the helper. Group/user event constructors add context with plain fmt.Errorf while callers log and continue, so a single bad event doesn’t break the feed. This all looks aligned with the desired error‑wrapping strategy.

Also applies to: 42-135, 137-214, 251-328, 366-450

pkg/connector/actions.go (1)

3-15: uhttp / Google API error wrapping in actions is well‑structured

Argument validation errors for IDs, booleans, and privacy levels now use uhttp.WrapErrors with codes.InvalidArgument, and Data Transfer API failures are routed through wrapGoogleApiErrorWithContext, while non‑HTTP failures (password generation, internal building) stay as plain fmt.Errorf. That separation matches the guidance on when to attach gRPC codes vs. leaving connector bugs as generic errors.

Also applies to: 149-205, 207-268, 270-276, 336-391

pkg/connector/connector.go (1)

298-336: Service construction errors are mapped to gRPC codes in a sensible way

newGWSAdminServiceForScopes now classifies failures as Internal (HTTP client), InvalidArgument (JWT config/credentials), and Unavailable (service construction), which is a good fit for how these failures surface. getReportService adds user‑facing context with fmt.Errorf while still preserving the underlying status from uhttp.WrapErrors. No issues here.

Also applies to: 338-348

pkg/connector/role.go (1)

39-45: Role CRUD error handling and gRPC mapping look solid

Role listing and grants now treat pagination and resource‑construction failures as plain connector errors, while Google API failures are wrapped with wrapGoogleApiErrorWithContext; Grants, Grant, and Revoke correctly special‑case 404/409 conditions and use uhttp.WrapErrors for precondition and argument issues. The Get path is similarly consistent. This matches the intended error‑wrapping and idempotency behavior.

Also applies to: 57-82, 98-149, 167-200, 202-225, 230-251

pkg/connector/group.go (1)

40-45: Group listing and membership error paths align with the new strategy

Group List/Grants now leave pagination and construction problems as plain fmt.Errorf connector bugs, while group/membership Google API failures consistently go through wrapGoogleApiErrorWithContext and, where appropriate, uhttp.WrapErrors (e.g., NotFound, FailedPrecondition, InvalidArgument). The conflict‑retry path in Grant uses the member‑scoped service and cleanly distinguishes “already a member” from real failures. Looks good.

Also applies to: 68-71, 83-107, 123-169, 199-225, 227-253, 255-283

pkg/connector/user.go (1)

54-60: User List/Get/Delete and trait building use the helpers appropriately

User List and Get now distinguish pagination/trait‑building problems (plain fmt.Errorf with context) from Google Admin API failures, which go through wrapGoogleApiErrorWithContext; domain/customer‑mismatch cases correctly log and return nil, and Delete uses the helper for API errors while keeping provisioning‑service absence as a plain configuration error. The new userResource extraction errors for POSIX accounts and external IDs are also appropriately treated as internal connector bugs.

Also applies to: 83-105, 230-269, 304-320, 346-359, 481-489

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants