Skip to content

refactor the FromProto methods to return structs - #63

Merged
jaypipes merged 2 commits into
mainfrom
to-from-proto
Nov 5, 2026
Merged

jaypipes merged 2 commits into
mainfrom
to-from-proto

Conversation

@jaypipes

@jaypipes jaypipes commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

Refactors the FromProto methods on the Namespace-related structs in some important ways:

  1. Use a value receiver and return a struct or pointer to a struct created by reading the supplied proto message. This gets rid of a bunch of nil checks and repetitive code, changing things from this:
// FromProto sets the spec from the supplied Temporal Cloud NamespaceSpec.
// Project and tags are not part of the Temporal Cloud NamespaceSpec, so they
// are left unchanged; [Namespace.FromProto] sets them.
func (s *NamespaceSpec) FromProto(in *tcloudnamespacev1.NamespaceSpec) {
	s.Replicas = ReplicasFromProto(in.GetReplicas())
	s.RetentionDays = in.GetRetentionDays()

	if in.GetMtlsAuth() == nil {
		s.MTLSAuth = nil
	} else {
		s.MTLSAuth = &MTLSAuthSpec{}
		s.MTLSAuth.FromProto(in.GetMtlsAuth())
	}

	if in.GetApiKeyAuth() == nil {
		s.APIKeyAuth = nil
	} else {
		s.APIKeyAuth = &APIKeyAuthSpec{}
		s.APIKeyAuth.FromProto(in.GetApiKeyAuth())
	}

	s.SearchAttributes = SearchAttributesFromProto(in.GetSearchAttributes())

	if in.GetCodecServer() == nil {
		s.CodecServer = nil
	} else {
		s.CodecServer = &CodecServerSpec{}
		s.CodecServer.FromProto(in.GetCodecServer())
	}

	s.Lifecycle = LifecycleFromProto(in.GetLifecycle())

	if in.GetHighAvailability() == nil {
		s.HighAvailability = nil
	} else {
		s.HighAvailability = &HighAvailabilitySpec{}
		s.HighAvailability.FromProto(in.GetHighAvailability())
	}

	s.ConnectivityRuleIDs = slices.Clone(in.GetConnectivityRuleIds())

	// A capacity spec with no mode set carries nothing the CR can represent, so
	// it is treated as unset.
	if c := in.GetCapacitySpec(); c.GetOnDemand() == nil && c.GetProvisioned() == nil {
		s.Capacity = nil
	} else {
		s.Capacity = &CapacitySpec{}
		s.Capacity.FromProto(c)
	}

	if in.GetFairness() == nil {
		s.Fairness = nil
	} else {
		s.Fairness = &FairnessSpec{}
		s.Fairness.FromProto(in.GetFairness())
	}

	if in.GetEncryptionValidation() == nil {
		s.EncryptionValidation = nil
	} else {
		s.EncryptionValidation = &EncryptionValidationSpec{}
		s.EncryptionValidation.FromProto(in.GetEncryptionValidation())
	}

	s.Description = in.GetDescription()
}

to this:

// FromProto returns a NamespaceSpec constructed from the supplied Temporal
// Cloud NamespaceSpec.
func (s NamespaceSpec) FromProto(in *tcloudnamespacev1.NamespaceSpec) NamespaceSpec {
	if in == nil {
		return NamespaceSpec{}
	}

	out := NamespaceSpec{
		Description:         in.GetDescription(),
		Replicas:            ReplicasFromProto(in.GetReplicas()),
		RetentionDays:       in.GetRetentionDays(),
		SearchAttributes:    SearchAttributesFromProto(in.GetSearchAttributes()),
		Lifecycle:           LifecycleFromProto(in.GetLifecycle()),
		ConnectivityRuleIDs: slices.Clone(in.GetConnectivityRuleIds()),
	}

	var mtlsAuthSpec MTLSAuthSpec
	out.MTLSAuth = mtlsAuthSpec.FromProto(in.GetMtlsAuth())

	var apiKeyAuthSpec APIKeyAuthSpec
	out.APIKeyAuth = apiKeyAuthSpec.FromProto(in.GetApiKeyAuth())

	var codecSpec CodecServerSpec
	out.CodecServer = codecSpec.FromProto(in.GetCodecServer())

	var haSpec HighAvailabilitySpec
	out.HighAvailability = haSpec.FromProto(in.GetHighAvailability())

	var capSpec CapacitySpec
	out.Capacity = capSpec.FromProto(in.GetCapacitySpec())

	var fairSpec FairnessSpec
	out.Fairness = fairSpec.FromProto(in.GetFairness())

	var encValSpec EncryptionValidationSpec
	out.EncryptionValidation = encValSpec.FromProto(in.GetEncryptionValidation())

	return out
}
  1. Updates the Reconciler.ResourceSetLatest method to construct the latest CR from only the returned results from the call to Temporal Cloud Ops GetNamespace API method.

I'm considering changing
the calling interface for ResourceSetLatest to not mutate the supplied latest parameter but rather return an updated CR with the latest read information from the external API call. This will make the types.Reconciler interface methods that work with types.Resource parameters consistent: none will ever mutate parameters but will instead return a copy of the parameter updated with whatever the method changed about the CR's representation.

@jaypipes
jaypipes requested review from tomba7 and zainawaisn November 4, 2026 16:55
Comment thread controller/namespace/reconcile.go Resolved
Comment thread api/cloud/v1alpha1/namespace.go Resolved
Comment thread api/cloud/v1alpha1/namespace.go Resolved
Comment thread api/cloud/v1alpha1/namespace.go Resolved
Refactors the `FromProto` methods on the Namespace-related structs in
some important ways:

1. Use a value receiver and return a struct or pointer to a struct
   created by reading the supplied proto message. This gets rid of a
   bunch of nil checks and repetitive code, changing things from this:

```go
// FromProto sets the spec from the supplied Temporal Cloud NamespaceSpec.
// Project and tags are not part of the Temporal Cloud NamespaceSpec, so they
// are left unchanged; [Namespace.FromProto] sets them.
func (s *NamespaceSpec) FromProto(in *tcloudnamespacev1.NamespaceSpec) {
	s.Replicas = ReplicasFromProto(in.GetReplicas())
	s.RetentionDays = in.GetRetentionDays()

	if in.GetMtlsAuth() == nil {
		s.MTLSAuth = nil
	} else {
		s.MTLSAuth = &MTLSAuthSpec{}
		s.MTLSAuth.FromProto(in.GetMtlsAuth())
	}

	if in.GetApiKeyAuth() == nil {
		s.APIKeyAuth = nil
	} else {
		s.APIKeyAuth = &APIKeyAuthSpec{}
		s.APIKeyAuth.FromProto(in.GetApiKeyAuth())
	}

	s.SearchAttributes = SearchAttributesFromProto(in.GetSearchAttributes())

	if in.GetCodecServer() == nil {
		s.CodecServer = nil
	} else {
		s.CodecServer = &CodecServerSpec{}
		s.CodecServer.FromProto(in.GetCodecServer())
	}

	s.Lifecycle = LifecycleFromProto(in.GetLifecycle())

	if in.GetHighAvailability() == nil {
		s.HighAvailability = nil
	} else {
		s.HighAvailability = &HighAvailabilitySpec{}
		s.HighAvailability.FromProto(in.GetHighAvailability())
	}

	s.ConnectivityRuleIDs = slices.Clone(in.GetConnectivityRuleIds())

	// A capacity spec with no mode set carries nothing the CR can represent, so
	// it is treated as unset.
	if c := in.GetCapacitySpec(); c.GetOnDemand() == nil && c.GetProvisioned() == nil {
		s.Capacity = nil
	} else {
		s.Capacity = &CapacitySpec{}
		s.Capacity.FromProto(c)
	}

	if in.GetFairness() == nil {
		s.Fairness = nil
	} else {
		s.Fairness = &FairnessSpec{}
		s.Fairness.FromProto(in.GetFairness())
	}

	if in.GetEncryptionValidation() == nil {
		s.EncryptionValidation = nil
	} else {
		s.EncryptionValidation = &EncryptionValidationSpec{}
		s.EncryptionValidation.FromProto(in.GetEncryptionValidation())
	}

	s.Description = in.GetDescription()
}
```

to this:

```go
// FromProto returns a NamespaceSpec constructed from the supplied Temporal
// Cloud NamespaceSpec.
func (s NamespaceSpec) FromProto(in *tcloudnamespacev1.NamespaceSpec) NamespaceSpec {
	if in == nil {
		return NamespaceSpec{}
	}

	out := NamespaceSpec{
		Description:         in.GetDescription(),
		Replicas:            ReplicasFromProto(in.GetReplicas()),
		RetentionDays:       in.GetRetentionDays(),
		SearchAttributes:    SearchAttributesFromProto(in.GetSearchAttributes()),
		Lifecycle:           LifecycleFromProto(in.GetLifecycle()),
		ConnectivityRuleIDs: slices.Clone(in.GetConnectivityRuleIds()),
	}

	var mtlsAuthSpec MTLSAuthSpec
	out.MTLSAuth = mtlsAuthSpec.FromProto(in.GetMtlsAuth())

	var apiKeyAuthSpec APIKeyAuthSpec
	out.APIKeyAuth = apiKeyAuthSpec.FromProto(in.GetApiKeyAuth())

	var codecSpec CodecServerSpec
	out.CodecServer = codecSpec.FromProto(in.GetCodecServer())

	var haSpec HighAvailabilitySpec
	out.HighAvailability = haSpec.FromProto(in.GetHighAvailability())

	var capSpec CapacitySpec
	out.Capacity = capSpec.FromProto(in.GetCapacitySpec())

	var fairSpec FairnessSpec
	out.Fairness = fairSpec.FromProto(in.GetFairness())

	var encValSpec EncryptionValidationSpec
	out.EncryptionValidation = encValSpec.FromProto(in.GetEncryptionValidation())

	return out
}
```

2. Updates the `Reconciler.ResourceSetLatest` method to construct the
   `latest` CR from only the returned results from the call to Temporal
Cloud Ops `GetNamespace` API method.

I'm considering changing the calling interface for `ResourceSetLatest`
to *not* mutate the supplied `latest` parameter but rather return an
updated CR with the latest read information from the external API call.
This will make the `types.Reconciler` interface methods that work with
`types.Resource` parameters consistent: none will ever mutate parameters
but will instead return a copy of the parameter updated with whatever
the method changed about the CR's representation.

Signed-off-by: Jay Pipes <jay.pipes@temporal.io>

@tomba7 tomba7 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Love it.

@jaypipes
jaypipes merged commit f94d356 into main Nov 5, 2026
7 checks passed
@jaypipes
jaypipes deleted the to-from-proto branch November 5, 2026 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants