feat!: generate Go SDK from OpenAPI spec#524
Conversation
Complete rewrite of the Go SDK generated by the oagen Go emitter. Flat workos package with idiomatic Go patterns: - NewClient(apiKey, ...ClientOption) functional options - context.Context on all methods - Typed string enum constants - Iterator[T] for auto-pagination - SDK-native error types - Exponential backoff retry with jitter - httptest-based generated tests This is a breaking change from the v1 SDK architecture. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add 8 AuthenticateWith* wrappers on UserManagement - Add CreateOAuthApplication and CreateM2MApplication on Connect - Fix test imports to use correct module path (v6) - Fix params struct names to use service-prefixed convention - Deduplicate test functions for merged mount groups - Preserve original go.mod (not overwritten by emitter) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements Passwordless, Vault KV/crypto, webhook verification, Actions helper, session seal/unseal, PKCE, AuthKit/SSO URL builders, JWKS helper, and public client factory (H01-H19). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Regenerated from spec changes: extracted DirectoryUser/EventContext schemas, SlimRole refs, limit type number->integer, RoleList ref. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@greptile review |
| ) | ||
|
|
||
| // passwordlessService handles Passwordless session operations. | ||
| type passwordlessService struct { |
There was a problem hiding this comment.
passwordless.go:16 and vault_kv.go:16 both initialize the client in the separate file, vs in workos.go like the other clients
There was a problem hiding this comment.
+1 — this is an inconsistency. All other services are initialized once in NewClient() and stored as fields on Client. Passwordless() and Vault() create a new allocation on every call, which is both inconsistent and wasteful.
Fix: add passwordless *passwordlessService and vault *vaultService fields to the Client struct, initialize them in NewClient() alongside the others, and have the accessors return the cached instance:
// In NewClient():
c.passwordless = &passwordlessService{client: c}
c.vault = &vaultService{client: c}
// Accessor:
func (c *Client) Passwordless() *passwordlessService {
return c.passwordless
}|
|
||
| // UserManagementCreateAuthenticateParams contains the parameters for CreateAuthenticate. | ||
| type UserManagementCreateAuthenticateParams struct { | ||
| Body interface{} `json:"-"` |
There was a problem hiding this comment.
is there a reason for the untyped blog here for the entire body?
There was a problem hiding this comment.
Agreed — Body interface{} here loses all type safety. Consumers can pass literally anything (a string, an int, a struct with wrong fields) and it'll only fail at runtime.
Since there are already dedicated typed methods for each grant type (AuthenticateWithPassword, AuthenticateWithCode, etc.), this generic CreateAuthenticate with an untyped body may not need to be part of the public API at all. If it does need to stay (e.g., for forward compatibility with new grant types), consider a sealed interface pattern:
type AuthenticateBody interface {
authenticateBody() // unexported method = sealed to this package
}The same issue applies to ConnectCreateApplicationParams.Body in connect.go:56.
| ParentResourceID *string `url:"parent_resource_id,omitempty" json:"-"` | ||
| // ParentResourceTypeSlug is the slug of the parent resource type. Must be provided together with `parent_resource_external_id`. | ||
| ParentResourceTypeSlug *string `url:"parent_resource_type_slug,omitempty" json:"-"` | ||
| // ParentResourceExternalID is the application-specific external identifier of the parent resource. Must be provided together with `parent_resource_type_slug`. | ||
| ParentResourceExternalID *string `url:"parent_resource_external_id,omitempty" json:"-"` | ||
| } |
There was a problem hiding this comment.
it would be cool if we could put types like (ParentResourceTypeSlug + ParentResourceExternalID) into it's own object so they are grouped together by default. This comes up a lot across the sdk
There was a problem hiding this comment.
This is a fair suggestion. The pattern of ParentResourceTypeSlug + ParentResourceExternalID appearing together across many param structs (here, AuthorizationListOrganizationMembershipResourcesParams, AuthorizationListResourcesParams, CreateAuthorizationResource, UpdateAuthorizationResource, etc.) is a good candidate for a shared embedded struct:
// ParentResourceRef identifies a parent resource by external ID and type.
type ParentResourceRef struct {
ParentResourceID *string `url:"parent_resource_id,omitempty" json:"parent_resource_id,omitempty"`
ParentResourceTypeSlug *string `url:"parent_resource_type_slug,omitempty" json:"parent_resource_type_slug,omitempty"`
ParentResourceExternalID *string `url:"parent_resource_external_id,omitempty" json:"parent_resource_external_id,omitempty"`
}
// Similarly for resource identification:
type ResourceRef struct {
ResourceID *string `json:"resource_id,omitempty"`
ResourceExternalID *string `json:"resource_external_id,omitempty"`
ResourceTypeSlug *string `json:"resource_type_slug,omitempty"`
}Then embed them:
type AuthorizationCheckParams struct {
PermissionSlug string `json:"permission_slug"`
ResourceRef // embedded
}This would be a codegen improvement — teaching oagen to detect these co-occurring field groups and extract shared types.
Go Best Practices ReviewI reviewed the full PR diff. Overall this is a well-structured SDK rewrite — the flat 🔴 Critical: Module path says
|
| Issue | Severity | Generated vs Hand-written |
|---|---|---|
| Module path v6/v7 mismatch | 🔴 Critical | Hand-written (go.mod) |
Untyped Body interface{} |
🟠 High | Generated |
map[string]interface{} bodies |
🟠 High | Hand-written |
| Inconsistent service init (see thread below) | 🟡 Medium | Hand-written |
| Unexported service types | 🟡 Medium | Generated |
*[]string / *map types |
🟡 Medium | Generated |
| Untyped return maps | 🟡 Medium | Hand-written |
| "an user" grammar | 🟢 Low | Generated |
| GetAuthorizationURL behavior | 🟢 Low | Generated/Hand-written |
Most of the generated code issues point to improvements needed in the oagen code generator templates. The hand-written code issues (authenticate bodies, service init consistency) can be addressed directly.
P0: URL-encode all path parameters with url.PathEscape to prevent
malformed URLs from IDs containing special characters. URL-encode
returnTo in GetLogoutURL with url.QueryEscape.
P1: Deduplicate HMAC signing logic in actions_helper.go by reusing
exported functions from webhook_verification.go. Replace
map[string]interface{} with typed structs (User, AuthenticateResponseImpersonator,
JWTClaims) in session helpers for type safety. Extract shared
pkceAndState helper in PublicClient and eliminate throwaway Client
allocations by storing a minimal Client at construction time.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… support Resolves #198 by parsing both code/message and error/error_description JSON formats, exposing ErrorCode and ErrorDescription on APIError, and returning typed errors (EmailVerificationRequiredError, MFAEnrollmentError, etc.) that callers can match with errors.As. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Description
This branch introduces the v7 Go SDK surface behind a single root
workospackage and sharedworkos.Client.It also:
pkg/*service packages in favor ofclient.Service()accessorsdocs/V7_MIGRATION_GUIDE.mdto document consumer migrationDocumentation
Does this require changes to the WorkOS Docs? E.g. the API Reference or code snippets need updates.