mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
fix: defensively handle nil maps and slices in marshaling (#18418)
Adds a custom marshaler to handle some cases where nils were being marshaled to nulls, causing the web UI to throw an error. --------- Co-authored-by: Steven Masley <stevenmasley@gmail.com>
This commit is contained in:
@ -274,6 +274,17 @@ func (s *GroupSyncSettings) String() string {
|
|||||||
return runtimeconfig.JSONString(s)
|
return runtimeconfig.JSONString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *GroupSyncSettings) MarshalJSON() ([]byte, error) {
|
||||||
|
if s.Mapping == nil {
|
||||||
|
s.Mapping = make(map[string][]uuid.UUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aliasing the struct to avoid infinite recursion when calling json.Marshal
|
||||||
|
// on the struct itself.
|
||||||
|
type Alias GroupSyncSettings
|
||||||
|
return json.Marshal(&struct{ *Alias }{Alias: (*Alias)(s)})
|
||||||
|
}
|
||||||
|
|
||||||
type ExpectedGroup struct {
|
type ExpectedGroup struct {
|
||||||
OrganizationID uuid.UUID
|
OrganizationID uuid.UUID
|
||||||
GroupID *uuid.UUID
|
GroupID *uuid.UUID
|
||||||
|
@ -2,6 +2,7 @@ package idpsync_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -9,6 +10,49 @@ import (
|
|||||||
"github.com/coder/coder/v2/coderd/idpsync"
|
"github.com/coder/coder/v2/coderd/idpsync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TestMarshalJSONEmpty ensures no empty maps are marshaled as `null` in JSON.
|
||||||
|
func TestMarshalJSONEmpty(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("Group", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
output, err := json.Marshal(&idpsync.GroupSyncSettings{
|
||||||
|
RegexFilter: regexp.MustCompile(".*"),
|
||||||
|
})
|
||||||
|
require.NoError(t, err, "marshal empty group settings")
|
||||||
|
require.NotContains(t, string(output), "null")
|
||||||
|
|
||||||
|
require.JSONEq(t,
|
||||||
|
`{"field":"","mapping":{},"regex_filter":".*","auto_create_missing_groups":false}`,
|
||||||
|
string(output))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Role", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
output, err := json.Marshal(&idpsync.RoleSyncSettings{})
|
||||||
|
require.NoError(t, err, "marshal empty group settings")
|
||||||
|
require.NotContains(t, string(output), "null")
|
||||||
|
|
||||||
|
require.JSONEq(t,
|
||||||
|
`{"field":"","mapping":{}}`,
|
||||||
|
string(output))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Organization", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
output, err := json.Marshal(&idpsync.OrganizationSyncSettings{})
|
||||||
|
require.NoError(t, err, "marshal empty group settings")
|
||||||
|
require.NotContains(t, string(output), "null")
|
||||||
|
|
||||||
|
require.JSONEq(t,
|
||||||
|
`{"field":"","mapping":{},"assign_default":false}`,
|
||||||
|
string(output))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseStringSliceClaim(t *testing.T) {
|
func TestParseStringSliceClaim(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -234,6 +234,17 @@ func (s *OrganizationSyncSettings) String() string {
|
|||||||
return runtimeconfig.JSONString(s)
|
return runtimeconfig.JSONString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *OrganizationSyncSettings) MarshalJSON() ([]byte, error) {
|
||||||
|
if s.Mapping == nil {
|
||||||
|
s.Mapping = make(map[string][]uuid.UUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aliasing the struct to avoid infinite recursion when calling json.Marshal
|
||||||
|
// on the struct itself.
|
||||||
|
type Alias OrganizationSyncSettings
|
||||||
|
return json.Marshal(&struct{ *Alias }{Alias: (*Alias)(s)})
|
||||||
|
}
|
||||||
|
|
||||||
// ParseClaims will parse the claims and return the list of organizations the user
|
// ParseClaims will parse the claims and return the list of organizations the user
|
||||||
// should sync to.
|
// should sync to.
|
||||||
func (s *OrganizationSyncSettings) ParseClaims(ctx context.Context, db database.Store, mergedClaims jwt.MapClaims) ([]uuid.UUID, error) {
|
func (s *OrganizationSyncSettings) ParseClaims(ctx context.Context, db database.Store, mergedClaims jwt.MapClaims) ([]uuid.UUID, error) {
|
||||||
|
@ -291,3 +291,14 @@ func (s *RoleSyncSettings) String() string {
|
|||||||
}
|
}
|
||||||
return runtimeconfig.JSONString(s)
|
return runtimeconfig.JSONString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *RoleSyncSettings) MarshalJSON() ([]byte, error) {
|
||||||
|
if s.Mapping == nil {
|
||||||
|
s.Mapping = make(map[string][]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aliasing the struct to avoid infinite recursion when calling json.Marshal
|
||||||
|
// on the struct itself.
|
||||||
|
type Alias RoleSyncSettings
|
||||||
|
return json.Marshal(&struct{ *Alias }{Alias: (*Alias)(s)})
|
||||||
|
}
|
||||||
|
@ -836,6 +836,9 @@ func (api *API) idpSyncClaimFieldValues(orgID uuid.UUID, rw http.ResponseWriter,
|
|||||||
httpapi.InternalServerError(rw, err)
|
httpapi.InternalServerError(rw, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if fieldValues == nil {
|
||||||
|
fieldValues = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
httpapi.Write(ctx, rw, http.StatusOK, fieldValues)
|
httpapi.Write(ctx, rw, http.StatusOK, fieldValues)
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user