Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/building_block_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ type MeshBuildingBlockDefinitionStatus struct {
LatestVersionUuid string `json:"latestVersionUuid"`
LatestReleasedVersion *int64 `json:"latestReleasedVersion"`
LatestReleasedVersionUuid *string `json:"latestReleasedVersionUuid"`
RedactedForNonOwnerAccess bool `json:"redactedForNonOwnerAccess"`
}

type MeshBuildingBlockDefinition struct {
Expand Down
2 changes: 1 addition & 1 deletion client/building_block_definition_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ type MeshBuildingBlockDefinitionVersionSpec struct {

type MeshBuildingBlockDefinitionVersionStatus struct {
State MeshBuildingBlockDefinitionVersionState `json:"state" tfsdk:"state"`
UsageCount int64 `json:"usageCount" tfsdk:"usage_count"`
UsageCount *int64 `json:"usageCount,omitzero" tfsdk:"usage_count"`
}

type MeshBuildingBlockDefinitionVersion struct {
Expand Down
38 changes: 18 additions & 20 deletions client/building_block_definition_version_implementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package client
import (
"encoding/json/v2"
"fmt"
"reflect"

"github.com/meshcloud/meshstack-cli/client/types"
"github.com/meshcloud/meshstack-cli/client/types/enum"
Expand Down Expand Up @@ -74,32 +73,31 @@ type MeshBuildingBlockDefinitionImplementation struct {
Terraform *MeshBuildingBlockDefinitionTerraformImplementation `json:"terraform,omitzero" tfsdk:"terraform"`
}

func (m MeshBuildingBlockDefinitionImplementation) InferTypeFromNonNilField() (result enum.Entry[MeshBuildingBlockImplementationType]) {
setResultIfNotNil := func(implType enum.Entry[MeshBuildingBlockImplementationType], v any) {
// Manual implementation is an empty struct, so carefully check v for nilness using reflection!
if !reflect.ValueOf(v).IsZero() {
if len(result) > 0 && result != implType {
panic(fmt.Errorf("inferred implementation type %s but already set to %s", implType, result))
}
result = implType
}
}
setResultIfNotNil(MeshBuildingBlockImplementationTypeManual, m.Manual)
setResultIfNotNil(MeshBuildingBlockImplementationTypeTerraform, m.Terraform)
setResultIfNotNil(MeshBuildingBlockImplementationTypeGithubWorkflows, m.GithubWorkflows)
setResultIfNotNil(MeshBuildingBlockImplementationTypeGitlabPipeline, m.GitlabPipeline)
setResultIfNotNil(MeshBuildingBlockImplementationTypeAzureDevOpsPipeline, m.AzureDevOpsPipeline)
if len(result) == 0 {
panic("cannot infer implementation type")
// InferType derives the implementation type from the one variant that is set. A version without any variant
// is what meshStack answers a workspace that may only consume the definition.
func (m MeshBuildingBlockDefinitionImplementation) InferType() (enum.Entry[MeshBuildingBlockImplementationType], error) {
result, err := inferVariantType(
variant(MeshBuildingBlockImplementationTypeManual, m.Manual != nil),
variant(MeshBuildingBlockImplementationTypeTerraform, m.Terraform != nil),
variant(MeshBuildingBlockImplementationTypeGithubWorkflows, m.GithubWorkflows != nil),
variant(MeshBuildingBlockImplementationTypeGitlabPipeline, m.GitlabPipeline != nil),
variant(MeshBuildingBlockImplementationTypeAzureDevOpsPipeline, m.AzureDevOpsPipeline != nil),
)
if err != nil {
return "", fmt.Errorf("cannot infer implementation type: %w", err)
}
return
return result, nil
}

func (m MeshBuildingBlockDefinitionImplementation) MarshalJSON() ([]byte, error) {
type wrapped MeshBuildingBlockDefinitionImplementation
w := wrapped(m)
if len(w.Type) == 0 {
w.Type = m.InferTypeFromNonNilField()
inferred, err := m.InferType()
if err != nil {
return nil, err
}
w.Type = inferred
}
return json.Marshal(w, wireCompatibility)
}
Expand Down
37 changes: 19 additions & 18 deletions client/integration_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package client
import (
"encoding/json/v2"
"fmt"
"reflect"

"github.com/meshcloud/meshstack-cli/client/types"
"github.com/meshcloud/meshstack-cli/client/types/enum"
Expand Down Expand Up @@ -57,30 +56,32 @@ type MeshIntegrationConfig struct {
EntraId *MeshIntegrationEntraIdConfig `json:"entraid,omitzero" tfsdk:"entraid"`
}

func (m MeshIntegrationConfig) InferTypeFromNonNilField() (result enum.Entry[MeshIntegrationConfigType]) {
setResultIfNotNil := func(implType enum.Entry[MeshIntegrationConfigType], v any) {
if !reflect.ValueOf(v).IsZero() {
if len(result) > 0 && result != implType {
panic(fmt.Errorf("inferred config type %s but already set to %s", implType, result))
}
result = implType
}
}
setResultIfNotNil(MeshIntegrationConfigTypeGithub, m.Github)
setResultIfNotNil(MeshIntegrationConfigTypeGitlab, m.Gitlab)
setResultIfNotNil(MeshIntegrationConfigTypeAzureDevops, m.AzureDevops)
setResultIfNotNil(MeshIntegrationConfigTypeEntraId, m.EntraId)
if len(result) == 0 {
panic("cannot infer config type")
func (m MeshIntegrationConfig) InferType() (enum.Entry[MeshIntegrationConfigType], error) {
result, err := inferVariantType(
variant(MeshIntegrationConfigTypeGithub, m.Github != nil),
variant(MeshIntegrationConfigTypeGitlab, m.Gitlab != nil),
variant(MeshIntegrationConfigTypeAzureDevops, m.AzureDevops != nil),
variant(MeshIntegrationConfigTypeEntraId, m.EntraId != nil),
)
if err != nil {
return "", fmt.Errorf("cannot infer integration config type: %w", err)
}
return
return result, nil
}

func (m MeshIntegrationConfig) MarshalJSON() ([]byte, error) {
// Using wrapped type avoids calling MarshalJSON recursively!
type wrapped MeshIntegrationConfig
w := wrapped(m)
w.Type = m.InferTypeFromNonNilField()
// Built-in integrations (replicator, metering) come with a type but no variant, so the type is only
// inferred when it is missing.
if len(w.Type) == 0 {
inferred, err := m.InferType()
if err != nil {
return nil, err
}
w.Type = inferred
}
return json.Marshal(w, wireCompatibility)
}

Expand Down
34 changes: 34 additions & 0 deletions client/variant_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package client

import (
"errors"
"fmt"

"github.com/meshcloud/meshstack-cli/client/types/enum"
)

type variantCandidate[T ~string] struct {
Type enum.Entry[T]
IsSet bool
}

func variant[T ~string](typ enum.Entry[T], isSet bool) variantCandidate[T] {
return variantCandidate[T]{Type: typ, IsSet: isSet}
}

func inferVariantType[T ~string](candidates ...variantCandidate[T]) (enum.Entry[T], error) {
var result enum.Entry[T]
for _, candidate := range candidates {
if !candidate.IsSet {
continue
}
if len(result) > 0 {
return "", fmt.Errorf("more than one variant is set: %s and %s", result, candidate.Type)
}
result = candidate.Type
}
if len(result) == 0 {
return "", errors.New("no variant is set")
}
return result, nil
}
53 changes: 53 additions & 0 deletions client/variant_type_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package client

import (
"encoding/json/v2"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestMeshBuildingBlockDefinitionImplementation_InferType(t *testing.T) {
t.Run("an empty manual struct counts as set", func(t *testing.T) {
got, err := MeshBuildingBlockDefinitionImplementation{Manual: &MeshBuildingBlockDefinitionManualImplementation{}}.InferType()
require.NoError(t, err)
assert.Equal(t, MeshBuildingBlockImplementationTypeManual, got)
})

t.Run("no variant", func(t *testing.T) {
_, err := MeshBuildingBlockDefinitionImplementation{}.InferType()
require.EqualError(t, err, "cannot infer implementation type: no variant is set")
})

t.Run("several variants", func(t *testing.T) {
_, err := MeshBuildingBlockDefinitionImplementation{
Terraform: &MeshBuildingBlockDefinitionTerraformImplementation{},
GitlabPipeline: &MeshBuildingBlockDefinitionGitLabPipelineImplementation{},
}.InferType()
require.EqualError(t, err, "cannot infer implementation type: more than one variant is set: terraform and gitlabPipeline")
})
}

func TestMeshIntegrationConfig_InferType(t *testing.T) {
t.Run("one variant", func(t *testing.T) {
got, err := MeshIntegrationConfig{Gitlab: &MeshIntegrationGitlabConfig{}}.InferType()
require.NoError(t, err)
assert.Equal(t, MeshIntegrationConfigTypeGitlab, got)
})

t.Run("no variant", func(t *testing.T) {
_, err := MeshIntegrationConfig{}.InferType()
require.EqualError(t, err, "cannot infer integration config type: no variant is set")
})

t.Run("several variants", func(t *testing.T) {
_, err := MeshIntegrationConfig{Github: &MeshIntegrationGithubConfig{}, EntraId: &MeshIntegrationEntraIdConfig{}}.InferType()
require.EqualError(t, err, "cannot infer integration config type: more than one variant is set: github and entraid")
})

t.Run("marshalling without a variant reports the error", func(t *testing.T) {
_, err := json.Marshal(MeshIntegrationConfig{})
require.ErrorContains(t, err, "cannot infer integration config type")
})
}
Loading