Private/Api.psm1
|
#!/usr/bin/env pwsh using namespace System using namespace System.Web using namespace System.Text using namespace System.Net.Http using namespace System.Threading.Tasks using namespace System.Text.Json using namespace System.Text.Json.Serialization using namespace System.Collections.Generic using module ./Exceptions.psm1 using module ./Enums.psm1 class ApiClient : IDisposable { hidden [HttpClient] $_httpClient hidden [AllowNull()][string] $_accessToken hidden [ValidateNotNullOrWhiteSpace()][string] $_baseUrl hidden [ValidateNotNullOrEmpty()][securestring]$_clientSecret ApiClient([string]$baseUrl) { $this.Initialize($baseUrl, $null, $null) } ApiClient([string]$baseUrl, [string]$accessToken) { $this.Initialize($baseUrl, $null, $accessToken) } hidden [void] Initialize([string]$baseUrl, [securestring]$clientSecret, [string]$accessToken) { $this._httpClient = [HttpClient]::new() $this._baseUrl = $baseUrl; $this.SetAccessToken($accessToken) if ($null -eq $clientSecret) { $client_secret_env = $env:INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET if (![string]::IsNullOrWhiteSpace($client_secret_env)) { $this.SetClientSecret(($client_secret_env | xconvert ToSecurestring)) } } else { $this.SetClientSecret($clientSecret) } $this.FormatBaseUrl() } [void] SetAccessToken([string]$accessToken) { $this._accessToken = $accessToken } [void] SetClientSecret([securestring]$clientSecret) { $this._clientSecret = $clientSecret } hidden [void] FormatBaseUrl() { if ($this._baseUrl.EndsWith("/")) { $this._baseUrl = $this._baseUrl.Substring(0, $this._baseUrl.Length - 1) } if (![RegularExpressions.Regex]::IsMatch($this._baseUrl, "^[a-zA-Z]+://.*")) { $this._baseUrl = "https://" + $this._baseUrl } if ($this._baseUrl.EndsWith("/api")) { $this._baseUrl = $this._baseUrl.Substring(0, $this._baseUrl.Length - 4) } } [string] GetBaseUrl() { return $this._baseUrl } [HttpClient] GetClient() { return $this._httpClient } hidden [Task[string]] FormatErrorMessageAsync([HttpResponseMessage]$response) { $message = "Unexpected response: $([int]$response.StatusCode) $($response.ReasonPhrase)" try { $content = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() if (![string]::IsNullOrEmpty($content)) { $message += " - $content" } } catch { $null } return [Task]::FromResult([string]$message) } hidden [JsonSerializerOptions] GetJsonOptions() { $options = [JsonSerializerOptions]::new() $options.PropertyNameCaseInsensitive = $true return $options } hidden [JsonSerializerOptions] GetJsonOptionsNullOmit() { $options = [JsonSerializerOptions]::new() $options.PropertyNameCaseInsensitive = $true $options.DefaultIgnoreCondition = [JsonIgnoreCondition]::WhenWritingNull return $options } [Task[object]] PostAsync([Type]$responseType, [string]$url, [object]$requestBody) { return $this.PostAsync($responseType, $url, $requestBody, $false) } [Task[object]] PostAsync([Type]$responseType, [string]$url, [object]$requestBody, [bool]$omitNullValues) { try { $options = if ($omitNullValues) { $this.GetJsonOptionsNullOmit() } else { $this.GetJsonOptions() } $jsonContent = [JsonSerializer]::Serialize($requestBody, $options) $content = [StringContent]::new($jsonContent, [Encoding]::UTF8, "application/json") $request = [HttpRequestMessage]::new([HttpMethod]::Post, [Uri]::new([Uri]::new($this._baseUrl), $url)) $request.Content = $content $request.Headers.Add("Accept", "application/json") if (![string]::IsNullOrEmpty($this._accessToken)) { $request.Headers.Authorization = [System.Net.Http.Headers.AuthenticationHeaderValue]::new("Bearer", $this._accessToken) } $response = $this._httpClient.SendAsync($request).GetAwaiter().GetResult() if (!$response.IsSuccessStatusCode) { $errorMessage = $this.FormatErrorMessageAsync($response).GetAwaiter().GetResult() throw [HttpRequestException]::new($errorMessage) } $responseContent = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() if ([string]::IsNullOrEmpty($responseContent)) { throw [HttpRequestException]::new("Response body is null or empty") } $result = [JsonSerializer]::Deserialize($responseContent, $responseType, $this.GetJsonOptions()) if ($null -eq $result) { throw [InfisicalException]::new("Failed to deserialize response content") } return [Task]::FromResult([object]$result) } catch [InfisicalException] { throw } catch { throw [InfisicalException]::new("Error during POST request: $($_.Exception.Message)", $_.Exception) } } [Task[object]] GetAsync([Type]$responseType, [string]$url) { return $this.GetAsync($responseType, $url, $null) } [Task[object]] GetAsync([Type]$responseType, [string]$url, [Dictionary[string, string]]$queryParams) { try { $uriBuilder = [UriBuilder]::new([Uri]::new([Uri]::new($this._baseUrl), $url)) if ($null -ne $queryParams -and $queryParams.Count -gt 0) { $query = [HttpUtility]::ParseQueryString([string]::Empty) foreach ($param in $queryParams.GetEnumerator()) { $query[$param.Key] = $param.Value } $uriBuilder.Query = $query.ToString() } $maxRetries = 3 $attempt = 1 $response = $null while ($attempt -le $maxRetries) { $request = [HttpRequestMessage]::new([HttpMethod]::Get, $uriBuilder.Uri) $request.Headers.Add("Accept", "application/json") if (![string]::IsNullOrEmpty($this._accessToken)) { $request.Headers.Authorization = [System.Net.Http.Headers.AuthenticationHeaderValue]::new("Bearer", $this._accessToken) } try { $response = $this._httpClient.SendAsync($request).GetAwaiter().GetResult() if ($response.IsSuccessStatusCode) { break } else { $code = [int]$response.StatusCode if ($code -lt 500 -and $code -ne 408) { break } } } catch { if ($attempt -eq $maxRetries) { throw } } [System.Threading.Thread]::Sleep([TimeSpan]::FromSeconds([Math]::Pow(2, $attempt))) $attempt++ } if (!$response.IsSuccessStatusCode) { $errorMessage = $this.FormatErrorMessageAsync($response).GetAwaiter().GetResult() throw [HttpRequestException]::new($errorMessage) } $responseContent = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() if ([string]::IsNullOrEmpty($responseContent)) { throw [HttpRequestException]::new("Response body is null or empty") } $result = [JsonSerializer]::Deserialize($responseContent, $responseType, $this.GetJsonOptions()) if ($null -eq $result) { throw [InfisicalException]::new("Failed to deserialize response content") } return [Task]::FromResult([object]$result) } catch [InfisicalException] { throw } catch { throw [InfisicalException]::new("Error during GET request: $($_.Exception.Message)", $_.Exception) } } [Task[object]] PatchAsync([Type]$responseType, [string]$url, [object]$requestBody, [bool]$omitNullValues = $false) { try { $options = if ($omitNullValues) { $this.GetJsonOptionsNullOmit() } else { $this.GetJsonOptions() } $jsonContent = [JsonSerializer]::Serialize($requestBody, $options) $content = [StringContent]::new($jsonContent, [Encoding]::UTF8, "application/json") $request = [HttpRequestMessage]::new([HttpMethod]::new("PATCH"), [Uri]::new([Uri]::new($this._baseUrl), $url)) $request.Content = $content $request.Headers.Add("Accept", "application/json") if (![string]::IsNullOrEmpty($this._accessToken)) { $request.Headers.Authorization = [System.Net.Http.Headers.AuthenticationHeaderValue]::new("Bearer", $this._accessToken) } $response = $this._httpClient.SendAsync($request).GetAwaiter().GetResult() if (!$response.IsSuccessStatusCode) { $errorMessage = $this.FormatErrorMessageAsync($response).GetAwaiter().GetResult() throw [HttpRequestException]::new($errorMessage) } $responseContent = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() if ([string]::IsNullOrEmpty($responseContent)) { throw [HttpRequestException]::new("Response body is null or empty") } $result = [JsonSerializer]::Deserialize($responseContent, $responseType, $this.GetJsonOptions()) if ($null -eq $result) { throw [InfisicalException]::new("Failed to deserialize response content") } return [Task]::FromResult([object]$result) } catch [InfisicalException] { throw } catch { throw [InfisicalException]::new("Error during PATCH request: $($_.Exception.Message)", $_.Exception) } } [Task[object]] DeleteAsync([Type]$responseType, [string]$url, [object]$requestBody, [bool]$omitNullValues = $false) { try { $options = if ($omitNullValues) { $this.GetJsonOptionsNullOmit() } else { $this.GetJsonOptions() } $jsonContent = [JsonSerializer]::Serialize($requestBody, $options) $content = [StringContent]::new($jsonContent, [Encoding]::UTF8, "application/json") $request = [HttpRequestMessage]::new([HttpMethod]::Delete, [Uri]::new([Uri]::new($this._baseUrl), $url)) $request.Content = $content $request.Headers.Add("Accept", "application/json") if (![string]::IsNullOrEmpty($this._accessToken)) { $request.Headers.Authorization = [System.Net.Http.Headers.AuthenticationHeaderValue]::new("Bearer", $this._accessToken) } $response = $this._httpClient.SendAsync($request).GetAwaiter().GetResult() if (!$response.IsSuccessStatusCode) { $errorMessage = $this.FormatErrorMessageAsync($response).GetAwaiter().GetResult() throw [HttpRequestException]::new($errorMessage) } $responseContent = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() if ([string]::IsNullOrEmpty($responseContent)) { throw [HttpRequestException]::new("Response body is null or empty") } $result = [JsonSerializer]::Deserialize($responseContent, $responseType, $this.GetJsonOptions()) if ($null -eq $result) { throw [InfisicalException]::new("Failed to deserialize response content") } return [Task]::FromResult([object]$result) } catch [InfisicalException] { throw } catch { throw [InfisicalException]::new("Error during DELETE request: $($_.Exception.Message)", $_.Exception) } } [void] Dispose() { if ($null -ne $this._httpClient) { $this._httpClient.Dispose() } } } class QueryBuilder { hidden [Dictionary[string, string]] $_params = [Dictionary[string, string]]::new() [QueryBuilder] Add([string]$key, [object]$value) { [ValidateNotNullOrWhiteSpace()][string]$key = $key if ($null -ne $value) { $this._params[$key] = $value.ToString() } return $this } [Dictionary[string, string]] Build() { return [Dictionary[string, string]]::new($this._params) } } |