From 349c41f977fab8ceb939481f2f19bb6088765ceb Mon Sep 17 00:00:00 2001 From: Marcos Navarro Date: Sat, 21 Dec 2024 03:15:26 +0900 Subject: [PATCH] Handle reset credentials in UI --- extensions/vscode/src/state.ts | 9 +++++++++ extensions/vscode/src/utils/errorTypes.ts | 18 +++++++++++++++++- internal/accounts/provider_credentials.go | 7 ++++++- internal/services/api/get_credentials.go | 5 +++++ internal/types/api_errors.go | 14 ++++++++++++++ internal/types/api_errors_test.go | 20 ++++++++++++++++++++ 6 files changed, 71 insertions(+), 2 deletions(-) diff --git a/extensions/vscode/src/state.ts b/extensions/vscode/src/state.ts index b5e5aa357..e9d788b04 100644 --- a/extensions/vscode/src/state.ts +++ b/extensions/vscode/src/state.ts @@ -19,6 +19,10 @@ import { getStatusFromError, getSummaryStringFromError, } from "src/utils/errors"; +import { + isErrCredentialsReset, + errCredentialsResetMessage, +} from "src/utils/errorTypes"; import { DeploymentSelector, SelectionState } from "src/types/shared"; import { LocalState, Views } from "./constants"; @@ -283,6 +287,11 @@ export class PublisherState implements Disposable { this.credentials = response.data; }); } catch (error: unknown) { + if (isErrCredentialsReset(error)) { + const warnMsg = errCredentialsResetMessage(); + window.showWarningMessage(warnMsg); + return; + } const summary = getSummaryStringFromError("refreshCredentials", error); window.showErrorMessage(summary); } diff --git a/extensions/vscode/src/utils/errorTypes.ts b/extensions/vscode/src/utils/errorTypes.ts index 5902eef56..e447e7063 100644 --- a/extensions/vscode/src/utils/errorTypes.ts +++ b/extensions/vscode/src/utils/errorTypes.ts @@ -17,7 +17,8 @@ export type ErrorCode = | "deployedContentNotRunning" | "tomlValidationError" | "tomlUnknownError" - | "pythonExecNotFound"; + | "pythonExecNotFound" + | "credentialCorruptedReset"; export type axiosErrorWithJson = AxiosError & { @@ -162,6 +163,15 @@ export type ErrInvalidConfigFiles = MkErrorDataType< export const isErrInvalidConfigFile = mkErrorTypeGuard("invalidConfigFile"); +// Invalid configuration file(s) +export type ErrCredentialsReset = MkErrorDataType<"credentialCorruptedReset">; +export const isErrCredentialsReset = mkErrorTypeGuard( + "credentialCorruptedReset", +); +export const errCredentialsResetMessage = () => { + return "Stored credentials for Posit Publisher found in an unrecognizable state. A reset was required in order to proceed."; +}; + // Tries to match an Axios error that comes with an identifiable Json structured data // defaulting to be ErrUnknown message when export function resolveAgentJsonErrorMsg(err: axiosErrorWithJson) { @@ -185,5 +195,11 @@ export function resolveAgentJsonErrorMsg(err: axiosErrorWithJson) { return errPythonExecNotFoundErrorMessage(err); } + // Ignore errors coming from credentials being reset, + // a warning is shown when PublisherState is updated. + if (isErrPythonExecNotFoundError(err)) { + return errPythonExecNotFoundErrorMessage(err); + } + return errUnknownMessage(err as axiosErrorWithJson); } diff --git a/internal/accounts/provider_credentials.go b/internal/accounts/provider_credentials.go index 276307906..1c16d0fb1 100644 --- a/internal/accounts/provider_credentials.go +++ b/internal/accounts/provider_credentials.go @@ -5,6 +5,7 @@ package accounts import ( "github.com/posit-dev/publisher/internal/credentials" "github.com/posit-dev/publisher/internal/logging" + "github.com/posit-dev/publisher/internal/types" ) type CredentialsProvider struct { @@ -14,7 +15,11 @@ type CredentialsProvider struct { func NewCredentialsProvider(log logging.Logger) (*CredentialsProvider, error) { cs, err := credentials.NewCredentialsService(log) if err != nil { - return nil, err + // Ignore errors from credentials reset at this point + _, isCredsReset := types.IsAgentErrorOf(err, types.ErrorCredentialCorruptedReset) + if !isCredsReset { + return nil, err + } } return &CredentialsProvider{cs}, nil diff --git a/internal/services/api/get_credentials.go b/internal/services/api/get_credentials.go index 24280cf1c..633204d71 100644 --- a/internal/services/api/get_credentials.go +++ b/internal/services/api/get_credentials.go @@ -21,6 +21,11 @@ func GetCredentialsHandlerFunc(log logging.Logger) http.HandlerFunc { apiErr.JSONResponse(w) return } + if aerr.Code == types.ErrorCredentialCorruptedReset { + apiErr := types.APIErrorCredentialCorruptedResetFromAgentError(*aerr) + apiErr.JSONResponse(w) + return + } } InternalError(w, req, log, err) return diff --git a/internal/types/api_errors.go b/internal/types/api_errors.go index 03a2435ea..aa1cc814d 100644 --- a/internal/types/api_errors.go +++ b/internal/types/api_errors.go @@ -162,6 +162,20 @@ func APIErrorCredentialsUnavailableFromAgentError(aerr AgentError) APIErrorCrede } } +type APIErrorCredentialCorruptedReset struct { + Code ErrorCode `json:"code"` +} + +func (apierr *APIErrorCredentialCorruptedReset) JSONResponse(w http.ResponseWriter) { + jsonResult(w, http.StatusConflict, apierr) +} + +func APIErrorCredentialCorruptedResetFromAgentError(aerr AgentError) APIErrorCredentialCorruptedReset { + return APIErrorCredentialCorruptedReset{ + Code: ErrorCredentialCorruptedReset, + } +} + type APIErrorPythonExecNotFound struct { Code ErrorCode `json:"code"` } diff --git a/internal/types/api_errors_test.go b/internal/types/api_errors_test.go index e59488887..1d0c3ac80 100644 --- a/internal/types/api_errors_test.go +++ b/internal/types/api_errors_test.go @@ -88,3 +88,23 @@ func (s *ApiErrorsSuite) TestAPIErrorPythonExecNotFoundFromAgentError() { s.Equal(http.StatusUnprocessableEntity, rec.Result().StatusCode) s.Contains(bodyRes, `{"code":"pythonExecNotFound"}`) } + +func (s *ApiErrorsSuite) TestAPIErrorCredentialCorruptedResetFromAgentError() { + agentErr := AgentError{ + Message: "", + Code: ErrorCredentialCorruptedReset, + Err: errors.New("unknown field error"), + Data: ErrorData{}, + } + + rec := httptest.NewRecorder() + + apiError := APIErrorCredentialCorruptedResetFromAgentError(agentErr) + s.Equal(apiError.Code, ErrorCredentialCorruptedReset) + + apiError.JSONResponse(rec) + + bodyRes := rec.Body.String() + s.Equal(http.StatusConflict, rec.Result().StatusCode) + s.Contains(bodyRes, `{"code":"credentialCorruptedReset"}`) +}