diff --git a/src/main/java/com/skyflow/ConnectionClient.java b/src/main/java/com/skyflow/ConnectionClient.java index d67122ad..77ebe96d 100644 --- a/src/main/java/com/skyflow/ConnectionClient.java +++ b/src/main/java/com/skyflow/ConnectionClient.java @@ -36,11 +36,11 @@ protected void setCommonCredentials(Credentials commonCredentials) throws Skyflo prioritiseCredentials(); } - protected void updateConnectionConfig(ConnectionConfig connectionConfig) throws SkyflowException { + protected void updateConnectionConfig() throws SkyflowException { prioritiseCredentials(); } - protected void setBearerToken() throws SkyflowException { + protected synchronized void setBearerToken() throws SkyflowException { prioritiseCredentials(); Validations.validateCredentials(this.finalCredentials); if (this.finalCredentials.getApiKey() != null) { @@ -89,7 +89,7 @@ private void prioritiseCredentials() throws SkyflowException { } catch (SkyflowException e) { throw e; } catch (Exception e) { - throw new RuntimeException(e); + throw new SkyflowException(ErrorCode.SERVER_ERROR.getCode(), ErrorMessage.EmptyCredentials.getMessage()); } } diff --git a/src/main/java/com/skyflow/Skyflow.java b/src/main/java/com/skyflow/Skyflow.java index eba8d6fd..8b5449f9 100644 --- a/src/main/java/com/skyflow/Skyflow.java +++ b/src/main/java/com/skyflow/Skyflow.java @@ -238,7 +238,7 @@ public SkyflowClientBuilder updateConnectionConfig(ConnectionConfig connectionCo Validations.validateConnectionConfig(connectionConfig); if (this.connectionsMap.containsKey(connectionConfig.getConnectionId())) { ConnectionConfig updatedConfig = findAndUpdateConnectionConfig(connectionConfig); - this.connectionsMap.get(updatedConfig.getConnectionId()).updateConnectionConfig(connectionConfig); + this.connectionsMap.get(updatedConfig.getConnectionId()).updateConnectionConfig(); } else { LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST.getLog(), connectionConfig.getConnectionId() diff --git a/src/main/java/com/skyflow/VaultClient.java b/src/main/java/com/skyflow/VaultClient.java index 1d5e5d74..5dabf190 100644 --- a/src/main/java/com/skyflow/VaultClient.java +++ b/src/main/java/com/skyflow/VaultClient.java @@ -1,5 +1,18 @@ package com.skyflow; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; import com.skyflow.enums.DetectEntities; @@ -10,8 +23,24 @@ import com.skyflow.generated.rest.ApiClient; import com.skyflow.generated.rest.ApiClientBuilder; import com.skyflow.generated.rest.resources.files.FilesClient; -import com.skyflow.generated.rest.resources.files.requests.*; -import com.skyflow.generated.rest.resources.files.types.*; +import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileAudioRequestDeidentifyAudio; +import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileDocumentPdfRequestDeidentifyPdf; +import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileImageRequestDeidentifyImage; +import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyDocument; +import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyPresentation; +import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifySpreadsheet; +import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyStructuredText; +import com.skyflow.generated.rest.resources.files.types.DeidentifyFileAudioRequestDeidentifyAudioEntityTypesItem; +import com.skyflow.generated.rest.resources.files.types.DeidentifyFileAudioRequestDeidentifyAudioOutputTranscription; +import com.skyflow.generated.rest.resources.files.types.DeidentifyFileDocumentPdfRequestDeidentifyPdfEntityTypesItem; +import com.skyflow.generated.rest.resources.files.types.DeidentifyFileImageRequestDeidentifyImageEntityTypesItem; +import com.skyflow.generated.rest.resources.files.types.DeidentifyFileImageRequestDeidentifyImageMaskingMethod; +import com.skyflow.generated.rest.resources.files.types.DeidentifyFileRequestDeidentifyDocumentEntityTypesItem; +import com.skyflow.generated.rest.resources.files.types.DeidentifyFileRequestDeidentifyPresentationEntityTypesItem; +import com.skyflow.generated.rest.resources.files.types.DeidentifyFileRequestDeidentifySpreadsheetEntityTypesItem; +import com.skyflow.generated.rest.resources.files.types.DeidentifyFileRequestDeidentifyStructuredTextEntityTypesItem; +import com.skyflow.generated.rest.resources.files.types.DeidentifyFileRequestDeidentifyTextEntityTypesItem; +import com.skyflow.generated.rest.resources.files.types.DeidentifyFileRequestEntityTypesItem; import com.skyflow.generated.rest.resources.query.QueryClient; import com.skyflow.generated.rest.resources.records.RecordsClient; import com.skyflow.generated.rest.resources.records.requests.RecordServiceBatchOperationBody; @@ -24,8 +53,40 @@ import com.skyflow.generated.rest.resources.tokens.TokensClient; import com.skyflow.generated.rest.resources.tokens.requests.V1DetokenizePayload; import com.skyflow.generated.rest.resources.tokens.requests.V1TokenizePayload; +import com.skyflow.generated.rest.types.BatchRecordMethod; +import com.skyflow.generated.rest.types.DeidentifyStringResponse; +import com.skyflow.generated.rest.types.FileData; +import com.skyflow.generated.rest.types.FileDataDataFormat; +import com.skyflow.generated.rest.types.FileDataDeidentifyAudio; +import com.skyflow.generated.rest.types.FileDataDeidentifyAudioDataFormat; +import com.skyflow.generated.rest.types.FileDataDeidentifyDocument; +import com.skyflow.generated.rest.types.FileDataDeidentifyDocumentDataFormat; +import com.skyflow.generated.rest.types.FileDataDeidentifyImage; +import com.skyflow.generated.rest.types.FileDataDeidentifyImageDataFormat; +import com.skyflow.generated.rest.types.FileDataDeidentifyPdf; +import com.skyflow.generated.rest.types.FileDataDeidentifyPresentation; +import com.skyflow.generated.rest.types.FileDataDeidentifyPresentationDataFormat; +import com.skyflow.generated.rest.types.FileDataDeidentifySpreadsheet; +import com.skyflow.generated.rest.types.FileDataDeidentifySpreadsheetDataFormat; +import com.skyflow.generated.rest.types.FileDataDeidentifyStructuredText; +import com.skyflow.generated.rest.types.FileDataDeidentifyStructuredTextDataFormat; +import com.skyflow.generated.rest.types.FileDataDeidentifyText; +import com.skyflow.generated.rest.types.Format; +import com.skyflow.generated.rest.types.FormatMaskedItem; +import com.skyflow.generated.rest.types.FormatPlaintextItem; +import com.skyflow.generated.rest.types.FormatRedactedItem; +import com.skyflow.generated.rest.types.ShiftDates; +import com.skyflow.generated.rest.types.ShiftDatesEntityTypesItem; +import com.skyflow.generated.rest.types.StringResponseEntities; +import com.skyflow.generated.rest.types.TokenTypeMapping; +import com.skyflow.generated.rest.types.TokenTypeMappingEntityOnlyItem; +import com.skyflow.generated.rest.types.TokenTypeMappingEntityUnqCounterItem; +import com.skyflow.generated.rest.types.TokenTypeMappingVaultTokenItem; import com.skyflow.generated.rest.types.Transformations; -import com.skyflow.generated.rest.types.*; +import com.skyflow.generated.rest.types.V1BatchRecord; +import com.skyflow.generated.rest.types.V1DetokenizeRecordRequest; +import com.skyflow.generated.rest.types.V1FieldRecords; +import com.skyflow.generated.rest.types.V1TokenizeRecordRequest; import com.skyflow.logs.InfoLogs; import com.skyflow.serviceaccount.util.Token; import com.skyflow.utils.Constants; @@ -35,25 +96,25 @@ import com.skyflow.vault.data.FileUploadRequest; import com.skyflow.vault.data.InsertRequest; import com.skyflow.vault.data.UpdateRequest; +import com.skyflow.vault.detect.AudioBleep; import com.skyflow.vault.detect.DeidentifyFileRequest; -import com.skyflow.vault.detect.*; +import com.skyflow.vault.detect.DeidentifyTextRequest; +import com.skyflow.vault.detect.DeidentifyTextResponse; +import com.skyflow.vault.detect.EntityInfo; +import com.skyflow.vault.detect.ReidentifyTextRequest; +import com.skyflow.vault.detect.TextIndex; +import com.skyflow.vault.detect.TokenFormat; import com.skyflow.vault.tokens.ColumnValue; import com.skyflow.vault.tokens.DetokenizeData; import com.skyflow.vault.tokens.DetokenizeRequest; import com.skyflow.vault.tokens.TokenizeRequest; + import io.github.cdimascio.dotenv.Dotenv; import io.github.cdimascio.dotenv.DotenvException; import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; import okhttp3.Request; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.*; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - public class VaultClient { private final VaultConfig vaultConfig; @@ -64,7 +125,6 @@ public class VaultClient { private Credentials commonCredentials; private Credentials finalCredentials; private String token; - private String apiKey; protected VaultClient(VaultConfig vaultConfig, Credentials credentials) { super(); @@ -232,15 +292,14 @@ protected File getFileForFileUpload(FileUploadRequest fileUploadRequest) throws return null; } - protected void setBearerToken() throws SkyflowException { + protected synchronized void setBearerToken() throws SkyflowException { prioritiseCredentials(); Validations.validateCredentials(this.finalCredentials); if (this.finalCredentials.getApiKey() != null) { LogUtil.printInfoLog(InfoLogs.REUSE_API_KEY.getLog()); token = this.finalCredentials.getApiKey(); - } else if (token == null || token.trim().isEmpty()) { - token = Utils.generateBearerToken(this.finalCredentials); } else if (Token.isExpired(token)) { + // Token.isExpired(null/empty) returns true, so this branch also covers first-time generation. LogUtil.printInfoLog(InfoLogs.BEARER_TOKEN_EXPIRED.getLog()); token = Utils.generateBearerToken(this.finalCredentials); } else { @@ -283,46 +342,17 @@ protected DeidentifyStringRequest getDeidentifyStringRequest(DeidentifyTextReque TokenFormat tokenFormat = deIdentifyTextRequest.getTokenFormat(); - Optional> vaultToken = Optional.empty(); - Optional> entityTypes = Optional.empty(); - Optional> entityUniqueCounter = Optional.empty(); Optional> allowRegex = Optional.ofNullable(deIdentifyTextRequest.getAllowRegexList()); Optional> restrictRegex = Optional.ofNullable(deIdentifyTextRequest.getRestrictRegexList()); Optional transformations = Optional.ofNullable(getTransformations(deIdentifyTextRequest.getTransformations())); - if (tokenFormat != null) { - if (tokenFormat.getVaultToken() != null && !tokenFormat.getVaultToken().isEmpty()) { - vaultToken = Optional.of( - tokenFormat.getVaultToken().stream() - .map(detectEntity -> TokenTypeMappingVaultTokenItem.valueOf(detectEntity.name())) - .collect(Collectors.toList()) - ); - } - - - if (tokenFormat.getEntityOnly() != null && !tokenFormat.getEntityOnly().isEmpty()) { - entityTypes = Optional.of( - tokenFormat.getEntityOnly().stream() - .map(detectEntity -> TokenTypeMappingEntityOnlyItem.valueOf(detectEntity.name())) - .collect(Collectors.toList()) - ); - } - - - if (tokenFormat.getEntityUniqueCounter() != null && !tokenFormat.getEntityUniqueCounter().isEmpty()) { - entityUniqueCounter = Optional.of( - tokenFormat.getEntityUniqueCounter().stream() - .map(detectEntity -> TokenTypeMappingEntityUnqCounterItem.valueOf(detectEntity.name())) - .collect(Collectors.toList()) - ); - } - - } - TokenTypeMapping tokenType = TokenTypeMapping.builder() - .vaultToken(vaultToken) - .entityOnly(entityTypes) - .entityUnqCounter(entityUniqueCounter) + .vaultToken(buildTokenEntities( + tokenFormat == null ? null : tokenFormat.getVaultToken(), TokenTypeMappingVaultTokenItem.class)) + .entityOnly(buildTokenEntities( + tokenFormat == null ? null : tokenFormat.getEntityOnly(), TokenTypeMappingEntityOnlyItem.class)) + .entityUnqCounter(buildTokenEntities( + tokenFormat == null ? null : tokenFormat.getEntityUniqueCounter(), TokenTypeMappingEntityUnqCounterItem.class)) .build(); @@ -338,9 +368,9 @@ protected DeidentifyStringRequest getDeidentifyStringRequest(DeidentifyTextReque } protected ReidentifyStringRequest getReidentifyStringRequest(ReidentifyTextRequest reidentifyTextRequest, String vaultId) throws SkyflowException { - Optional> maskEntities = null; - Optional> redactedEntities = null; - Optional> plaintextEntities = null; + Optional> maskEntities = Optional.empty(); + Optional> redactedEntities = Optional.empty(); + Optional> plaintextEntities = Optional.empty(); if (reidentifyTextRequest.getMaskedEntities() != null) { maskEntities = Optional.of(reidentifyTextRequest.getMaskedEntities().stream() @@ -386,11 +416,7 @@ private EntityInfo convertDetectedEntityToEntityInfo(StringResponseEntities dete ); Map entityScores = detectedEntity.getEntityScores() - .map(doubleMap -> doubleMap.entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - Map.Entry::getValue - ))) + .>map(HashMap::new) .orElse(Collections.emptyMap()); @@ -409,7 +435,7 @@ private Transformations getTransformations(com.skyflow.vault.detect.Transformati return null; } - Optional> entityTypes = null; + Optional> entityTypes = Optional.empty(); if (!transformations.getShiftDates().getEntities().isEmpty()) { entityTypes = Optional.of(transformations.getShiftDates().getEntities().stream() .map(entity -> ShiftDatesEntityTypesItem.valueOf(entity.name())) @@ -428,7 +454,9 @@ private Transformations getTransformations(com.skyflow.vault.detect.Transformati } private > List getEntityTypes(List entities, Class type) { - if (entities == null) return Collections.emptyList(); + if (entities == null) { + return Collections.emptyList(); + } return entities.stream() .map(e -> Enum.valueOf(type, e.name())) @@ -441,49 +469,25 @@ protected com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequ TokenFormat tokenFormat = request.getTokenFormat(); - Optional> entityTypes = Optional.empty(); - Optional> entityUniqueCounter = Optional.empty(); Optional> allowRegex = Optional.ofNullable(request.getAllowRegexList()); Optional> restrictRegex = Optional.ofNullable(request.getRestrictRegexList()); Optional transformations = Optional.ofNullable(getTransformations(request.getTransformations())); - if (tokenFormat != null) { - - if (tokenFormat.getEntityOnly() != null && !tokenFormat.getEntityOnly().isEmpty()) { - entityTypes = Optional.of(tokenFormat.getEntityOnly().stream() - .map(detectEntity -> TokenTypeMappingEntityOnlyItem.valueOf(detectEntity.name())) - .collect(Collectors.toList())); - } - - if (tokenFormat.getEntityUniqueCounter() != null && !tokenFormat.getEntityUniqueCounter().isEmpty()) { - entityUniqueCounter = Optional.of(tokenFormat.getEntityUniqueCounter().stream() - .map(detectEntity -> TokenTypeMappingEntityUnqCounterItem.valueOf(detectEntity.name())) - .collect(Collectors.toList())); - } - } - - TokenTypeMapping tokenType = TokenTypeMapping.builder() - .entityOnly(entityTypes) - .entityUnqCounter(entityUniqueCounter) - .build(); + TokenTypeMapping tokenType = buildTokenType(tokenFormat); FileDataDeidentifyText file = FileDataDeidentifyText.builder() .base64(base64Content) .build(); - // Build the final request - com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyText req = - com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyText.builder() - .file(file) - .vaultId(vaultId) - .entityTypes(mappedEntityTypes) - .tokenType(tokenType) - .allowRegex(allowRegex) - .restrictRegex(restrictRegex) - .transformations(transformations) - .build(); - - return req; + return com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyText.builder() + .file(file) + .vaultId(vaultId) + .entityTypes(mappedEntityTypes) + .tokenType(tokenType) + .allowRegex(allowRegex) + .restrictRegex(restrictRegex) + .transformations(transformations) + .build(); } @@ -492,57 +496,31 @@ protected DeidentifyFileAudioRequestDeidentifyAudio getDeidentifyAudioRequest(De TokenFormat tokenFormat = request.getTokenFormat(); - Optional> entityTypes = Optional.empty(); - Optional> entityUniqueCounter = Optional.empty(); Optional> allowRegex = Optional.ofNullable(request.getAllowRegexList()); Optional> restrictRegex = Optional.ofNullable(request.getRestrictRegexList()); Optional transformations = Optional.ofNullable(getTransformations(request.getTransformations())); - if (tokenFormat != null) { - if (tokenFormat.getEntityOnly() != null && !tokenFormat.getEntityOnly().isEmpty()) { - entityTypes = Optional.of( - tokenFormat.getEntityOnly().stream() - .map(detectEntity -> DeidentifyFileAudioRequestDeidentifyAudioEntityTypesItem.valueOf(detectEntity.name())) - .collect(Collectors.toList()) - ).map(list -> (List) (List) list); - } - - - if (tokenFormat.getEntityUniqueCounter() != null && !tokenFormat.getEntityUniqueCounter().isEmpty()) { - entityUniqueCounter = Optional.of( - (List) (List) - tokenFormat.getEntityUniqueCounter().stream() - .map(detectEntity -> DeidentifyFileAudioRequestDeidentifyAudioEntityTypesItem.valueOf(detectEntity.name())) - .collect(Collectors.toList()) - ); - } - - } - - TokenTypeMapping tokenType = TokenTypeMapping.builder() - .entityOnly(entityTypes) - .entityUnqCounter(entityUniqueCounter) - .build(); + TokenTypeMapping tokenType = buildTokenType(tokenFormat); FileDataDeidentifyAudio deidentifyAudioRequestFile = FileDataDeidentifyAudio.builder().base64(base64Content).dataFormat(mapAudioDataFormat(dataFormat)).build(); DetectOutputTranscriptions transcription = request.getOutputTranscription(); - Optional outputTranscriptionType = null; + Optional outputTranscriptionType = Optional.empty(); if (transcription != null) { outputTranscriptionType = Optional.of(DeidentifyFileAudioRequestDeidentifyAudioOutputTranscription.valueOf(transcription.name())); } + AudioBleep bleep = request.getBleep(); + return DeidentifyFileAudioRequestDeidentifyAudio.builder() .file(deidentifyAudioRequestFile) .vaultId(vaultId) .allowRegex(allowRegex) .restrictRegex(restrictRegex) .entityTypes(mappedEntityTypes) - .bleepFrequency(request.getBleep() != null - ? (int) Math.round(request.getBleep().getFrequency()) - : null) - .bleepGain(request.getBleep() != null ? (int) Math.round(request.getBleep().getGain()) : null) - .bleepStartPadding(request.getBleep() != null ? (float) Math.round(request.getBleep().getStartPadding()) : null) - .bleepStopPadding(request.getBleep() != null ? (float) Math.round(request.getBleep().getStopPadding()) : null) + .bleepFrequency(bleep != null ? (int) Math.round(bleep.getFrequency()) : null) + .bleepGain(bleep != null ? (int) Math.round(bleep.getGain()) : null) + .bleepStartPadding(bleep != null ? (float) Math.round(bleep.getStartPadding()) : null) + .bleepStopPadding(bleep != null ? (float) Math.round(bleep.getStopPadding()) : null) .outputProcessedAudio(request.getOutputProcessedAudio()) .outputTranscription(outputTranscriptionType) .tokenType(tokenType) @@ -554,36 +532,10 @@ protected DeidentifyFileDocumentPdfRequestDeidentifyPdf getDeidentifyPdfRequest( List mappedEntityTypes = getEntityTypes(request.getEntities(), DeidentifyFileDocumentPdfRequestDeidentifyPdfEntityTypesItem.class); TokenFormat tokenFormat = request.getTokenFormat(); - Optional> entityTypes = Optional.empty(); - Optional> entityUniqueCounter = Optional.empty(); Optional> allowRegex = Optional.ofNullable(request.getAllowRegexList()); Optional> restrictRegex = Optional.ofNullable(request.getRestrictRegexList()); - if (tokenFormat != null) { - if (tokenFormat.getEntityOnly() != null && !tokenFormat.getEntityOnly().isEmpty()) { - entityTypes = Optional.of( - (List) (List) - tokenFormat.getEntityOnly().stream() - .map(detectEntity -> DeidentifyFileDocumentPdfRequestDeidentifyPdfEntityTypesItem.valueOf(detectEntity.name())) - .collect(Collectors.toList()) - ); - } - - if (tokenFormat.getEntityUniqueCounter() != null && !tokenFormat.getEntityUniqueCounter().isEmpty()) { - entityUniqueCounter = Optional.of( - (List) (List) - tokenFormat.getEntityUniqueCounter().stream() - .map(detectEntity -> DeidentifyFileDocumentPdfRequestDeidentifyPdfEntityTypesItem.valueOf(detectEntity.name())) - .collect(Collectors.toList()) - ); - } - - } - - TokenTypeMapping tokenType = TokenTypeMapping.builder() - .entityOnly(entityTypes) - .entityUnqCounter(entityUniqueCounter) - .build(); + TokenTypeMapping tokenType = buildTokenType(tokenFormat); FileDataDeidentifyPdf file = FileDataDeidentifyPdf.builder() .base64(base64Content) @@ -603,12 +555,10 @@ protected DeidentifyFileImageRequestDeidentifyImage getDeidentifyImageRequest(De List mappedEntityTypes = getEntityTypes(request.getEntities(), DeidentifyFileImageRequestDeidentifyImageEntityTypesItem.class); TokenFormat tokenFormat = request.getTokenFormat(); - Optional> entityTypes = Optional.empty(); - Optional> entityUniqueCounter = Optional.empty(); Optional> allowRegex = Optional.ofNullable(request.getAllowRegexList()); Optional> restrictRegex = Optional.ofNullable(request.getRestrictRegexList()); - TokenTypeMapping tokenType = buildTokenType(tokenFormat, entityTypes, entityUniqueCounter); + TokenTypeMapping tokenType = buildTokenType(tokenFormat); FileDataDeidentifyImage file = FileDataDeidentifyImage.builder() .base64(base64Content) @@ -637,12 +587,10 @@ protected DeidentifyFileRequestDeidentifyPresentation getDeidentifyPresentationR List mappedEntityTypes = getEntityTypes(request.getEntities(), DeidentifyFileRequestDeidentifyPresentationEntityTypesItem.class); TokenFormat tokenFormat = request.getTokenFormat(); - Optional> entityTypes = Optional.empty(); - Optional> entityUniqueCounter = Optional.empty(); Optional> allowRegex = Optional.ofNullable(request.getAllowRegexList()); Optional> restrictRegex = Optional.ofNullable(request.getRestrictRegexList()); - TokenTypeMapping tokenType = buildTokenType(tokenFormat, entityTypes, entityUniqueCounter); + TokenTypeMapping tokenType = buildTokenType(tokenFormat); FileDataDeidentifyPresentation file = FileDataDeidentifyPresentation.builder() .base64(base64Content) @@ -663,12 +611,10 @@ protected DeidentifyFileRequestDeidentifySpreadsheet getDeidentifySpreadsheetReq List mappedEntityTypes = getEntityTypes(request.getEntities(), DeidentifyFileRequestDeidentifySpreadsheetEntityTypesItem.class); TokenFormat tokenFormat = request.getTokenFormat(); - Optional> entityTypes = Optional.empty(); - Optional> entityUniqueCounter = Optional.empty(); Optional> allowRegex = Optional.ofNullable(request.getAllowRegexList()); Optional> restrictRegex = Optional.ofNullable(request.getRestrictRegexList()); - TokenTypeMapping tokenType = buildTokenType(tokenFormat, entityTypes, entityUniqueCounter); + TokenTypeMapping tokenType = buildTokenType(tokenFormat); FileDataDeidentifySpreadsheet file = FileDataDeidentifySpreadsheet.builder() .base64(base64Content) @@ -689,13 +635,11 @@ protected DeidentifyFileRequestDeidentifyStructuredText getDeidentifyStructuredT List mappedEntityTypes = getEntityTypes(request.getEntities(), DeidentifyFileRequestDeidentifyStructuredTextEntityTypesItem.class); TokenFormat tokenFormat = request.getTokenFormat(); - Optional> entityTypes = Optional.empty(); - Optional> entityUniqueCounter = Optional.empty(); Optional> allowRegex = Optional.ofNullable(request.getAllowRegexList()); Optional> restrictRegex = Optional.ofNullable(request.getRestrictRegexList()); Optional transformations = Optional.ofNullable(getTransformations(request.getTransformations())); - TokenTypeMapping tokenType = buildTokenType(tokenFormat, entityTypes, entityUniqueCounter); + TokenTypeMapping tokenType = buildTokenType(tokenFormat); FileDataDeidentifyStructuredText file = FileDataDeidentifyStructuredText.builder() .base64(base64Content) @@ -718,12 +662,10 @@ protected DeidentifyFileRequestDeidentifyDocument getDeidentifyDocumentRequest(D getEntityTypes(request.getEntities(), DeidentifyFileRequestDeidentifyDocumentEntityTypesItem.class); TokenFormat tokenFormat = request.getTokenFormat(); - Optional> entityTypes = Optional.empty(); - Optional> entityUniqueCounter = Optional.empty(); Optional> allowRegex = Optional.ofNullable(request.getAllowRegexList()); Optional> restrictRegex = Optional.ofNullable(request.getRestrictRegexList()); - TokenTypeMapping tokenType = buildTokenType(tokenFormat, entityTypes, entityUniqueCounter); + TokenTypeMapping tokenType = buildTokenType(tokenFormat); FileDataDeidentifyDocument file = FileDataDeidentifyDocument.builder() .base64(base64Content) @@ -748,30 +690,11 @@ protected com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequ TokenFormat tokenFormat = request.getTokenFormat(); - Optional> entityTypes = Optional.empty(); - Optional> entityUniqueCounter = Optional.empty(); Optional> allowRegex = Optional.ofNullable(request.getAllowRegexList()); Optional> restrictRegex = Optional.ofNullable(request.getRestrictRegexList()); Optional transformations = Optional.ofNullable(getTransformations(request.getTransformations())); - if (tokenFormat != null) { - if (tokenFormat.getEntityOnly() != null && !tokenFormat.getEntityOnly().isEmpty()) { - entityTypes = Optional.of(tokenFormat.getEntityOnly().stream() - .map(detectEntity -> TokenTypeMappingEntityOnlyItem.valueOf(detectEntity.name())) - .collect(Collectors.toList())); - } - - if (tokenFormat.getEntityUniqueCounter() != null && !tokenFormat.getEntityUniqueCounter().isEmpty()) { - entityUniqueCounter = Optional.of(tokenFormat.getEntityUniqueCounter().stream() - .map(detectEntity -> TokenTypeMappingEntityUnqCounterItem.valueOf(detectEntity.name())) - .collect(Collectors.toList())); - } - } - - TokenTypeMapping tokenType = TokenTypeMapping.builder() - .entityOnly(entityTypes) - .entityUnqCounter(entityUniqueCounter) - .build(); + TokenTypeMapping tokenType = buildTokenType(tokenFormat); FileData file = FileData.builder() @@ -790,29 +713,20 @@ protected com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequ .build(); } - private TokenTypeMapping buildTokenType(TokenFormat tokenFormat, - Optional> entityTypes, - Optional> entityUniqueCounter) { - - if (tokenFormat != null) { - if (tokenFormat.getEntityOnly() != null && !tokenFormat.getEntityOnly().isEmpty()) { - entityTypes = Optional.of(tokenFormat.getEntityOnly().stream() - .map(detectEntity -> TokenTypeMappingEntityOnlyItem.valueOf(detectEntity.name())) - .collect(Collectors.toList())); - } - - if (tokenFormat.getEntityUniqueCounter() != null && !tokenFormat.getEntityUniqueCounter().isEmpty()) { - entityUniqueCounter = Optional.of(tokenFormat.getEntityUniqueCounter().stream() - .map(detectEntity -> TokenTypeMappingEntityUnqCounterItem.valueOf(detectEntity.name())) - .collect(Collectors.toList())); - } - } - + private TokenTypeMapping buildTokenType(TokenFormat tokenFormat) { return TokenTypeMapping.builder() - .entityOnly(entityTypes) - .entityUnqCounter(entityUniqueCounter) + .entityOnly(buildTokenEntities( + tokenFormat == null ? null : tokenFormat.getEntityOnly(), TokenTypeMappingEntityOnlyItem.class)) + .entityUnqCounter(buildTokenEntities( + tokenFormat == null ? null : tokenFormat.getEntityUniqueCounter(), TokenTypeMappingEntityUnqCounterItem.class)) .build(); + } + private > Optional> buildTokenEntities(List entities, Class type) { + if (entities == null || entities.isEmpty()) { + return Optional.empty(); + } + return Optional.of(getEntityTypes(entities, type)); } @@ -839,7 +753,8 @@ private void updateVaultURL() { private void updateExecutorInHTTP() { if (sharedHttpClient == null) { sharedHttpClient = new OkHttpClient.Builder() - .connectionPool(new ConnectionPool(10, 1, TimeUnit.MINUTES)) + .connectionPool(new ConnectionPool(Constants.HTTP_MAX_IDLE_CONNECTIONS, + Constants.HTTP_KEEP_ALIVE_DURATION_MINUTES, TimeUnit.MINUTES)) .addInterceptor(chain -> { Request requestWithAuth = chain.request().newBuilder() .header("Authorization", "Bearer " + this.token) @@ -871,7 +786,6 @@ private void prioritiseCredentials() throws SkyflowException { } if (original != null && !original.equals(this.finalCredentials)) { token = null; - apiKey = null; } } catch (DotenvException e) { throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), @@ -879,7 +793,7 @@ private void prioritiseCredentials() throws SkyflowException { } catch (SkyflowException e) { throw e; } catch (Exception e) { - throw new RuntimeException(e); + throw new SkyflowException(ErrorCode.SERVER_ERROR.getCode(), ErrorMessage.EmptyCredentials.getMessage()); } } } diff --git a/src/main/java/com/skyflow/errors/SkyflowException.java b/src/main/java/com/skyflow/errors/SkyflowException.java index 6fedf9c3..ca10ad5f 100644 --- a/src/main/java/com/skyflow/errors/SkyflowException.java +++ b/src/main/java/com/skyflow/errors/SkyflowException.java @@ -4,6 +4,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; import com.skyflow.utils.Constants; import java.util.List; @@ -95,7 +96,20 @@ public SkyflowException(int httpCode, Throwable cause, Map> } private void setResponseBody(String responseBody, Map> responseHeaders) { - this.responseBody = JsonParser.parseString(responseBody).getAsJsonObject(); + JsonElement parsedBody; + try { + parsedBody = JsonParser.parseString(responseBody); + } catch (JsonSyntaxException e) { + // Malformed / unparseable JSON: use a generic message instead of echoing the raw body. + this.message = ErrorMessage.ErrorOccurred.getMessage(); + return; + } + if (!parsedBody.isJsonObject()) { + // Valid JSON but not the expected object shape: do not echo the raw body. + this.message = ErrorMessage.ErrorOccurred.getMessage(); + return; + } + this.responseBody = parsedBody.getAsJsonObject(); if (this.responseBody.get("error") != null) { setGrpcCode(); setHttpStatus(); diff --git a/src/main/java/com/skyflow/utils/Constants.java b/src/main/java/com/skyflow/utils/Constants.java index aa3a3f14..53910846 100644 --- a/src/main/java/com/skyflow/utils/Constants.java +++ b/src/main/java/com/skyflow/utils/Constants.java @@ -38,9 +38,19 @@ public final class Constants { public static final String CURLY_PLACEHOLDER = "{%s}"; public static final String EMPTY_STRING = ""; public static final String QUOTE = "\""; + public static final int HTTP_MAX_IDLE_CONNECTIONS = 10; + public static final long HTTP_KEEP_ALIVE_DURATION_MINUTES = 1; public static final class HttpUtilityExtra { - public static final String SDK_GENERATED_PREFIX = "SDK-Generated-"; + public static final int HTTP_SUCCESS_STATUS_MAX = 299; + public static final String HEADER_ACCEPT = "Accept"; + public static final String ACCEPT_ALL = "*/*"; + public static final String HEADER_CONTENT_TYPE = "content-type"; + public static final String HEADER_REQUEST_ID = "x-request-id"; + public static final String CONTENT_TYPE_JSON = "application/json"; + public static final String CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"; + public static final String CONTENT_TYPE_MULTIPART = "multipart/form-data"; + public static final String EMPTY_JSON_BODY = "{}"; private HttpUtilityExtra() {} } diff --git a/src/main/java/com/skyflow/utils/HttpUtility.java b/src/main/java/com/skyflow/utils/HttpUtility.java index 671e2415..364508ef 100644 --- a/src/main/java/com/skyflow/utils/HttpUtility.java +++ b/src/main/java/com/skyflow/utils/HttpUtility.java @@ -2,9 +2,14 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; -import java.io.*; +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; @@ -12,92 +17,77 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.UUID; public final class HttpUtility { private static final String LINE_FEED = "\r\n"; - private static String requestID; + // Per-thread so concurrent requests do not race on a single shared slot. + private static final ThreadLocal REQUEST_ID = new ThreadLocal<>(); public static String getRequestID() { - return requestID; + return REQUEST_ID.get(); } public static String sendRequest(String method, URL url, JsonObject params, Map headers) throws IOException, SkyflowException { HttpURLConnection connection = null; BufferedReader in = null; - StringBuffer response = null; + StringBuilder response = null; String boundary = String.valueOf(System.currentTimeMillis()); try { connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod(method); - connection.setRequestProperty("Accept", "*/*"); - boolean hasContentType = headers != null && headers.containsKey("content-type"); + connection.setRequestProperty(Constants.HttpUtilityExtra.HEADER_ACCEPT, Constants.HttpUtilityExtra.ACCEPT_ALL); + boolean hasContentType = headers != null && headers.containsKey(Constants.HttpUtilityExtra.HEADER_CONTENT_TYPE); if (!hasContentType && params != null && !params.isEmpty()) { - connection.setRequestProperty("content-type", "application/json"); + connection.setRequestProperty(Constants.HttpUtilityExtra.HEADER_CONTENT_TYPE, Constants.HttpUtilityExtra.CONTENT_TYPE_JSON); } if (headers != null && !headers.isEmpty()) { - for (Map.Entry entry : headers.entrySet()) + for (Map.Entry entry : headers.entrySet()) { connection.setRequestProperty(entry.getKey(), entry.getValue()); + } // append dynamic boundary if content-type is multipart/form-data - if (headers.containsKey("content-type")) { - if (Objects.equals(headers.get("content-type"), "multipart/form-data")) { - connection.setRequestProperty("content-type", "multipart/form-data; boundary=" + boundary); - } + if (Constants.HttpUtilityExtra.CONTENT_TYPE_MULTIPART.equals(headers.get(Constants.HttpUtilityExtra.HEADER_CONTENT_TYPE))) { + connection.setRequestProperty(Constants.HttpUtilityExtra.HEADER_CONTENT_TYPE, + Constants.HttpUtilityExtra.CONTENT_TYPE_MULTIPART + "; boundary=" + boundary); } } if (params != null && !params.isEmpty()) { connection.setDoOutput(true); try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) { - byte[] input = null; - String requestContentType = connection.getRequestProperty("content-type"); - - if (requestContentType != null && requestContentType.contains("application/x-www-form-urlencoded")) { - input = formatJsonToFormEncodedString(params).getBytes(StandardCharsets.UTF_8); - } else if (requestContentType != null && requestContentType.contains("multipart/form-data")) { - input = formatJsonToMultiPartFormDataString(params, boundary).getBytes(StandardCharsets.UTF_8); - } else { - input = params.toString().getBytes(StandardCharsets.UTF_8); - } - + byte[] input = encodeRequestBody(params, connection.getRequestProperty(Constants.HttpUtilityExtra.HEADER_CONTENT_TYPE), boundary); wr.write(input, 0, input.length); wr.flush(); } } int httpCode = connection.getResponseCode(); - String requestID = connection.getHeaderField("x-request-id"); - if (requestID != null) { - HttpUtility.requestID = requestID.split(",")[0]; - } else { - HttpUtility.requestID = Constants.HttpUtilityExtra.SDK_GENERATED_PREFIX + UUID.randomUUID(); - } + String responseRequestId = connection.getHeaderField(Constants.HttpUtilityExtra.HEADER_REQUEST_ID); + REQUEST_ID.set(responseRequestId != null ? responseRequestId.split(",")[0] : null); Map> responseHeaders = connection.getHeaderFields(); Reader streamReader; - if (httpCode > 299) { - if (connection.getErrorStream() != null) + if (httpCode > Constants.HttpUtilityExtra.HTTP_SUCCESS_STATUS_MAX) { + if (connection.getErrorStream() != null) { streamReader = new InputStreamReader(connection.getErrorStream()); - else { - String description = appendRequestId("replace with description", requestID); - throw new SkyflowException(description); + } else { + String description = appendRequestId(ErrorMessage.ErrorOccurred.getMessage(), REQUEST_ID.get()); + throw new SkyflowException(httpCode, new Throwable(description), responseHeaders, Constants.HttpUtilityExtra.EMPTY_JSON_BODY); } } else { streamReader = new InputStreamReader(connection.getInputStream()); } - response = new StringBuffer(); + response = new StringBuilder(); in = new BufferedReader(streamReader); String inputLine; while ((inputLine = in.readLine()) != null) { response.append(inputLine); } - if (httpCode > 299) { + if (httpCode > Constants.HttpUtilityExtra.HTTP_SUCCESS_STATUS_MAX) { throw new SkyflowException(httpCode, new Throwable(), responseHeaders, response.toString()); } } finally { @@ -112,12 +102,23 @@ public static String sendRequest(String method, URL url, JsonObject params, Map< } + private static byte[] encodeRequestBody(JsonObject params, String requestContentType, String boundary) { + if (requestContentType != null && requestContentType.contains(Constants.HttpUtilityExtra.CONTENT_TYPE_FORM_URLENCODED)) { + return formatJsonToFormEncodedString(params).getBytes(StandardCharsets.UTF_8); + } else if (requestContentType != null && requestContentType.contains(Constants.HttpUtilityExtra.CONTENT_TYPE_MULTIPART)) { + return formatJsonToMultiPartFormDataString(params, boundary).getBytes(StandardCharsets.UTF_8); + } else { + return params.toString().getBytes(StandardCharsets.UTF_8); + } + } + public static String formatJsonToFormEncodedString(JsonObject requestBody) { StringBuilder formEncodeString = new StringBuilder(); HashMap jsonMap = convertJsonToMap(requestBody, ""); - for (Map.Entry currentEntry : jsonMap.entrySet()) + for (Map.Entry currentEntry : jsonMap.entrySet()) { formEncodeString.append(makeFormEncodeKeyValuePair(currentEntry.getKey(), currentEntry.getValue())); + } return formEncodeString.length() == 0 ? "" : formEncodeString.substring(0, formEncodeString.length() - 1); } @@ -126,8 +127,9 @@ public static String formatJsonToMultiPartFormDataString(JsonObject requestBody, StringBuilder formEncodeString = new StringBuilder(); HashMap jsonMap = convertJsonToMap(requestBody, ""); - for (Map.Entry currentEntry : jsonMap.entrySet()) + for (Map.Entry currentEntry : jsonMap.entrySet()) { formEncodeString.append(makeFormDataKeyValuePair(currentEntry.getKey(), currentEntry.getValue(), boundary)); + } formEncodeString.append(LINE_FEED); formEncodeString.append("--").append(boundary).append("--").append(LINE_FEED); @@ -162,19 +164,15 @@ private static String makeFormDataKeyValuePair(String key, String value, String public static String appendRequestId(String message, String requestId) { if (requestId != null && !requestId.isEmpty()) { - message = message + " - requestId: " + requestId; + return message + " - requestId: " + requestId; } return message; } private static String makeFormEncodeKeyValuePair(String key, String value) { - try { - String encodedKey = URLEncoder.encode(key, StandardCharsets.UTF_8.toString()); - String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8.toString()); - return encodedKey + "=" + encodedValue + "&"; - } catch (Exception e) { - return key + "=" + value + "&"; - } + String encodedKey = URLEncoder.encode(key, StandardCharsets.UTF_8); + String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8); + return encodedKey + "=" + encodedValue + "&"; } } diff --git a/src/test/java/com/skyflow/ConnectionClientDotenvTests.java b/src/test/java/com/skyflow/ConnectionClientDotenvTests.java index 4916f628..0b7c4396 100644 --- a/src/test/java/com/skyflow/ConnectionClientDotenvTests.java +++ b/src/test/java/com/skyflow/ConnectionClientDotenvTests.java @@ -59,7 +59,7 @@ public void testPrioritiseCredentials_dotenvReturnsCredentials_setsCredentials() ConnectionClient client = buildClientWithNoCreds("dotenv-valid-1"); // updateConnectionConfig calls prioritiseCredentials which reads from .env - client.updateConnectionConfig(client.getConnectionConfig()); + client.updateConnectionConfig(); } @Test @@ -72,7 +72,7 @@ public void testPrioritiseCredentials_dotenvReturnsNullKey_throwsSkyflowExceptio ConnectionClient client = buildClientWithNoCreds("dotenv-null-1"); // Null sysCredentials → SkyflowException thrown directly try { - client.updateConnectionConfig(client.getConnectionConfig()); + client.updateConnectionConfig(); Assert.fail("Should have thrown SkyflowException"); } catch (SkyflowException e) { Assert.assertTrue(e.getMessage().contains(ErrorMessage.EmptyCredentials.getMessage())); diff --git a/src/test/java/com/skyflow/ConnectionClientTests.java b/src/test/java/com/skyflow/ConnectionClientTests.java index c24bb20a..8ad8ed0c 100644 --- a/src/test/java/com/skyflow/ConnectionClientTests.java +++ b/src/test/java/com/skyflow/ConnectionClientTests.java @@ -51,7 +51,7 @@ public void testSetBearerToken() { Credentials credentials = new Credentials(); credentials.setToken(bearerToken); connectionConfig.setCredentials(credentials); - connectionClient.updateConnectionConfig(connectionConfig); + connectionClient.updateConnectionConfig(); // regular scenario connectionClient.setBearerToken(); @@ -69,7 +69,7 @@ public void testSetBearerTokenWithApiKey() { Credentials credentials = new Credentials(); credentials.setApiKey(apiKey); connectionConfig.setCredentials(null); - connectionClient.updateConnectionConfig(connectionConfig); + connectionClient.updateConnectionConfig(); connectionClient.setCommonCredentials(credentials); // regular scenario @@ -86,7 +86,7 @@ public void testSetBearerTokenWithApiKey() { public void testSetBearerTokenWithEnvCredentials() { try { connectionConfig.setCredentials(null); - connectionClient.updateConnectionConfig(connectionConfig); + connectionClient.updateConnectionConfig(); connectionClient.setCommonCredentials(null); Assert.assertNull(connectionClient.getConnectionConfig().getCredentials()); } catch (Exception e) { @@ -152,7 +152,7 @@ public void testPrioritiseCredentials_credentialChange_resetsToken() { config.setCredentials(credentialsA); ConnectionClient client = new ConnectionClient(config, null); - client.updateConnectionConfig(config); // sets finalCredentials = credentialsA (original=null → no reset) + client.updateConnectionConfig(); // sets finalCredentials = credentialsA (original=null → no reset) client.token = "cached-token-value"; // simulate previously obtained bearer token // Change to different credentials object @@ -160,7 +160,7 @@ public void testPrioritiseCredentials_credentialChange_resetsToken() { credentialsB.setToken("different-token"); config.setCredentials(credentialsB); - client.updateConnectionConfig(config); // original=A, new=B → !A.equals(B) → reset (lines 83-84) + client.updateConnectionConfig(); // original=A, new=B → !A.equals(B) → reset (lines 83-84) Assert.assertNull(client.token); Assert.assertNull(client.apiKey); } catch (Exception e) { diff --git a/src/test/java/com/skyflow/VaultClientTests.java b/src/test/java/com/skyflow/VaultClientTests.java index d98f5964..40eedee7 100644 --- a/src/test/java/com/skyflow/VaultClientTests.java +++ b/src/test/java/com/skyflow/VaultClientTests.java @@ -1043,7 +1043,7 @@ public void testGetDeIdentifyTextResponse_withEntityScores() { } @Test - public void testPrioritiseCredentials_credentialChange_resetsTokenAndApiKey() { + public void testPrioritiseCredentials_credentialChange_resetsToken() { try { Credentials credentialsA = new Credentials(); credentialsA.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y"); @@ -1061,9 +1061,8 @@ public void testPrioritiseCredentials_credentialChange_resetsTokenAndApiKey() { credentialsB.setToken("other-token"); config.setCredentials(credentialsB); - freshClient.updateVaultConfig(); // original=A, new=B → different → reset token/apiKey + freshClient.updateVaultConfig(); // original=A, new=B → different → reset token Assert.assertNull(getPrivateField(freshClient, "token")); - Assert.assertNull(getPrivateField(freshClient, "apiKey")); } catch (Exception e) { Assert.fail("Should not have thrown: " + e.getMessage()); } diff --git a/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java b/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java index 83df09ee..479baed8 100644 --- a/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java +++ b/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java @@ -176,11 +176,33 @@ public void testJsonErrorBodyWithNoGrpcCodeField() { } @Test - public void testNonJsonBodyFallsBackToRawBodyAsMessage() { + public void testMalformedJsonBodyUsesGenericMessage() { + // Malformed / unparseable JSON must not be echoed back as the message. Map> headers = new HashMap<>(); String body = "plain text error response"; SkyflowException ex = new SkyflowException(500, new RuntimeException("fail"), headers, body); - Assert.assertEquals("plain text error response", ex.getMessage()); + Assert.assertEquals(ErrorMessage.ErrorOccurred.getMessage(), ex.getMessage()); + Assert.assertFalse(ex.getMessage().contains("plain text error response")); + } + + @Test + public void testNonObjectJsonBodyUsesGenericMessage() { + // Valid JSON that is not an object (array) must not be echoed back as the message. + Map> headers = new HashMap<>(); + String body = "[\"sensitive\", \"values\"]"; + SkyflowException ex = new SkyflowException(500, new RuntimeException("fail"), headers, body); + Assert.assertEquals(ErrorMessage.ErrorOccurred.getMessage(), ex.getMessage()); + Assert.assertFalse(ex.getMessage().contains("sensitive")); + } + + @Test + public void testWellFormedErrorBodyWithBadFieldTypeFallsBackToConstructorCatch() { + // Valid JSON object with "error", but a non-numeric grpc_code makes getAsInt() throw + // after the object guard -> exercises the constructor catch's (responseBody != null) branch. + Map> headers = new HashMap<>(); + String json = "{\"error\":{\"grpc_code\":\"not-a-number\"}}"; + SkyflowException ex = new SkyflowException(500, new RuntimeException("fail"), headers, json); + Assert.assertEquals(json, ex.getMessage()); } @Test diff --git a/src/test/java/com/skyflow/utils/HttpUtilityTests.java b/src/test/java/com/skyflow/utils/HttpUtilityTests.java index 0158e904..94bf4acb 100644 --- a/src/test/java/com/skyflow/utils/HttpUtilityTests.java +++ b/src/test/java/com/skyflow/utils/HttpUtilityTests.java @@ -167,7 +167,8 @@ public void testSendRequestWithNullRequestId() { params.addProperty("key", "value"); String response = httpUtility.sendRequest("GET", url, params, headers); Assert.assertEquals(expected, response); - Assert.assertNotNull(HttpUtility.getRequestID()); + // No x-request-id header from the backend -> SDK does not generate one. + Assert.assertNull(HttpUtility.getRequestID()); } catch (Exception e) { fail(INVALID_EXCEPTION_THROWN); }