Skip to content

Base fga pr for Go sdk#511

Draft
swaroopAkkineniWorkos wants to merge 12 commits intomainfrom
ENT-5353-base-fga-for-go-sdk
Draft

Base fga pr for Go sdk#511
swaroopAkkineniWorkos wants to merge 12 commits intomainfrom
ENT-5353-base-fga-for-go-sdk

Conversation

@linear
Copy link

linear bot commented Mar 6, 2026

@swaroopAkkineniWorkos swaroopAkkineniWorkos changed the title Base fga pr for Go sdk feat: Base fga pr for Go sdk Mar 6, 2026
Apply gofmt-aligned spacing in CreateAuthorizationResourceOpts so CI format/diff checks pass.

Made-with: Cursor
@swaroopAkkineniWorkos
Copy link
Contributor Author

@greptile review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 9, 2026

Greptile Summary

This PR introduces the foundational scaffolding for the FGA (Fine-Grained Authorization) Go SDK package (pkg/authorization), along with a shared pkg/common/order.go type. It defines the full public API surface — all types, request/response structs, and 28 client methods — but every client method is a stub returning errors.New("not implemented"). Actual implementations are deferred to the follow-up PRs listed in the description (#512#521).

Key concerns found during review:

  • All 28 methods are unimplemented stubs — the entire authorization package is currently non-functional. Care should be taken that this isn't exposed as a usable public API surface until implementations land.
  • ListEnvironmentRolesResponse and ListOrganizationRolesResponse are missing ListMetadata — every other list response struct includes pagination metadata; these two don't, which may prevent callers from paginating through results.
  • Description *string in all update opts lacks omitempty — unlike Name *string (which has omitempty), a nil Description will be serialized as JSON null, silently clearing the field on update. This is likely unintentional.
  • PermissionSlug missing omitempty in three list opts structsListResourcesForMembershipOpts, ListMembershipsForResourceOpts, and ListMembershipsForResourceByExternalIdOpts will append permission_slug= to the URL even when the field is empty.
  • RemoveEnvironmentRolePermission is absent — there is a RemoveOrganizationRolePermission but no equivalent for environment roles, creating an asymmetric API surface.
  • client_test.go is empty — no tests are present; this is expected for a base PR but should be addressed in follow-up work.

Confidence Score: 2/5

  • Not safe to merge as a user-facing release — all methods are stubs, and several structural API design issues should be resolved before implementations land in follow-up PRs.
  • The PR is explicitly a scaffolding/base PR with no functional implementation. While the type definitions and structure are largely sound, there are multiple correctness issues in the data model (missing ListMetadata, inconsistent omitempty, missing RemoveEnvironmentRolePermission) that should be fixed before implementations are written on top of them — fixing them later would require coordinated changes across all follow-up PRs. Score of 2 reflects the non-functional state and the design issues identified.
  • pkg/authorization/client.go needs the most attention: fix the missing ListMetadata on two response types, add omitempty to PermissionSlug fields, and clarify the Description serialization semantics before follow-up implementation PRs build on this foundation.

Important Files Changed

Filename Overview
pkg/authorization/client.go Large new file defining all FGA client types, opts, and 28 stub methods — every method returns errors.New("not implemented"). Contains structural issues: missing ListMetadata on two list response types, PermissionSlug without omitempty in three opts structs, inconsistent Description JSON tag (no omitempty unlike Name), and a missing RemoveEnvironmentRolePermission counterpart.
pkg/authorization/authorization.go New package-level facade file delegating all 28 FGA operations to DefaultClient. Standard WorkOS SDK pattern; no issues found here beyond those in client.go.
pkg/authorization/client_test.go Stub test file containing only the package declaration — no tests are present. Expected to be filled in by follow-up PRs referenced in the description.
pkg/common/order.go New shared Order type ("asc" / "desc") added to the common package; straightforward and consistent with typical SDK patterns.

Class Diagram

%%{init: {'theme': 'neutral'}}%%
classDiagram
    class Client {
        +APIKey string
        +HTTPClient retryablehttp.HttpClient
        +Endpoint string
        +JSONEncode func
        -once sync.Once
        +init()
        +CreateEnvironmentRole()
        +ListEnvironmentRoles()
        +GetEnvironmentRole()
        +UpdateEnvironmentRole()
        +CreateOrganizationRole()
        +ListOrganizationRoles()
        +GetOrganizationRole()
        +UpdateOrganizationRole()
        +DeleteOrganizationRole()
        +SetEnvironmentRolePermissions()
        +AddEnvironmentRolePermission()
        +SetOrganizationRolePermissions()
        +AddOrganizationRolePermission()
        +RemoveOrganizationRolePermission()
        +CreatePermission()
        +ListPermissions()
        +GetPermission()
        +UpdatePermission()
        +DeletePermission()
        +GetResource()
        +CreateResource()
        +UpdateResource()
        +DeleteResource()
        +ListResources()
        +GetResourceByExternalId()
        +UpdateResourceByExternalId()
        +DeleteResourceByExternalId()
        +Check()
        +ListRoleAssignments()
        +AssignRole()
        +RemoveRole()
        +RemoveRoleAssignment()
        +ListResourcesForMembership()
        +ListMembershipsForResource()
        +ListMembershipsForResourceByExternalId()
    }

    class ResourceIdentifier {
        <<interface>>
        +resourceIdentifierParams() map
    }
    class ResourceIdentifierById {
        +ResourceId string
    }
    class ResourceIdentifierByExternalId {
        +ResourceExternalId string
        +ResourceTypeSlug string
    }

    class ParentResourceIdentifier {
        <<interface>>
        +parentResourceIdentifierParams() map
    }
    class ParentResourceIdentifierById {
        +ParentResourceId string
    }
    class ParentResourceIdentifierByExternalId {
        +ParentResourceExternalId string
        +ParentResourceTypeSlug string
    }

    class EnvironmentRole {
        +Id string
        +Slug string
        +Permissions []string
    }
    class OrganizationRole {
        +Id string
        +Slug string
        +Permissions []string
    }
    class Permission {
        +Id string
        +Slug string
    }
    class AuthorizationResource {
        +Id string
        +ExternalId string
        +OrganizationId string
    }
    class RoleAssignment {
        +Id string
        +Role RoleAssignmentRole
        +Resource RoleAssignmentResource
    }
    class AuthorizationCheckResult {
        +Authorized bool
    }

    ResourceIdentifier <|.. ResourceIdentifierById
    ResourceIdentifier <|.. ResourceIdentifierByExternalId
    ParentResourceIdentifier <|.. ParentResourceIdentifierById
    ParentResourceIdentifier <|.. ParentResourceIdentifierByExternalId

    Client ..> EnvironmentRole : returns
    Client ..> OrganizationRole : returns
    Client ..> Permission : returns
    Client ..> AuthorizationResource : returns
    Client ..> RoleAssignment : returns
    Client ..> AuthorizationCheckResult : returns
    Client ..> ResourceIdentifier : uses
    Client ..> ParentResourceIdentifier : uses
Loading

Comments Outside Diff (2)

  1. pkg/authorization/client.go, line 802-1008 (link)

    All client methods are unimplemented stubs

    Every single one of the 28 client methods returns errors.New("not implemented"). While the PR title says "Base fga pr" and the description references follow-up PRs (feat: step1-env-roles #512feat: step9 membership queries #521) that will implement the logic, this means the entire authorization package as introduced here is non-functional. Any consumer calling these methods will receive runtime errors.

    This is fine as a scaffolding PR, but it should be clearly gated (e.g., unexported, behind a feature flag, or not wired into the public SDK surface) until the implementations land, to avoid shipping a broken public API.

  2. pkg/authorization/client.go, line 764-796 (link)

    PermissionSlug missing omitempty in three list opts structs

    In ListResourcesForMembershipOpts, ListMembershipsForResourceOpts, and ListMembershipsForResourceByExternalIdOpts, the PermissionSlug field uses url:"permission_slug" without omitempty:

    PermissionSlug string `url:"permission_slug"`

    This means when PermissionSlug is an empty string, it will still be serialized as permission_slug= in the query string, which may cause unexpected API behaviour (e.g., a validation error from the server, or returning zero results).

    If permission_slug is a required parameter, consider adding validation. If it is optional, add omitempty:

    PermissionSlug string `url:"permission_slug,omitempty"`

    This same issue applies to all three structs: ListResourcesForMembershipOpts (line 766), ListMembershipsForResourceOpts (line 777), and ListMembershipsForResourceByExternalIdOpts (line 790).

Last reviewed commit: 2f95368

Comment on lines +506 to +514
// GetEnvironmentRole gets an environment role by slug.
func (c *Client) GetEnvironmentRole(ctx context.Context, opts GetEnvironmentRoleOpts) (EnvironmentRole, error) {
c.once.Do(c.init)
return EnvironmentRole{}, errors.New("not implemented")
}

// UpdateEnvironmentRole updates an environment role.
func (c *Client) UpdateEnvironmentRole(ctx context.Context, opts UpdateEnvironmentRoleOpts) (EnvironmentRole, error) {
c.once.Do(c.init)
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing ListMetadata in environment and organization role list responses

ListEnvironmentRolesResponse and ListOrganizationRolesResponse do not include pagination metadata, while all other list responses (ListPermissionsResponse, ListAuthorizationResourcesResponse, ListRoleAssignmentsResponse, ListAuthorizationOrganizationMembershipsResponse) consistently include ListMetadata:

type ListEnvironmentRolesResponse struct {
    Data         []EnvironmentRole   `json:"data"`
    ListMetadata common.ListMetadata `json:"list_metadata"`
}

type ListOrganizationRolesResponse struct {
    Data         []OrganizationRole  `json:"data"`
    ListMetadata common.ListMetadata `json:"list_metadata"`
}

If these APIs support pagination (which is likely given the pattern used elsewhere), callers will have no way to iterate through pages. This could be intentional if the API returns all results in a single call, but it's worth confirming.

Comment on lines +555 to +560
func (c *Client) AddEnvironmentRolePermission(ctx context.Context, opts AddEnvironmentRolePermissionOpts) (EnvironmentRole, error) {
c.once.Do(c.init)
return EnvironmentRole{}, errors.New("not implemented")
}

// SetOrganizationRolePermissions sets permissions for an organization role.
Copy link
Contributor

Choose a reason for hiding this comment

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

Inconsistent omitempty on Description in update opts

Across all update opts structs (UpdateEnvironmentRoleOpts, UpdateOrganizationRoleOpts, UpdatePermissionOpts, UpdateAuthorizationResourceOpts, UpdateResourceByExternalIdOpts), Name uses omitempty but Description does not:

Name        *string `json:"name,omitempty"`
Description *string `json:"description"`        // always serialized, even when nil → JSON null

When Description is nil, it will be serialized as null in the JSON body, which will actively clear the description on the server side. This may be intentional (to allow callers to explicitly clear a description), but it creates an asymmetry: callers who omit Description will unintentionally wipe it from the resource.

A more explicit pattern would be to either document this behaviour clearly, or use a sentinel/wrapper type to distinguish "not set" from "explicitly cleared". This same pattern appears at the Description field of all five update opts types.

Comment on lines +601 to +626

// DeletePermission deletes a permission.
func (c *Client) DeletePermission(ctx context.Context, opts DeletePermissionOpts) error {
c.once.Do(c.init)
return errors.New("not implemented")
}

// GetResource gets a resource by Id.
func (c *Client) GetResource(ctx context.Context, opts GetAuthorizationResourceOpts) (AuthorizationResource, error) {
c.once.Do(c.init)
return AuthorizationResource{}, errors.New("not implemented")
}

// CreateResource creates a new resource.
func (c *Client) CreateResource(ctx context.Context, opts CreateAuthorizationResourceOpts) (AuthorizationResource, error) {
c.once.Do(c.init)
return AuthorizationResource{}, errors.New("not implemented")
}

// UpdateResource updates a resource.
func (c *Client) UpdateResource(ctx context.Context, opts UpdateAuthorizationResourceOpts) (AuthorizationResource, error) {
c.once.Do(c.init)
return AuthorizationResource{}, errors.New("not implemented")
}

// DeleteResource deletes a resource.
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing RemoveEnvironmentRolePermission method

There is an asymmetry between environment and organization role permission management:

Operation Environment Role Organization Role
Set (replace all) SetEnvironmentRolePermissions SetOrganizationRolePermissions
Add (one) AddEnvironmentRolePermission AddOrganizationRolePermission
Remove (one) missing RemoveOrganizationRolePermission

If the WorkOS FGA API supports removing individual permissions from environment roles, a corresponding RemoveEnvironmentRolePermission function and opts type should be added here to keep the API surface consistent. If this is intentionally omitted (e.g., the API doesn't support it at the environment scope), a brief comment explaining the asymmetry would help future maintainers.

Comment on lines +338 to +354
// GetPermissionOpts contains the options for getting a permission.
type GetPermissionOpts struct {
Slug string `json:"-"`
}

// UpdatePermissionOpts contains the options for updating a permission.
type UpdatePermissionOpts struct {
Slug string `json:"-"`
Name *string `json:"name,omitempty"`
Description *string `json:"description"`
}

// DeletePermissionOpts contains the options for deleting a permission.
type DeletePermissionOpts struct {
Slug string `json:"-"`
}

Copy link
Contributor

Choose a reason for hiding this comment

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

sync.Once prevents re-initialization after first method call

The sync.Once pattern used here means init() fires exactly once — on the first method call. This creates a subtle footgun: if HTTPClient or Endpoint are customized on the Client struct after the first method call, the changes will be silently ignored because init() has already run and the once can never be reset.

For the DefaultClient pattern (where SetAPIKey is called before use), this is fine. However, for consumers who construct a custom Client{...} and then call a method before setting all fields, it can lead to hard-to-debug issues.

Consider following the pattern used elsewhere in the workos-go SDK to verify this is consistent, or document the expected initialization order explicitly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Add reusable path segment constants matching the Python SDK pattern
for authorization API endpoints.
@swaroopAkkineniWorkos swaroopAkkineniWorkos changed the title feat: Base fga pr for Go sdk Base fga pr for Go sdk Mar 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant