From cf06e0294f864a8617038aedfa4128de3c512b3e Mon Sep 17 00:00:00 2001 From: SwiftEngineer Date: Thu, 9 Jun 2022 20:33:24 -0700 Subject: [PATCH] make nullable wrapper type-safe, at the cost of verbousity --- admin_organization.go | 14 +++++++------- admin_organization_integration_test.go | 4 ++-- nullable.go | 20 ++++++++++++++++++++ nullable_test.go | 22 ++++++++++++++-------- 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/admin_organization.go b/admin_organization.go index 77e06dd79..bceec252b 100644 --- a/admin_organization.go +++ b/admin_organization.go @@ -59,13 +59,13 @@ type AdminOrganization struct { // AdminOrganizationUpdateOptions represents the admin options for updating an organization. // https://www.terraform.io/docs/cloud/api/admin/organizations.html#request-body type AdminOrganizationUpdateOptions struct { - AccessBetaTools *bool `jsonapi:"attr,access-beta-tools,omitempty"` - GlobalModuleSharing *bool `jsonapi:"attr,global-module-sharing,omitempty"` - IsDisabled *bool `jsonapi:"attr,is-disabled,omitempty"` - TerraformBuildWorkerApplyTimeout *string `jsonapi:"attr,terraform-build-worker-apply-timeout,omitempty"` - TerraformBuildWorkerPlanTimeout *string `jsonapi:"attr,terraform-build-worker-plan-timeout,omitempty"` - TerraformWorkerSudoEnabled bool `jsonapi:"attr,terraform-worker-sudo-enabled,omitempty"` - WorkspaceLimit *Nullable `jsonapi:"attr,workspace-limit,omitempty"` + AccessBetaTools *bool `jsonapi:"attr,access-beta-tools,omitempty"` + GlobalModuleSharing *bool `jsonapi:"attr,global-module-sharing,omitempty"` + IsDisabled *bool `jsonapi:"attr,is-disabled,omitempty"` + TerraformBuildWorkerApplyTimeout *string `jsonapi:"attr,terraform-build-worker-apply-timeout,omitempty"` + TerraformBuildWorkerPlanTimeout *string `jsonapi:"attr,terraform-build-worker-plan-timeout,omitempty"` + TerraformWorkerSudoEnabled bool `jsonapi:"attr,terraform-worker-sudo-enabled,omitempty"` + WorkspaceLimit *NullableInt `jsonapi:"attr,workspace-limit,omitempty"` } // AdminOrganizationList represents a list of organizations via Admin API. diff --git a/admin_organization_integration_test.go b/admin_organization_integration_test.go index b06a90dba..cc3572166 100644 --- a/admin_organization_integration_test.go +++ b/admin_organization_integration_test.go @@ -252,7 +252,7 @@ func TestAdminOrganizations_Update(t *testing.T) { opts = AdminOrganizationUpdateOptions{ GlobalModuleSharing: &globalModuleSharing, IsDisabled: &isDisabled, - WorkspaceLimit: &Nullable{&workspaceLimit}, + WorkspaceLimit: &NullableInt{&workspaceLimit}, } adminOrg, err = client.Admin.Organizations.Update(ctx, org.Name, opts) @@ -268,7 +268,7 @@ func TestAdminOrganizations_Update(t *testing.T) { opts = AdminOrganizationUpdateOptions{ GlobalModuleSharing: &globalModuleSharing, IsDisabled: &isDisabled, - WorkspaceLimit: &Nullable{nil}, + WorkspaceLimit: &NullableInt{nil}, } adminOrg, err = client.Admin.Organizations.Update(ctx, org.Name, opts) diff --git a/nullable.go b/nullable.go index 3271255aa..6df02fe35 100644 --- a/nullable.go +++ b/nullable.go @@ -7,6 +7,8 @@ import "encoding/json" // // This is particularly useful for attributes that are set to null if provided as null in a json request body. // Conversely, if they are omitted from the request body, they will retain their existing value. +// +// This struct should use generics, but this project doesn't support them yet :( type Nullable struct { value interface{} } @@ -17,3 +19,21 @@ func (i Nullable) MarshalJSON() ([]byte, error) { } return json.Marshal(nil) } + +// int version of Nullable +type NullableInt struct { + value *int +} + +func (i NullableInt) MarshalJSON() ([]byte, error) { + return Nullable{i.value}.MarshalJSON() +} + +// string version of Nullable +type NullableString struct { + value *string +} + +func (i NullableString) MarshalJSON() ([]byte, error) { + return Nullable{i.value}.MarshalJSON() +} diff --git a/nullable_test.go b/nullable_test.go index 9ded56c28..9854aa2e4 100644 --- a/nullable_test.go +++ b/nullable_test.go @@ -11,16 +11,22 @@ import ( func TestNullable_MarshalJSON(t *testing.T) { type fields struct { - WithValue *Nullable `jsonapi:"attr,with-value,omitempty"` - WithZeroValue *Nullable `jsonapi:"attr,with-zero-value,omitempty"` - WithNilValue *Nullable `jsonapi:"attr,with-nil-value,omitempty"` - Unset *Nullable `jsonapi:"attr,unset,omitempty"` + IntWithValue *NullableInt `jsonapi:"attr,i-with-value,omitempty"` + IntWithZeroValue *NullableInt `jsonapi:"attr,i-with-zero-value,omitempty"` + IntWithNilValue *NullableInt `jsonapi:"attr,i-with-nil-value,omitempty"` + StringWithValue *NullableString `jsonapi:"attr,s-with-value,omitempty"` + StringWithEmptyValue *NullableString `jsonapi:"attr,s-with-empty-value,omitempty"` + StringWithNilValue *NullableString `jsonapi:"attr,s-with-nil-value,omitempty"` + Unset *Nullable `jsonapi:"attr,unset,omitempty"` } var instance = fields{ - WithValue: &Nullable{42}, - WithZeroValue: &Nullable{0}, - WithNilValue: &Nullable{nil}, + IntWithValue: &NullableInt{Int(42)}, + IntWithZeroValue: &NullableInt{Int(0)}, + IntWithNilValue: &NullableInt{nil}, + StringWithValue: &NullableString{String("hello")}, + StringWithEmptyValue: &NullableString{String("")}, + StringWithNilValue: &NullableString{nil}, } reqBody, err := serializeRequestBody(&instance) @@ -29,5 +35,5 @@ func TestNullable_MarshalJSON(t *testing.T) { bodyBytes, err := req.BodyBytes() assert.NoError(t, err) bodyString := strings.Trim(string(bodyBytes), "\n") - assert.Equal(t, `{"data":{"type":"","attributes":{"with-nil-value":null,"with-value":42,"with-zero-value":0}}}`, bodyString) + assert.Equal(t, `{"data":{"type":"","attributes":{"i-with-nil-value":null,"i-with-value":42,"i-with-zero-value":0,"s-with-empty-value":"","s-with-nil-value":null,"s-with-value":"hello"}}}`, bodyString) }