Refine connector provider registry per review
Three follow-ups from review of the registry consolidation:
- Build Vercel's authorization URL with net/url instead of a
hand-rolled "{integration_slug}" placeholder resolved by
strings.ReplaceAll. The slug is escaped via url.PathEscape in a
per-provider Registration.BuildAuthURL closure, and the unused
AuthURLParams plumbing on Registration and OAuth2Connector is
removed (OAuth2Connector now carries a typed IntegrationSlug).
- Drop the SettingsInput union type and the per-provider
MarshalSettings closures. The create resolvers now build the typed
coredata.*ConnectorSettings directly from the gqlgen input, the
same way the OAuth callback path already does, so there is no
shared catch-all DTO and no stringly-typed boundary.
- Restore ConnectorProviders() to a plain ordered slice literal; the
intermediate map + slices.Sort added nondeterminism and a sort for
no benefit.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
"go.probo.inc/probo/pkg/connector"
|
||||
"go.probo.inc/probo/pkg/connector/provider"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
|
||||
@@ -44,21 +43,12 @@ func (r *mutationResolver) CreateAPIKeyConnector(ctx context.Context, input type
|
||||
Connection: &connector.APIKeyConnection{APIKey: input.APIKey},
|
||||
}
|
||||
|
||||
in := &provider.SettingsInput{
|
||||
TallyOrganizationID: input.TallyOrganizationID,
|
||||
SentryOrganizationSlug: input.SentryOrganizationSlug,
|
||||
SupabaseOrganizationSlug: input.SupabaseOrganizationSlug,
|
||||
GitHubOrganization: input.GithubOrganization,
|
||||
OnePasswordSCIMBridgeURL: input.OnePasswordScimBridgeURL,
|
||||
raw, err := apiKeyConnectorSettings(input)
|
||||
if err != nil {
|
||||
return nil, gqlutils.Invalid(ctx, err)
|
||||
}
|
||||
if reg, ok := r.providerRegistry.Get(input.Provider); ok && reg.MarshalSettings != nil {
|
||||
raw, err := reg.MarshalSettings(in)
|
||||
if err != nil {
|
||||
return nil, gqlutils.Invalid(ctx, err)
|
||||
}
|
||||
|
||||
req.RawSettings = raw
|
||||
}
|
||||
req.RawSettings = raw
|
||||
|
||||
cnnctr, err := r.probo.Connectors.Create(ctx, scope, req)
|
||||
if err != nil {
|
||||
@@ -101,18 +91,12 @@ func (r *mutationResolver) CreateClientCredentialsConnector(ctx context.Context,
|
||||
Connection: oauth2Conn,
|
||||
}
|
||||
|
||||
in := &provider.SettingsInput{
|
||||
OnePasswordAccountID: input.OnePasswordAccountID,
|
||||
OnePasswordRegion: input.OnePasswordRegion,
|
||||
raw, err := clientCredentialsConnectorSettings(input)
|
||||
if err != nil {
|
||||
return nil, gqlutils.Invalid(ctx, err)
|
||||
}
|
||||
if reg, ok := r.providerRegistry.Get(input.Provider); ok && reg.MarshalSettings != nil {
|
||||
raw, err := reg.MarshalSettings(in)
|
||||
if err != nil {
|
||||
return nil, gqlutils.Invalid(ctx, err)
|
||||
}
|
||||
|
||||
req.RawSettings = raw
|
||||
}
|
||||
req.RawSettings = raw
|
||||
|
||||
cnnctr, err := r.probo.Connectors.Create(ctx, scope, req)
|
||||
if err != nil {
|
||||
|
||||
97
pkg/server/api/console/v1/connector_settings.go
Normal file
97
pkg/server/api/console/v1/connector_settings.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package console_v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/server/api/console/v1/types"
|
||||
)
|
||||
|
||||
// These helpers live outside connector_resolvers.go because that file is
|
||||
// regenerated by gqlgen, which does not preserve standalone functions.
|
||||
|
||||
// apiKeyConnectorSettings marshals the provider-specific extra settings
|
||||
// for an API-key connector from the typed gqlgen input into the JSON
|
||||
// blob persisted on coredata.Connector.RawSettings. It returns (nil,
|
||||
// nil) for providers without extra settings.
|
||||
//
|
||||
// Returned errors are surfaced verbatim to the client via
|
||||
// gqlutils.Invalid: they must contain only field names and structural
|
||||
// information, never user-supplied values.
|
||||
func apiKeyConnectorSettings(input types.CreateAPIKeyConnectorInput) (json.RawMessage, error) {
|
||||
switch input.Provider {
|
||||
case coredata.ConnectorProviderTally:
|
||||
if input.TallyOrganizationID == nil || *input.TallyOrganizationID == "" {
|
||||
return nil, fmt.Errorf("cannot create tally connector: tallyOrganizationId is required")
|
||||
}
|
||||
|
||||
return json.Marshal(&coredata.TallyConnectorSettings{OrganizationID: *input.TallyOrganizationID})
|
||||
case coredata.ConnectorProviderSentry:
|
||||
if input.SentryOrganizationSlug == nil || *input.SentryOrganizationSlug == "" {
|
||||
return nil, fmt.Errorf("cannot create sentry connector: sentryOrganizationSlug is required")
|
||||
}
|
||||
|
||||
return json.Marshal(&coredata.SentryConnectorSettings{OrganizationSlug: *input.SentryOrganizationSlug})
|
||||
case coredata.ConnectorProviderSupabase:
|
||||
if input.SupabaseOrganizationSlug == nil || *input.SupabaseOrganizationSlug == "" {
|
||||
return nil, fmt.Errorf("cannot create supabase connector: supabaseOrganizationSlug is required")
|
||||
}
|
||||
|
||||
return json.Marshal(&coredata.SupabaseConnectorSettings{OrganizationSlug: *input.SupabaseOrganizationSlug})
|
||||
case coredata.ConnectorProviderGitHub:
|
||||
if input.GithubOrganization == nil || *input.GithubOrganization == "" {
|
||||
return nil, fmt.Errorf("cannot create github connector: githubOrganization is required")
|
||||
}
|
||||
|
||||
return json.Marshal(&coredata.GitHubConnectorSettings{Organization: *input.GithubOrganization})
|
||||
case coredata.ConnectorProviderOnePassword:
|
||||
if input.OnePasswordScimBridgeURL == nil || *input.OnePasswordScimBridgeURL == "" {
|
||||
return nil, fmt.Errorf("cannot create 1password connector: onePasswordScimBridgeURL is required")
|
||||
}
|
||||
|
||||
u, err := url.Parse(*input.OnePasswordScimBridgeURL)
|
||||
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" {
|
||||
return nil, fmt.Errorf("cannot create 1password connector: onePasswordScimBridgeURL must be an http(s) URL")
|
||||
}
|
||||
|
||||
return json.Marshal(&coredata.OnePasswordConnectorSettings{SCIMBridgeURL: *input.OnePasswordScimBridgeURL})
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// clientCredentialsConnectorSettings marshals the provider-specific
|
||||
// extra settings for a client-credentials connector. See
|
||||
// apiKeyConnectorSettings for the error contract.
|
||||
func clientCredentialsConnectorSettings(input types.CreateClientCredentialsConnectorInput) (json.RawMessage, error) {
|
||||
switch input.Provider {
|
||||
case coredata.ConnectorProviderOnePassword:
|
||||
if input.OnePasswordAccountID == nil || *input.OnePasswordAccountID == "" ||
|
||||
input.OnePasswordRegion == nil || *input.OnePasswordRegion == "" {
|
||||
return nil, fmt.Errorf("cannot create 1password connector: onePasswordAccountId and onePasswordRegion are required")
|
||||
}
|
||||
|
||||
return json.Marshal(&coredata.OnePasswordUsersAPISettings{
|
||||
AccountID: *input.OnePasswordAccountID,
|
||||
Region: *input.OnePasswordRegion,
|
||||
})
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
Reference in New Issue
Block a user