databricks-sdk-go icon indicating copy to clipboard operation
databricks-sdk-go copied to clipboard

[ISSUE] `apierr.GetAPIError` does not unmarshal `ErrorCode` correctly

Open nkvuong opened this issue 1 year ago • 3 comments

Description apierr.GetAPIError does not unmarshal ErrorCode correctly

Reproduction Below is a simplified version of parseErrorFromResponse

func main() {

	myError := apierr.APIError{
		ErrorCode: "RESOURCE_DOES_NOT_EXIST",
		Message:   "Path (/Users/abc) doesn't exist.",
	}
	b, err := json.Marshal(myError)
	if err != nil {
		panic(err)
	}
	print(b)
	var errorBody struct {
		ErrorCode any    `json:"error_code,omitempty"`
		Message   string `json:"message,omitempty"`
	}
	err = json.Unmarshal(b, &errorBody)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%+v\n", errorBody)
}

Expected behavior errorBody should be populated with ErrorCode & Message, however, ErrorCode is nil in this case

{ErrorCode:<nil> Message:Path (/Users/abc) doesn't exist.}

nkvuong avatar Jul 29 '24 08:07 nkvuong

Hi @nkvuong,

apierr.GetAPIError does not unmarshal ErrorCode correctly

Do you have an example of an input/ouput pair for apierr.GetAPIError that produces the incorrect behavior you are referring to?

renaudhartert-db avatar Aug 01 '24 09:08 renaudhartert-db

it happened in the failed test in this PR from TF provider - https://github.com/databricks/terraform-provider-databricks/pull/3832. The PR simply replaces the deprecated struct in TF with the Go SDK equivalent.

For example TestResourceUserDelete_NonExistingDir should produce no error due to the error handling in this line https://github.com/databricks/terraform-provider-databricks/blob/main/scim/resource_user.go#L156. However, apierr.IsMissing is returning false for this test case, because the ErrorCode was not unmarshaled correctly.

Since the only change is replacing the structs, we can simply compare the 2 structs, and the difference is the missing field tags

https://github.com/databricks/terraform-provider-databricks/blob/8d628d198523beb0beb379a94661d31ac8c3b3b7/common/apierr.go#L9-L17

https://github.com/databricks/databricks-sdk-go/blob/9bbc945278e8d97ae011a90dbd331c6e90180799/apierr/errors.go#L30-L39

nkvuong avatar Aug 01 '24 09:08 nkvuong

I see, thanks!

I think the core of the problem you're facing is that apierr.APIError is not meant to be a substitute for common.APIErrorBody. Rather, it is the "higher level" object that is returned by the SDK service clients, not the SDK HTTP client. Making a clear distinction between these two levels is the reason why the parsing struct was made anonymous in parseErrorFromResponse.

It seems that resource_user.go is not using the SDK service client but the HTTP client directly. Therefore, we cannot use apierr.IsMissing on the returned error given that the HTTP client is not returning an api.APIError to begin with.

I think there's two ways we can resolve this:

  • A: Change the terraform code to use the service client instead of the HTTP client — and then truly deprecate common.APIErrorBody.
  • B: Implement your own IsMissing function that would take a common.APIErrorBody struct as parameter.

What do you think?

renaudhartert-db avatar Aug 01 '24 10:08 renaudhartert-db

Closing this issue as "work as expected"

renaudhartert-db avatar Aug 29 '24 09:08 renaudhartert-db