Skip to content

refactor: read GraphQL errors from the typed SDK error - #280

Open
ianaya89 wants to merge 1 commit into
masterfrom
refactor/typed-graphql-errors
Open

refactor: read GraphQL errors from the typed SDK error#280
ianaya89 wants to merge 1 commit into
masterfrom
refactor/typed-graphql-errors

Conversation

@ianaya89

Copy link
Copy Markdown
Member

ParseError unmarshalled err.Error() into a hand-written mirror of clientv2.ErrorResponse:

var errResp ClientError
if jsonErr := json.Unmarshal([]byte(err.Error()), &errResp); jsonErr != nil { ... }

That string is itself json.Marshal(er)ErrorResponse.Error() does exactly that. So every classification in the provider round-tripped through the error message, and only worked while the outermost error was the SDK's own.

It is not just aesthetic

Wrapping the error anywhere breaks the whole chain. Measured on master:

FormatError(resp, "test")
  → "Not Found: env not found"

FormatError(fmt.Errorf("executing query: %w", resp), "test")
  → "executing query: {\"networkErrors\":null,\"graphqlErrors\":[{\"message\":\"env not found\",\"extensions\":{\"code\":\"NOT_FOUND\"}}]}"

IsNotFoundError(fmt.Errorf("executing query: %w", resp))
  → (false, error parsing: invalid character 'e' looking for beginning of value)

A wrapped not-found stops being recognised, so Read would surface a raw JSON blob instead of removing the resource from state. Nothing wraps it today, which is the only reason this is latent — but WithRetry sits directly in that path, and errors.As is what stops the next wrap from being a silent regression.

Change

errors.As(err, &errResp), the way isRetryable in client.go already does two files over. The result is projected into ClientError so FormatError, IsNotFoundError, IsActiveClustersError and errorPrefix are untouched — ClientError stops being a JSON-parse target and becomes a deliberate projection, keeping the SDK's shape out of the rest of the file.

Two knock-on details:

  • Path elements now arrive as ast.PathName / ast.PathIndex, not the string / float64 the JSON round-trip produced. errorPrefix type-asserts on string, so pathElements flattens them. There is a test with an index in the path.
  • NetworkError is typed (*clientv2.HTTPError), which removes the string and map[string]interface{} branches of formatNetworkErrors — a string network error was never representable. A network error with an empty message now reports Network error: HTTP 503 instead of Network error: .

Tests

errors_test.go and two tests in env/common hand-wrote the SDK's JSON into errors.New. They now build real clientv2.ErrorResponse values, so waitForDeletion and FormatDeleteError are exercised against the shape they actually receive. Added cases for a wrapped error response, an indexed path, a network error without a message, and a nil entry in the error list.

Overlap

Touches the same file as #269 and #270 but different functions, so the source hunks are disjoint. errors_test.go is rewritten here, so whichever merges second will want this version of that file.

Not done, worth a follow-up: every caller of IsNotFoundError and IsActiveClustersError discards the returned error (notFound, _ := ...), so both could drop it and return a plain bool.

ParseError unmarshalled err.Error() into a mirror of clientv2.ErrorResponse.
That string is itself json.Marshal(er), so every classification round-tripped
through the error message and only worked while the outermost error was the
SDK's own. Wrapping it anywhere broke everything downstream:

	FormatError(fmt.Errorf("executing query: %w", resp), "env")
	  before: executing query: {"networkErrors":null,"graphqlErrors":[...]}
	  after:  Not Found: env not found

	IsNotFoundError(same)
	  before: (false, error parsing: invalid character 'e' ...)
	  after:  (true, nil)

Resolve the error with errors.As instead, the way isRetryable already does two
files over, and project it into ClientError so the rest of the file stays put.

Path elements arrive as ast.PathName/ast.PathIndex rather than the string and
float64 the JSON round-trip produced, so they are flattened for errorPrefix.
NetworkError is typed now, which drops the string and map branches of
formatNetworkErrors; a network error with no message reports its status code
instead of nothing.

The tests here and in env/common hand-wrote the SDK's JSON. They now build real
clientv2.ErrorResponse values, so these paths are covered as they are called.
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.

1 participant