Surface Infisical API error body in thrown exceptions
Parse the JSON envelope (message/error/reqId) returned by 4xx/5xx responses and include the human-readable message in the InfisicalApiException message itself, plus new ApiErrorMessage and ApiRequestId properties. InfisicalErrorDetails and the error handler propagate the new fields so PowerShell error records and the logger surface the server-side reason instead of an opaque status line.
This commit is contained in:
+3
-1
@@ -6,11 +6,13 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Infisical API error responses are now parsed to surface the server-side `message`, `error`, and `reqId` fields. The 4xx/5xx exception message includes the human-readable explanation (e.g. "The project is of type secret-manager") instead of an opaque `Infisical API returned 400 (Bad Request)`. The `InfisicalApiException` gains `ApiErrorMessage` and `ApiRequestId` properties; `InfisicalErrorDetails` carries the same fields so PowerShell error records and logger output expose them.
|
||||||
|
|
||||||
## 2026.06.04.1920
|
## 2026.06.04.1920
|
||||||
|
|
||||||
- Build produced from commit 0f8f44afdb38.
|
- Build produced from commit 0f8f44afdb38.
|
||||||
|
|
||||||
## Unreleased (carried forward)
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
- `build.ps1` gains a `-CommitArtifacts` switch that, after a successful build, stages and commits only the build outputs (`Module/PSInfisicalAPI/bin/**`, `Module/PSInfisicalAPI/PSInfisicalAPI.psd1`, and the auto-inserted `CHANGELOG.md` build stamp) with a message that references the source commit whose hash is now embedded in `BuildCommitHash`. The switch is mutually exclusive with the older broader `-CommitOnSuccess` (which still uses `git add -A`). README extended with a "Committing source and build artifacts in lockstep" section describing the recommended two-commit workflow.
|
- `build.ps1` gains a `-CommitArtifacts` switch that, after a successful build, stages and commits only the build outputs (`Module/PSInfisicalAPI/bin/**`, `Module/PSInfisicalAPI/PSInfisicalAPI.psd1`, and the auto-inserted `CHANGELOG.md` build stamp) with a message that references the source commit whose hash is now embedded in `BuildCommitHash`. The switch is mutually exclusive with the older broader `-CommitOnSuccess` (which still uses `git add -A`). README extended with a "Committing source and build artifacts in lockstep" section describing the recommended two-commit workflow.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
using System;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace PSInfisicalAPI.Errors
|
||||||
|
{
|
||||||
|
internal static class InfisicalApiErrorEnvelope
|
||||||
|
{
|
||||||
|
public static void Enrich(InfisicalApiException exception, string body)
|
||||||
|
{
|
||||||
|
if (exception == null || string.IsNullOrEmpty(body))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string trimmed = body.TrimStart();
|
||||||
|
if (trimmed.Length == 0 || (trimmed[0] != '{' && trimmed[0] != '['))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JObject obj;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
JToken token = JToken.Parse(body);
|
||||||
|
if (token.Type != JTokenType.Object) { return; }
|
||||||
|
obj = (JObject)token;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string message = ReadString(obj, "message");
|
||||||
|
string error = ReadString(obj, "error");
|
||||||
|
string reqId = ReadString(obj, "reqId");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(message)) { exception.ApiErrorMessage = message; }
|
||||||
|
if (!string.IsNullOrEmpty(error) && string.IsNullOrEmpty(exception.ApiErrorCode)) { exception.ApiErrorCode = error; }
|
||||||
|
if (!string.IsNullOrEmpty(reqId)) { exception.ApiRequestId = reqId; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string BuildExceptionMessage(int statusCode, string reasonPhrase, string body)
|
||||||
|
{
|
||||||
|
string baseMessage = string.Concat(
|
||||||
|
"Infisical API returned ",
|
||||||
|
statusCode.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
||||||
|
" (", reasonPhrase ?? string.Empty, ").");
|
||||||
|
|
||||||
|
string apiMessage = null;
|
||||||
|
string apiError = null;
|
||||||
|
string reqId = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(body))
|
||||||
|
{
|
||||||
|
string trimmed = body.TrimStart();
|
||||||
|
if (trimmed.Length > 0 && trimmed[0] == '{')
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
JToken token = JToken.Parse(body);
|
||||||
|
if (token.Type == JTokenType.Object)
|
||||||
|
{
|
||||||
|
JObject obj = (JObject)token;
|
||||||
|
apiMessage = ReadString(obj, "message");
|
||||||
|
apiError = ReadString(obj, "error");
|
||||||
|
reqId = ReadString(obj, "reqId");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(apiMessage) && string.IsNullOrEmpty(apiError) && string.IsNullOrEmpty(reqId))
|
||||||
|
{
|
||||||
|
return baseMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Text.StringBuilder builder = new System.Text.StringBuilder(baseMessage);
|
||||||
|
if (!string.IsNullOrEmpty(apiMessage))
|
||||||
|
{
|
||||||
|
builder.Append(' ').Append(apiMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(apiError) || !string.IsNullOrEmpty(reqId))
|
||||||
|
{
|
||||||
|
builder.Append(" [");
|
||||||
|
bool needsSeparator = false;
|
||||||
|
if (!string.IsNullOrEmpty(apiError))
|
||||||
|
{
|
||||||
|
builder.Append("error=").Append(apiError);
|
||||||
|
needsSeparator = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(reqId))
|
||||||
|
{
|
||||||
|
if (needsSeparator) { builder.Append("; "); }
|
||||||
|
builder.Append("reqId=").Append(reqId);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Append(']');
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReadString(JObject obj, string name)
|
||||||
|
{
|
||||||
|
JToken token;
|
||||||
|
if (obj.TryGetValue(name, StringComparison.OrdinalIgnoreCase, out token) && token != null && token.Type == JTokenType.String)
|
||||||
|
{
|
||||||
|
return (string)token;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ namespace PSInfisicalAPI.Errors
|
|||||||
public int? StatusCode { get; set; }
|
public int? StatusCode { get; set; }
|
||||||
public string ReasonPhrase { get; set; }
|
public string ReasonPhrase { get; set; }
|
||||||
public string ApiErrorCode { get; set; }
|
public string ApiErrorCode { get; set; }
|
||||||
|
public string ApiErrorMessage { get; set; }
|
||||||
|
public string ApiRequestId { get; set; }
|
||||||
public string SanitizedBody { get; set; }
|
public string SanitizedBody { get; set; }
|
||||||
public int? LineNumber { get; set; }
|
public int? LineNumber { get; set; }
|
||||||
public int? LinePosition { get; set; }
|
public int? LinePosition { get; set; }
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ namespace PSInfisicalAPI.Errors
|
|||||||
details.StatusCode = apiException.StatusCode;
|
details.StatusCode = apiException.StatusCode;
|
||||||
details.ReasonPhrase = apiException.ReasonPhrase;
|
details.ReasonPhrase = apiException.ReasonPhrase;
|
||||||
details.ApiErrorCode = apiException.ApiErrorCode;
|
details.ApiErrorCode = apiException.ApiErrorCode;
|
||||||
|
details.ApiErrorMessage = apiException.ApiErrorMessage;
|
||||||
|
details.ApiRequestId = apiException.ApiRequestId;
|
||||||
details.SanitizedBody = apiException.SanitizedBody;
|
details.SanitizedBody = apiException.SanitizedBody;
|
||||||
details.EndpointName = apiException.EndpointName;
|
details.EndpointName = apiException.EndpointName;
|
||||||
details.RequestMethod = apiException.RequestMethod;
|
details.RequestMethod = apiException.RequestMethod;
|
||||||
@@ -70,6 +72,16 @@ namespace PSInfisicalAPI.Errors
|
|||||||
logger.Error(Component, string.Concat("API Error Code: ", details.ApiErrorCode));
|
logger.Error(Component, string.Concat("API Error Code: ", details.ApiErrorCode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(details.ApiErrorMessage))
|
||||||
|
{
|
||||||
|
logger.Error(Component, string.Concat("API Error Message: ", details.ApiErrorMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(details.ApiRequestId))
|
||||||
|
{
|
||||||
|
logger.Error(Component, string.Concat("API Request Id: ", details.ApiRequestId));
|
||||||
|
}
|
||||||
|
|
||||||
if (details.LineNumber.HasValue)
|
if (details.LineNumber.HasValue)
|
||||||
{
|
{
|
||||||
logger.Error(Component, string.Concat("Line: ", details.LineNumber.Value.ToString(CultureInfo.InvariantCulture)));
|
logger.Error(Component, string.Concat("Line: ", details.LineNumber.Value.ToString(CultureInfo.InvariantCulture)));
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ namespace PSInfisicalAPI.Errors
|
|||||||
public int StatusCode { get; set; }
|
public int StatusCode { get; set; }
|
||||||
public string ReasonPhrase { get; set; }
|
public string ReasonPhrase { get; set; }
|
||||||
public string ApiErrorCode { get; set; }
|
public string ApiErrorCode { get; set; }
|
||||||
|
public string ApiErrorMessage { get; set; }
|
||||||
|
public string ApiRequestId { get; set; }
|
||||||
public string SanitizedBody { get; set; }
|
public string SanitizedBody { get; set; }
|
||||||
public string EndpointName { get; set; }
|
public string EndpointName { get; set; }
|
||||||
public string RequestMethod { get; set; }
|
public string RequestMethod { get; set; }
|
||||||
|
|||||||
@@ -135,15 +135,14 @@ namespace PSInfisicalAPI.Http
|
|||||||
|
|
||||||
private static InfisicalApiException BuildApiException(InfisicalHttpResponse response, InfisicalEndpointDefinition definition)
|
private static InfisicalApiException BuildApiException(InfisicalHttpResponse response, InfisicalEndpointDefinition definition)
|
||||||
{
|
{
|
||||||
InfisicalApiException exception = new InfisicalApiException(string.Concat(
|
string message = InfisicalApiErrorEnvelope.BuildExceptionMessage(response.StatusCode, response.ReasonPhrase, response.Body);
|
||||||
"Infisical API returned ",
|
InfisicalApiException exception = new InfisicalApiException(message);
|
||||||
response.StatusCode.ToString(CultureInfo.InvariantCulture),
|
|
||||||
" (", response.ReasonPhrase ?? string.Empty, ")."));
|
|
||||||
exception.StatusCode = response.StatusCode;
|
exception.StatusCode = response.StatusCode;
|
||||||
exception.ReasonPhrase = response.ReasonPhrase;
|
exception.ReasonPhrase = response.ReasonPhrase;
|
||||||
exception.EndpointName = definition.Name;
|
exception.EndpointName = definition.Name;
|
||||||
exception.RequestMethod = definition.Method;
|
exception.RequestMethod = definition.Method;
|
||||||
exception.SanitizedBody = response.Body;
|
exception.SanitizedBody = response.Body;
|
||||||
|
InfisicalApiErrorEnvelope.Enrich(exception, response.Body);
|
||||||
return exception;
|
return exception;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -625,15 +625,14 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
|
|
||||||
private static InfisicalApiException BuildApiException(InfisicalHttpResponse response, InfisicalEndpointDefinition definition)
|
private static InfisicalApiException BuildApiException(InfisicalHttpResponse response, InfisicalEndpointDefinition definition)
|
||||||
{
|
{
|
||||||
InfisicalApiException exception = new InfisicalApiException(string.Concat(
|
string message = InfisicalApiErrorEnvelope.BuildExceptionMessage(response.StatusCode, response.ReasonPhrase, response.Body);
|
||||||
"Infisical API returned ",
|
InfisicalApiException exception = new InfisicalApiException(message);
|
||||||
response.StatusCode.ToString(CultureInfo.InvariantCulture),
|
|
||||||
" (", response.ReasonPhrase ?? string.Empty, ")."));
|
|
||||||
exception.StatusCode = response.StatusCode;
|
exception.StatusCode = response.StatusCode;
|
||||||
exception.ReasonPhrase = response.ReasonPhrase;
|
exception.ReasonPhrase = response.ReasonPhrase;
|
||||||
exception.EndpointName = definition.Name;
|
exception.EndpointName = definition.Name;
|
||||||
exception.RequestMethod = definition.Method;
|
exception.RequestMethod = definition.Method;
|
||||||
exception.SanitizedBody = response.Body;
|
exception.SanitizedBody = response.Body;
|
||||||
|
InfisicalApiErrorEnvelope.Enrich(exception, response.Body);
|
||||||
return exception;
|
return exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user