From 602318e07dc6512e13b8248f233cba7a929fe2b3 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Tue, 6 Oct 2026 22:39:08 +0530 Subject: [PATCH 1/5] SK-3175 detect v2 support in flowvault --- common/pom.xml | 9 + .../main/java/com/skyflow/BaseSkyflow.java | 10 +- .../java/com/skyflow/BaseVaultClient.java | 8 +- .../main/java/com/skyflow/detect/Audio.java | 93 ++ .../main/java/com/skyflow/detect/Bleep.java | 113 +++ .../detect/CheckGuardrailsRequest.java | 95 ++ .../detect/CheckGuardrailsResponse.java | 56 ++ .../java/com/skyflow/detect/CustomType.java | 75 ++ .../com/skyflow/detect/DataSourceType.java | 10 + .../skyflow/detect/DeidentificationType.java | 12 + .../skyflow/detect/DeidentifyFileRequest.java | 212 +++++ .../detect/DeidentifyFileResponse.java | 83 ++ .../detect/DeidentifyStringRequest.java | 94 ++ .../detect/DeidentifyStringResponse.java | 48 ++ .../main/java/com/skyflow/detect/Detect.java | 155 ++++ .../skyflow/detect/DetectConfiguration.java | 135 +++ .../com/skyflow/detect/DetectRunStatus.java | 12 + .../com/skyflow/detect/DetectedEntity.java | 68 ++ .../java/com/skyflow/detect/Document.java | 53 ++ .../main/java/com/skyflow/detect/Entity.java | 113 +++ .../com/skyflow/detect/EntityLocation.java | 56 ++ .../java/com/skyflow/detect/EntityType.java | 78 ++ .../com/skyflow/detect/FileDataFormat.java | 30 + .../java/com/skyflow/detect/FileMapping.java | 133 +++ .../java/com/skyflow/detect/FileOutput.java | 46 + .../com/skyflow/detect/GetRunRequest.java | 53 ++ .../com/skyflow/detect/GetRunResponse.java | 78 ++ .../skyflow/detect/GuardrailsValidation.java | 11 + .../main/java/com/skyflow/detect/Image.java | 93 ++ .../com/skyflow/detect/MaskingMethod.java | 10 + .../main/java/com/skyflow/detect/Media.java | 93 ++ .../main/java/com/skyflow/detect/Metrics.java | 76 ++ .../detect/ObjectDeidentificationType.java | 10 + .../java/com/skyflow/detect/ObjectEntity.java | 73 ++ .../com/skyflow/detect/ObjectEntityType.java | 13 + .../detect/OutputTranscriptionType.java | 12 + .../src/main/java/com/skyflow/detect/Pdf.java | 93 ++ .../com/skyflow/detect/PdfProcessingMode.java | 10 + .../java/com/skyflow/detect/PollOptions.java | 91 ++ .../com/skyflow/detect/RedactionLevel.java | 113 +++ .../com/skyflow/detect/RedactionType.java | 11 + .../detect/ReidentifyStringRequest.java | 75 ++ .../detect/ReidentifyStringResponse.java | 36 + .../skyflow/detect/ReturnEntitiesType.java | 10 + .../java/com/skyflow/detect/ShiftDates.java | 73 ++ .../com/skyflow/detect/Transformation.java | 53 ++ .../detect/internal/DeidentifyFileFlow.java | 41 + .../detect/internal/DetectRequestMapper.java | 407 +++++++++ .../detect/internal/DetectResponseMapper.java | 145 ++++ .../detect/internal/DetectValidations.java | 114 +++ .../detect/internal/FileOutputWriter.java | 118 +++ .../com/skyflow/detect/polling/RunPoller.java | 99 +++ ...rrorMessage.java => BaseErrorMessage.java} | 74 +- .../com/skyflow/errors/SkyflowException.java | 2 +- .../rest/core/ApiClientApiException.java | 73 ++ .../detect/rest/core/ApiClientException.java | 17 + .../rest/core/ApiClientHttpResponse.java | 37 + .../detect/rest/core/ClientOptions.java | 320 +++++++ .../detect/rest/core/ConsoleLogger.java | 52 ++ .../rest/core/DateTimeDeserializer.java | 70 ++ .../detect/rest/core/DoubleSerializer.java | 43 + .../detect/rest/core/Environment.java | 25 + .../generated/detect/rest/core/ILogger.java | 38 + .../generated/detect/rest/core/LogConfig.java | 98 +++ .../generated/detect/rest/core/LogLevel.java | 36 + .../generated/detect/rest/core/Logger.java | 97 +++ .../detect/rest/core/LoggingInterceptor.java | 104 +++ .../detect/rest/core/MediaTypes.java | 13 + .../detect/rest/core/ObjectMappers.java | 46 + .../detect/rest/core/QueryStringMapper.java | 142 +++ .../detect/rest/core/RequestOptions.java | 154 ++++ .../ResponseDecompressionInterceptor.java | 56 ++ .../detect/rest/core/RetryInterceptor.java | 242 ++++++ .../detect/rest/errors/BadRequestError.java | 33 + .../rest/errors/InternalServerError.java | 33 + .../detect/rest/errors/UnauthorizedError.java | 33 + .../resources/filesv2/AsyncFilesV2Client.java | 85 ++ .../filesv2/AsyncRawFilesV2Client.java | 350 ++++++++ .../rest/resources/filesv2/FilesV2Client.java | 81 ++ .../resources/filesv2/RawFilesV2Client.java | 282 ++++++ .../requests/DeidentifyFileRequestV2.java | 286 ++++++ .../filesv2/requests/GetRunV2Request.java | 118 +++ .../requests/ReidentifyFileRequestV2.java | 291 +++++++ .../DeidentifyFileRequestV2DataFormat.java | 301 +++++++ .../DeidentifyFileRequestV2DataSource.java | 97 +++ .../ReidentifyFileRequestV2DataFormat.java | 149 ++++ .../ReidentifyFileRequestV2DataSource.java | 97 +++ .../guardrailsv2/AsyncGuardrailsV2Client.java | 43 + .../AsyncRawGuardrailsV2Client.java | 142 +++ .../guardrailsv2/GuardrailsV2Client.java | 42 + .../guardrailsv2/RawGuardrailsV2Client.java | 116 +++ .../requests/DetectGuardrailsRequestV2.java | 253 ++++++ .../stringsv2/AsyncRawStringsV2Client.java | 246 ++++++ .../stringsv2/AsyncStringsV2Client.java | 60 ++ .../stringsv2/RawStringsV2Client.java | 198 +++++ .../resources/stringsv2/StringsV2Client.java | 59 ++ .../requests/DeidentifyStringRequestV2.java | 206 +++++ .../requests/ReidentifyStringRequestV2.java | 209 +++++ .../generated/detect/rest/types/AudioV2.java | 167 ++++ .../detect/rest/types/AudioV2Bleep.java | 203 +++++ .../types/AudioV2OutputTranscription.java | 118 +++ .../rest/types/DeidentifyFileResponseV2.java | 111 +++ .../types/DeidentifyStringResponseV2.java | 168 ++++ ...dentifyStringResponseV2DetectedEntity.java | 229 +++++ ...dentifyStringResponseV2EntityLocation.java | 205 +++++ .../generated/detect/rest/types/Detect.java | 266 ++++++ .../detect/rest/types/DetectConfigV2.java | 470 ++++++++++ .../rest/types/DetectConfigV2VaultType.java | 105 +++ .../detect/rest/types/DetectCustomTypes.java | 141 +++ .../detect/rest/types/DetectEntity.java | 198 +++++ .../DetectEntityDeidentificationType.java | 119 +++ .../rest/types/DetectEntityEntityType.java | 806 +++++++++++++++++ .../types/DetectGuardrailsResponseV2.java | 252 ++++++ .../DetectGuardrailsResponseV2Validation.java | 86 ++ .../detect/rest/types/DetectObjectEntity.java | 142 +++ ...etectObjectEntityDeidentificationType.java | 97 +++ .../types/DetectObjectEntityEntityType.java | 126 +++ .../rest/types/DetectReturnEntities.java | 94 ++ .../rest/types/DetectRunsResponseV2.java | 228 +++++ .../types/DetectRunsResponseV2OutputType.java | 97 +++ .../types/DetectRunsResponseV2Status.java | 115 +++ .../detect/rest/types/DocumentV2.java | 105 +++ .../detect/rest/types/DocumentV2Pdf.java | 173 ++++ .../types/DocumentV2PdfProcessingMode.java | 95 ++ .../rest/types/EntityTransformation.java | 106 +++ .../detect/rest/types/ErrorResponse.java | 118 +++ .../detect/rest/types/ErrorResponseError.java | 236 +++++ .../detect/rest/types/FileMapping.java | 244 ++++++ .../detect/rest/types/FileOutput.java | 173 ++++ .../FileOutputProcessedFileExtension.java | 301 +++++++ .../types/FileOutputProcessedFileType.java | 64 ++ .../generated/detect/rest/types/ImageV2.java | 173 ++++ .../rest/types/ImageV2MaskingMethod.java | 93 ++ .../generated/detect/rest/types/Media.java | 153 ++++ .../generated/detect/rest/types/Metrics.java | 263 ++++++ .../detect/rest/types/RedactionLevel.java | 204 +++++ .../rest/types/RedactionLevelEntityName.java | 812 ++++++++++++++++++ .../types/RedactionLevelRedactionType.java | 106 +++ .../rest/types/ReidentifyFileResponseV2.java | 198 +++++ .../ReidentifyFileResponseV2OutputType.java | 97 +++ .../types/ReidentifyFileResponseV2Status.java | 119 +++ .../types/ReidentifyStringResponseV2.java | 134 +++ .../rest/types/TransformationShiftDates.java | 140 +++ .../main/java/com/skyflow/logs/ErrorLogs.java | 29 +- .../main/java/com/skyflow/logs/InfoLogs.java | 35 +- .../serviceaccount/util/BearerToken.java | 24 +- .../serviceaccount/util/SignedDataTokens.java | 18 +- .../skyflow/serviceaccount/util/Token.java | 8 +- .../java/com/skyflow/utils/BaseConstants.java | 1 + .../java/com/skyflow/utils/BaseUtils.java | 10 +- .../utils/validations/BaseValidations.java | 30 +- .../controller/BaseDetectController.java | 379 ++++++++ .../java/com/skyflow/BaseSkyflowTests.java | 24 +- .../com/skyflow/BaseVaultClientTests.java | 4 +- .../com/skyflow/config/CredentialsTests.java | 28 +- .../internal/DeidentifyFileFlowTests.java | 109 +++ .../internal/DeidentifyFileMapperTests.java | 146 ++++ .../DeidentifyFileValidationsTests.java | 82 ++ .../internal/DetectRequestMapperTests.java | 161 ++++ .../internal/DetectResponseMapperTests.java | 140 +++ .../internal/DetectValidationsTests.java | 138 +++ .../internal/FileOutputWriterTests.java | 119 +++ .../detect/polling/RunPollerTests.java | 162 ++++ .../serviceaccount/util/BearerTokenTests.java | 30 +- .../util/SignedDataTokensTests.java | 24 +- .../com/skyflow/utils/BaseUtilsTests.java | 16 +- .../controller/BaseDetectControllerTests.java | 385 +++++++++ flowvault/README.md | 211 +++++ .../skyflow-flowvault-java.baseline.jar | Bin 499943 -> 938467 bytes flowvault/pom.xml | 6 + .../example/detect/CheckGuardrailsSample.java | 53 ++ .../example/detect/DeidentifyFileSample.java | 66 ++ .../detect/DeidentifyStringSample.java | 94 ++ .../java/com/example/detect/GetRunSample.java | 62 ++ .../example/detect/ReidentifyFileSample.java | 74 ++ .../detect/ReidentifyStringSample.java | 59 ++ .../src/main/java/com/skyflow/Skyflow.java | 52 +- .../skyflow/detect/ReidentifyFileRequest.java | 118 +++ .../detect/ReidentifyFileResponse.java | 59 ++ .../detect/internal/ReidentifyFileMapper.java | 69 ++ .../internal/ReidentifyFileValidations.java | 43 + .../java/com/skyflow/errors/ErrorMessage.java | 77 ++ .../utils/validations/Validations.java | 73 +- .../vault/controller/DetectController.java | 183 ++++ .../internal/ReidentifyFileMapperTests.java | 182 ++++ .../ReidentifyFileValidationsTests.java | 85 ++ .../utils/validations/ValidationsTests.java | 41 +- .../controller/DetectControllerTests.java | 622 ++++++++++++++ .../src/main/java/com/skyflow/Skyflow.java | 17 +- 189 files changed, 22117 insertions(+), 238 deletions(-) create mode 100644 common/src/main/java/com/skyflow/detect/Audio.java create mode 100644 common/src/main/java/com/skyflow/detect/Bleep.java create mode 100644 common/src/main/java/com/skyflow/detect/CheckGuardrailsRequest.java create mode 100644 common/src/main/java/com/skyflow/detect/CheckGuardrailsResponse.java create mode 100644 common/src/main/java/com/skyflow/detect/CustomType.java create mode 100644 common/src/main/java/com/skyflow/detect/DataSourceType.java create mode 100644 common/src/main/java/com/skyflow/detect/DeidentificationType.java create mode 100644 common/src/main/java/com/skyflow/detect/DeidentifyFileRequest.java create mode 100644 common/src/main/java/com/skyflow/detect/DeidentifyFileResponse.java create mode 100644 common/src/main/java/com/skyflow/detect/DeidentifyStringRequest.java create mode 100644 common/src/main/java/com/skyflow/detect/DeidentifyStringResponse.java create mode 100644 common/src/main/java/com/skyflow/detect/Detect.java create mode 100644 common/src/main/java/com/skyflow/detect/DetectConfiguration.java create mode 100644 common/src/main/java/com/skyflow/detect/DetectRunStatus.java create mode 100644 common/src/main/java/com/skyflow/detect/DetectedEntity.java create mode 100644 common/src/main/java/com/skyflow/detect/Document.java create mode 100644 common/src/main/java/com/skyflow/detect/Entity.java create mode 100644 common/src/main/java/com/skyflow/detect/EntityLocation.java create mode 100644 common/src/main/java/com/skyflow/detect/EntityType.java create mode 100644 common/src/main/java/com/skyflow/detect/FileDataFormat.java create mode 100644 common/src/main/java/com/skyflow/detect/FileMapping.java create mode 100644 common/src/main/java/com/skyflow/detect/FileOutput.java create mode 100644 common/src/main/java/com/skyflow/detect/GetRunRequest.java create mode 100644 common/src/main/java/com/skyflow/detect/GetRunResponse.java create mode 100644 common/src/main/java/com/skyflow/detect/GuardrailsValidation.java create mode 100644 common/src/main/java/com/skyflow/detect/Image.java create mode 100644 common/src/main/java/com/skyflow/detect/MaskingMethod.java create mode 100644 common/src/main/java/com/skyflow/detect/Media.java create mode 100644 common/src/main/java/com/skyflow/detect/Metrics.java create mode 100644 common/src/main/java/com/skyflow/detect/ObjectDeidentificationType.java create mode 100644 common/src/main/java/com/skyflow/detect/ObjectEntity.java create mode 100644 common/src/main/java/com/skyflow/detect/ObjectEntityType.java create mode 100644 common/src/main/java/com/skyflow/detect/OutputTranscriptionType.java create mode 100644 common/src/main/java/com/skyflow/detect/Pdf.java create mode 100644 common/src/main/java/com/skyflow/detect/PdfProcessingMode.java create mode 100644 common/src/main/java/com/skyflow/detect/PollOptions.java create mode 100644 common/src/main/java/com/skyflow/detect/RedactionLevel.java create mode 100644 common/src/main/java/com/skyflow/detect/RedactionType.java create mode 100644 common/src/main/java/com/skyflow/detect/ReidentifyStringRequest.java create mode 100644 common/src/main/java/com/skyflow/detect/ReidentifyStringResponse.java create mode 100644 common/src/main/java/com/skyflow/detect/ReturnEntitiesType.java create mode 100644 common/src/main/java/com/skyflow/detect/ShiftDates.java create mode 100644 common/src/main/java/com/skyflow/detect/Transformation.java create mode 100644 common/src/main/java/com/skyflow/detect/internal/DeidentifyFileFlow.java create mode 100644 common/src/main/java/com/skyflow/detect/internal/DetectRequestMapper.java create mode 100644 common/src/main/java/com/skyflow/detect/internal/DetectResponseMapper.java create mode 100644 common/src/main/java/com/skyflow/detect/internal/DetectValidations.java create mode 100644 common/src/main/java/com/skyflow/detect/internal/FileOutputWriter.java create mode 100644 common/src/main/java/com/skyflow/detect/polling/RunPoller.java rename common/src/main/java/com/skyflow/errors/{ErrorMessage.java => BaseErrorMessage.java} (72%) create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/ApiClientApiException.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/ApiClientException.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/ApiClientHttpResponse.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/ClientOptions.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/ConsoleLogger.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/DateTimeDeserializer.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/DoubleSerializer.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/Environment.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/ILogger.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/LogConfig.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/LogLevel.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/Logger.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/LoggingInterceptor.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/MediaTypes.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/ObjectMappers.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/QueryStringMapper.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/RequestOptions.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/ResponseDecompressionInterceptor.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/core/RetryInterceptor.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/errors/BadRequestError.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/errors/InternalServerError.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/errors/UnauthorizedError.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/AsyncFilesV2Client.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/AsyncRawFilesV2Client.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/FilesV2Client.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/RawFilesV2Client.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/requests/DeidentifyFileRequestV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/requests/GetRunV2Request.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/requests/ReidentifyFileRequestV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/DeidentifyFileRequestV2DataFormat.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/DeidentifyFileRequestV2DataSource.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/ReidentifyFileRequestV2DataFormat.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/ReidentifyFileRequestV2DataSource.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/AsyncGuardrailsV2Client.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/AsyncRawGuardrailsV2Client.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/GuardrailsV2Client.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/RawGuardrailsV2Client.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/requests/DetectGuardrailsRequestV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/AsyncRawStringsV2Client.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/AsyncStringsV2Client.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/RawStringsV2Client.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/StringsV2Client.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/requests/DeidentifyStringRequestV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/requests/ReidentifyStringRequestV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/AudioV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/AudioV2Bleep.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/AudioV2OutputTranscription.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyFileResponseV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyStringResponseV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyStringResponseV2DetectedEntity.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyStringResponseV2EntityLocation.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/Detect.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectConfigV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectConfigV2VaultType.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectCustomTypes.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectEntity.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectEntityDeidentificationType.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectEntityEntityType.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectGuardrailsResponseV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectGuardrailsResponseV2Validation.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectObjectEntity.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectObjectEntityDeidentificationType.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectObjectEntityEntityType.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectReturnEntities.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectRunsResponseV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectRunsResponseV2OutputType.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DetectRunsResponseV2Status.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DocumentV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DocumentV2Pdf.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/DocumentV2PdfProcessingMode.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/EntityTransformation.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/ErrorResponse.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/ErrorResponseError.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/FileMapping.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/FileOutput.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/FileOutputProcessedFileExtension.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/FileOutputProcessedFileType.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/ImageV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/ImageV2MaskingMethod.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/Media.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/Metrics.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/RedactionLevel.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/RedactionLevelEntityName.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/RedactionLevelRedactionType.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyFileResponseV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyFileResponseV2OutputType.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyFileResponseV2Status.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyStringResponseV2.java create mode 100644 common/src/main/java/com/skyflow/generated/detect/rest/types/TransformationShiftDates.java create mode 100644 common/src/main/java/com/skyflow/vault/controller/BaseDetectController.java create mode 100644 common/src/test/java/com/skyflow/detect/internal/DeidentifyFileFlowTests.java create mode 100644 common/src/test/java/com/skyflow/detect/internal/DeidentifyFileMapperTests.java create mode 100644 common/src/test/java/com/skyflow/detect/internal/DeidentifyFileValidationsTests.java create mode 100644 common/src/test/java/com/skyflow/detect/internal/DetectRequestMapperTests.java create mode 100644 common/src/test/java/com/skyflow/detect/internal/DetectResponseMapperTests.java create mode 100644 common/src/test/java/com/skyflow/detect/internal/DetectValidationsTests.java create mode 100644 common/src/test/java/com/skyflow/detect/internal/FileOutputWriterTests.java create mode 100644 common/src/test/java/com/skyflow/detect/polling/RunPollerTests.java create mode 100644 common/src/test/java/com/skyflow/vault/controller/BaseDetectControllerTests.java create mode 100644 flowvault/samples/src/main/java/com/example/detect/CheckGuardrailsSample.java create mode 100644 flowvault/samples/src/main/java/com/example/detect/DeidentifyFileSample.java create mode 100644 flowvault/samples/src/main/java/com/example/detect/DeidentifyStringSample.java create mode 100644 flowvault/samples/src/main/java/com/example/detect/GetRunSample.java create mode 100644 flowvault/samples/src/main/java/com/example/detect/ReidentifyFileSample.java create mode 100644 flowvault/samples/src/main/java/com/example/detect/ReidentifyStringSample.java create mode 100644 flowvault/src/main/java/com/skyflow/detect/ReidentifyFileRequest.java create mode 100644 flowvault/src/main/java/com/skyflow/detect/ReidentifyFileResponse.java create mode 100644 flowvault/src/main/java/com/skyflow/detect/internal/ReidentifyFileMapper.java create mode 100644 flowvault/src/main/java/com/skyflow/detect/internal/ReidentifyFileValidations.java create mode 100644 flowvault/src/main/java/com/skyflow/errors/ErrorMessage.java create mode 100644 flowvault/src/main/java/com/skyflow/vault/controller/DetectController.java create mode 100644 flowvault/src/test/java/com/skyflow/detect/internal/ReidentifyFileMapperTests.java create mode 100644 flowvault/src/test/java/com/skyflow/detect/internal/ReidentifyFileValidationsTests.java create mode 100644 flowvault/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java diff --git a/common/pom.xml b/common/pom.xml index 72fff275..99f43063 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -21,4 +21,13 @@ 8 UTF-8 + + + + com.squareup.okhttp3 + mockwebserver + ${okhttp-version} + test + + \ No newline at end of file diff --git a/common/src/main/java/com/skyflow/BaseSkyflow.java b/common/src/main/java/com/skyflow/BaseSkyflow.java index d02dc45a..fdca3f26 100644 --- a/common/src/main/java/com/skyflow/BaseSkyflow.java +++ b/common/src/main/java/com/skyflow/BaseSkyflow.java @@ -4,7 +4,7 @@ import com.skyflow.config.Credentials; import com.skyflow.enums.LogLevel; import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; import com.skyflow.logs.InfoLogs; @@ -66,7 +66,7 @@ public LogLevel getLogLevel() { } protected static T resolveOrThrow(Map map, String key, - ErrorLogs errorLog, ErrorMessage errorMessage) throws SkyflowException { + ErrorLogs errorLog, BaseErrorMessage errorMessage) throws SkyflowException { T value = key != null ? map.get(key) : map.values().stream().findFirst().orElse(null); if (value == null) { // The log line carries a %s1 placeholder for the id. Callers that resolve the single @@ -125,7 +125,7 @@ protected final void addVaultConfigTemplate(V vaultConfig) throws SkyflowExcepti ErrorLogs.VAULT_CONFIG_EXISTS.getLog(), vaultId )); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), - ErrorMessage.VaultIdAlreadyInConfigList.getMessage()); + BaseErrorMessage.VaultIdAlreadyInConfigList.getMessage()); } onVaultConfigAdded(vaultConfigCopy); this.vaultConfigMap.put(vaultId, vaultConfigCopy); @@ -139,7 +139,7 @@ protected final void updateVaultConfigTemplate(V vaultConfig) throws SkyflowExce LogUtil.printErrorLog(BaseUtils.parameterizedString( ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST.getLog(), vaultId )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.VaultIdNotInConfigList.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.VaultIdNotInConfigList.getMessage()); } V previousConfig = this.vaultConfigMap.get(vaultId); V merged = mergeVaultConfig(vaultConfig, cloneVaultConfig(previousConfig)); @@ -150,7 +150,7 @@ protected final void updateVaultConfigTemplate(V vaultConfig) throws SkyflowExce protected final void removeVaultConfigTemplate(String vaultId) throws SkyflowException { if (!hasVaultClient(vaultId)) { LogUtil.printErrorLog(BaseUtils.parameterizedString(ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST.getLog(), vaultId)); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.VaultIdNotInConfigList.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.VaultIdNotInConfigList.getMessage()); } onVaultConfigRemoved(vaultId); this.vaultConfigMap.remove(vaultId); diff --git a/common/src/main/java/com/skyflow/BaseVaultClient.java b/common/src/main/java/com/skyflow/BaseVaultClient.java index 3d5b0d32..973510d7 100644 --- a/common/src/main/java/com/skyflow/BaseVaultClient.java +++ b/common/src/main/java/com/skyflow/BaseVaultClient.java @@ -5,7 +5,7 @@ import com.skyflow.config.BaseCredentials; import com.skyflow.config.BaseVaultConfig; import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; import com.skyflow.logs.InfoLogs; @@ -25,7 +25,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -class BaseVaultClient { +public class BaseVaultClient { protected V vaultConfig; protected OkHttpClient sharedHttpClient; protected String currentVaultURL; @@ -69,7 +69,7 @@ protected synchronized void prioritiseCredentials(BaseCredentials vaultSpecificC sysCredentials = dotenv.get(BaseConstants.ENV_CREDENTIALS_KEY_NAME); } if (sysCredentials == null) { - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentials.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyCredentials.getMessage()); } else { this.finalCredentials = new BaseCredentials(); this.finalCredentials.setCredentialsString(sysCredentials); @@ -80,7 +80,7 @@ protected synchronized void prioritiseCredentials(BaseCredentials vaultSpecificC apiKey = null; } } catch (DotenvException e) { - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentials.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyCredentials.getMessage()); } catch (SkyflowException e) { throw e; } catch (Exception e) { diff --git a/common/src/main/java/com/skyflow/detect/Audio.java b/common/src/main/java/com/skyflow/detect/Audio.java new file mode 100644 index 00000000..6d4e46f9 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/Audio.java @@ -0,0 +1,93 @@ +package com.skyflow.detect; + +/** + * Audio processing options. + */ +public final class Audio { + private final Boolean outputProcessedAudio; + private final OutputTranscriptionType outputTranscription; + private final Bleep bleep; + + private Audio(AudioBuilder builder) { + this.outputProcessedAudio = builder.outputProcessedAudio; + this.outputTranscription = builder.outputTranscription; + this.bleep = builder.bleep; + } + + /** + * @return a new builder + */ + public static AudioBuilder builder() { + return new AudioBuilder(); + } + + /** + * @return Whether to return the processed audio file. + */ + public Boolean getOutputProcessedAudio() { + return outputProcessedAudio; + } + + /** + * @return Transcription output to produce. + */ + public OutputTranscriptionType getOutputTranscription() { + return outputTranscription; + } + + /** + * @return Bleep configuration. + */ + public Bleep getBleep() { + return bleep; + } + + @Override + public String toString() { + return "Audio{" + + "outputProcessedAudio=" + outputProcessedAudio + + ", outputTranscription=" + outputTranscription + + ", bleep=" + bleep + + '}'; + } + + public static final class AudioBuilder { + private Boolean outputProcessedAudio; + private OutputTranscriptionType outputTranscription; + private Bleep bleep; + + private AudioBuilder() { + } + + /** + * @param outputProcessedAudio Whether to return the processed audio file. + * @return this builder + */ + public AudioBuilder outputProcessedAudio(Boolean outputProcessedAudio) { + this.outputProcessedAudio = outputProcessedAudio; + return this; + } + + /** + * @param outputTranscription Transcription output to produce. + * @return this builder + */ + public AudioBuilder outputTranscription(OutputTranscriptionType outputTranscription) { + this.outputTranscription = outputTranscription; + return this; + } + + /** + * @param bleep Bleep configuration. + * @return this builder + */ + public AudioBuilder bleep(Bleep bleep) { + this.bleep = bleep; + return this; + } + + public Audio build() { + return new Audio(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/Bleep.java b/common/src/main/java/com/skyflow/detect/Bleep.java new file mode 100644 index 00000000..63346aeb --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/Bleep.java @@ -0,0 +1,113 @@ +package com.skyflow.detect; + +/** + * Bleep tone applied over detected speech in audio. + */ +public final class Bleep { + private final Float startPadding; + private final Float stopPadding; + private final Integer frequency; + private final Integer gain; + + private Bleep(BleepBuilder builder) { + this.startPadding = builder.startPadding; + this.stopPadding = builder.stopPadding; + this.frequency = builder.frequency; + this.gain = builder.gain; + } + + /** + * @return a new builder + */ + public static BleepBuilder builder() { + return new BleepBuilder(); + } + + /** + * @return Seconds of bleep added before the detected span. + */ + public Float getStartPadding() { + return startPadding; + } + + /** + * @return Seconds of bleep added after the detected span. + */ + public Float getStopPadding() { + return stopPadding; + } + + /** + * @return Bleep frequency in Hz. + */ + public Integer getFrequency() { + return frequency; + } + + /** + * @return Bleep gain. + */ + public Integer getGain() { + return gain; + } + + @Override + public String toString() { + return "Bleep{" + + "startPadding=" + startPadding + + ", stopPadding=" + stopPadding + + ", frequency=" + frequency + + ", gain=" + gain + + '}'; + } + + public static final class BleepBuilder { + private Float startPadding; + private Float stopPadding; + private Integer frequency; + private Integer gain; + + private BleepBuilder() { + } + + /** + * @param startPadding Seconds of bleep added before the detected span. + * @return this builder + */ + public BleepBuilder startPadding(Float startPadding) { + this.startPadding = startPadding; + return this; + } + + /** + * @param stopPadding Seconds of bleep added after the detected span. + * @return this builder + */ + public BleepBuilder stopPadding(Float stopPadding) { + this.stopPadding = stopPadding; + return this; + } + + /** + * @param frequency Bleep frequency in Hz. + * @return this builder + */ + public BleepBuilder frequency(Integer frequency) { + this.frequency = frequency; + return this; + } + + /** + * @param gain Bleep gain. + * @return this builder + */ + public BleepBuilder gain(Integer gain) { + this.gain = gain; + return this; + } + + public Bleep build() { + return new Bleep(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/CheckGuardrailsRequest.java b/common/src/main/java/com/skyflow/detect/CheckGuardrailsRequest.java new file mode 100644 index 00000000..526ddd9c --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/CheckGuardrailsRequest.java @@ -0,0 +1,95 @@ +package com.skyflow.detect; + +import java.util.List; + +/** + * Request for {@code checkGuardrails}: screens text for toxicity and denied topics. + */ +public final class CheckGuardrailsRequest { + private final String text; + private final Boolean checkToxicity; + private final List denyTopics; + + private CheckGuardrailsRequest(CheckGuardrailsRequestBuilder builder) { + this.text = builder.text; + this.checkToxicity = builder.checkToxicity; + this.denyTopics = builder.denyTopics; + } + + /** + * @return a new builder + */ + public static CheckGuardrailsRequestBuilder builder() { + return new CheckGuardrailsRequestBuilder(); + } + + /** + * @return Text to screen. Required. + */ + public String getText() { + return text; + } + + /** + * @return Whether to check the text for toxicity. + */ + public Boolean getCheckToxicity() { + return checkToxicity; + } + + /** + * @return Topics the text must not discuss. + */ + public List getDenyTopics() { + return denyTopics; + } + + @Override + public String toString() { + return "CheckGuardrailsRequest{" + + "text=" + text + + ", checkToxicity=" + checkToxicity + + ", denyTopics=" + denyTopics + + '}'; + } + + public static final class CheckGuardrailsRequestBuilder { + private String text; + private Boolean checkToxicity; + private List denyTopics; + + private CheckGuardrailsRequestBuilder() { + } + + /** + * @param text Text to screen. Required. + * @return this builder + */ + public CheckGuardrailsRequestBuilder text(String text) { + this.text = text; + return this; + } + + /** + * @param checkToxicity Whether to check the text for toxicity. + * @return this builder + */ + public CheckGuardrailsRequestBuilder checkToxicity(Boolean checkToxicity) { + this.checkToxicity = checkToxicity; + return this; + } + + /** + * @param denyTopics Topics the text must not discuss. + * @return this builder + */ + public CheckGuardrailsRequestBuilder denyTopics(List denyTopics) { + this.denyTopics = denyTopics; + return this; + } + + public CheckGuardrailsRequest build() { + return new CheckGuardrailsRequest(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/CheckGuardrailsResponse.java b/common/src/main/java/com/skyflow/detect/CheckGuardrailsResponse.java new file mode 100644 index 00000000..153e68cf --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/CheckGuardrailsResponse.java @@ -0,0 +1,56 @@ +package com.skyflow.detect; + +/** + * Response of {@code checkGuardrails}. + */ +public final class CheckGuardrailsResponse { + private final String text; + private final GuardrailsValidation validation; + private final Boolean toxic; + private final Boolean deniedTopic; + + public CheckGuardrailsResponse(String text, GuardrailsValidation validation, Boolean toxic, Boolean deniedTopic) { + this.text = text; + this.validation = validation; + this.toxic = toxic; + this.deniedTopic = deniedTopic; + } + + /** + * @return The text that was screened. + */ + public String getText() { + return text; + } + + /** + * @return PASSED when no guardrail was triggered, FAILED otherwise. + */ + public GuardrailsValidation getValidation() { + return validation; + } + + /** + * @return Whether the text was judged toxic. Null when toxicity was not checked. + */ + public Boolean getToxic() { + return toxic; + } + + /** + * @return Whether the text touched a denied topic. Null when no topics were supplied. + */ + public Boolean getDeniedTopic() { + return deniedTopic; + } + + @Override + public String toString() { + return "CheckGuardrailsResponse{" + + "text=" + text + + ", validation=" + validation + + ", toxic=" + toxic + + ", deniedTopic=" + deniedTopic + + '}'; + } +} diff --git a/common/src/main/java/com/skyflow/detect/CustomType.java b/common/src/main/java/com/skyflow/detect/CustomType.java new file mode 100644 index 00000000..1eee269a --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/CustomType.java @@ -0,0 +1,75 @@ +package com.skyflow.detect; + +import java.util.List; + +/** + * A custom, regex-defined entity type. + */ +public final class CustomType { + private final String label; + private final List match; + + private CustomType(CustomTypeBuilder builder) { + this.label = builder.label; + this.match = builder.match; + } + + /** + * @return a new builder + */ + public static CustomTypeBuilder builder() { + return new CustomTypeBuilder(); + } + + /** + * @return Label reported as the entityType of matches. + */ + public String getLabel() { + return label; + } + + /** + * @return Regular expressions that identify the custom entity. + */ + public List getMatch() { + return match; + } + + @Override + public String toString() { + return "CustomType{" + + "label=" + label + + ", match=" + match + + '}'; + } + + public static final class CustomTypeBuilder { + private String label; + private List match; + + private CustomTypeBuilder() { + } + + /** + * @param label Label reported as the entityType of matches. + * @return this builder + */ + public CustomTypeBuilder label(String label) { + this.label = label; + return this; + } + + /** + * @param match Regular expressions that identify the custom entity. + * @return this builder + */ + public CustomTypeBuilder match(List match) { + this.match = match; + return this; + } + + public CustomType build() { + return new CustomType(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/DataSourceType.java b/common/src/main/java/com/skyflow/detect/DataSourceType.java new file mode 100644 index 00000000..d152fbb4 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/DataSourceType.java @@ -0,0 +1,10 @@ +package com.skyflow.detect; + +/** + * How file content is supplied to, or returned by, Detect file operations. + */ +public enum DataSourceType { + BASE64, + SKYFLOW_ID, + PRESIGNED_URL; +} diff --git a/common/src/main/java/com/skyflow/detect/DeidentificationType.java b/common/src/main/java/com/skyflow/detect/DeidentificationType.java new file mode 100644 index 00000000..067d533f --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/DeidentificationType.java @@ -0,0 +1,12 @@ +package com.skyflow.detect; + +/** + * How a detected entity is replaced in the processed output. + */ +public enum DeidentificationType { + UNKNOWN, + ENTITY_UNIQUE_COUNTER, + ENTITY_ONLY, + VAULT_TOKEN, + MASK; +} diff --git a/common/src/main/java/com/skyflow/detect/DeidentifyFileRequest.java b/common/src/main/java/com/skyflow/detect/DeidentifyFileRequest.java new file mode 100644 index 00000000..b91b3a76 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/DeidentifyFileRequest.java @@ -0,0 +1,212 @@ +package com.skyflow.detect; + +import java.io.File; + +/** + * Request for {@code deidentifyFile}. Supply the file through exactly one of {@code value} (with a + * {@code dataSource}), {@code file} or {@code filePath}. The two file forms are builder sugar that set + * {@code dataSource = BASE64}, encode the content into {@code value} when the request is sent, and infer + * {@code dataFormat} from the extension unless it is set explicitly. + * + *

Without {@code pollOptions} the call submits the run and returns its id. With {@code pollOptions} the + * SDK polls the run and, on success for a file-based request, writes the outputs to disk. + */ +public final class DeidentifyFileRequest { + private final DataSourceType dataSource; + private final String value; + private final File file; + private final FileDataFormat dataFormat; + private final String configurationId; + private final DetectConfiguration configuration; + private final PollOptions pollOptions; + private final String outputDirectory; + + private DeidentifyFileRequest(DeidentifyFileRequestBuilder builder) { + this.dataSource = builder.dataSource; + this.value = builder.value; + this.file = builder.file; + this.dataFormat = builder.dataFormat; + this.configurationId = builder.configurationId; + this.configuration = builder.configuration; + this.pollOptions = builder.pollOptions; + this.outputDirectory = builder.outputDirectory; + } + + /** + * @return a new builder + */ + public static DeidentifyFileRequestBuilder builder() { + return new DeidentifyFileRequestBuilder(); + } + + /** + * @return How {@code value} supplies the file. Required unless {@code file} or {@code filePath} was used. + */ + public DataSourceType getDataSource() { + return dataSource; + } + + /** + * @return Base64 content, skyflow id or presigned URL, according to {@code dataSource}. + */ + public String getValue() { + return value; + } + + /** + * @return Local file to de-identify, when {@code file} or {@code filePath} was used. + */ + public File getFile() { + return file; + } + + /** + * @return Format of the file. Optional; inferred from the file extension or by the server when omitted. + */ + public FileDataFormat getDataFormat() { + return dataFormat; + } + + /** + * @return Id of a stored Detect configuration. + */ + public String getConfigurationId() { + return configurationId; + } + + /** + * @return Inline Detect configuration. + */ + public DetectConfiguration getConfiguration() { + return configuration; + } + + /** + * @return Polling settings, or null to submit only. + */ + public PollOptions getPollOptions() { + return pollOptions; + } + + /** + * @return Directory the outputs are written to on success. Only with {@code pollOptions} and a file-based request. + */ + public String getOutputDirectory() { + return outputDirectory; + } + + @Override + public String toString() { + return "DeidentifyFileRequest{" + + "dataSource=" + dataSource + + ", value=" + (value == null ? null : "<" + value.length() + " chars>") + + ", file=" + file + + ", dataFormat=" + dataFormat + + ", configurationId=" + configurationId + + ", configuration=" + configuration + + ", pollOptions=" + pollOptions + + ", outputDirectory=" + outputDirectory + + '}'; + } + + public static final class DeidentifyFileRequestBuilder { + private DataSourceType dataSource; + private String value; + private File file; + private FileDataFormat dataFormat; + private String configurationId; + private DetectConfiguration configuration; + private PollOptions pollOptions; + private String outputDirectory; + + private DeidentifyFileRequestBuilder() { + } + + /** + * @param dataSource How {@code value} supplies the file. + * @return this builder + */ + public DeidentifyFileRequestBuilder dataSource(DataSourceType dataSource) { + this.dataSource = dataSource; + return this; + } + + /** + * @param value Base64 content, skyflow id or presigned URL, according to {@code dataSource}. + * @return this builder + */ + public DeidentifyFileRequestBuilder value(String value) { + this.value = value; + return this; + } + + /** + * @param file Local file to de-identify. Sets {@code dataSource} to BASE64; the content is encoded when sent. + * @return this builder + */ + public DeidentifyFileRequestBuilder file(File file) { + this.file = file; + if (file != null) { + this.dataSource = DataSourceType.BASE64; + } + return this; + } + + /** + * @param filePath Path of a local file to de-identify. Same as {@link #file(File)}. + * @return this builder + */ + public DeidentifyFileRequestBuilder filePath(String filePath) { + return file(filePath == null ? null : new File(filePath)); + } + + /** + * @param dataFormat Format of the file. Optional. + * @return this builder + */ + public DeidentifyFileRequestBuilder dataFormat(FileDataFormat dataFormat) { + this.dataFormat = dataFormat; + return this; + } + + /** + * @param configurationId Id of a stored Detect configuration. + * @return this builder + */ + public DeidentifyFileRequestBuilder configurationId(String configurationId) { + this.configurationId = configurationId; + return this; + } + + /** + * @param configuration Inline Detect configuration. + * @return this builder + */ + public DeidentifyFileRequestBuilder configuration(DetectConfiguration configuration) { + this.configuration = configuration; + return this; + } + + /** + * @param pollOptions Poll the run after submitting. Null to submit only. + * @return this builder + */ + public DeidentifyFileRequestBuilder pollOptions(PollOptions pollOptions) { + this.pollOptions = pollOptions; + return this; + } + + /** + * @param outputDirectory Where to write outputs on success. Requires {@code pollOptions} and a file-based request. + * @return this builder + */ + public DeidentifyFileRequestBuilder outputDirectory(String outputDirectory) { + this.outputDirectory = outputDirectory; + return this; + } + + public DeidentifyFileRequest build() { + return new DeidentifyFileRequest(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/DeidentifyFileResponse.java b/common/src/main/java/com/skyflow/detect/DeidentifyFileResponse.java new file mode 100644 index 00000000..482b3b63 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/DeidentifyFileResponse.java @@ -0,0 +1,83 @@ +package com.skyflow.detect; + +import java.util.List; + +/** + * Response of {@code deidentifyFile}. {@code runId} is always present. {@code status} is null when the + * request did not poll; retrieve the run later with {@code getRun}. When polled, the remaining fields + * mirror {@link GetRunResponse}: output, outputType and metrics are populated only on SUCCESS, and + * message carries the failure detail on FAILED. + */ +public final class DeidentifyFileResponse { + private final String runId; + private final DetectRunStatus status; + private final DataSourceType outputType; + private final List output; + private final Metrics metrics; + private final String message; + + public DeidentifyFileResponse(String runId, DetectRunStatus status, DataSourceType outputType, + List output, Metrics metrics, String message) { + this.runId = runId; + this.status = status; + this.outputType = outputType; + this.output = output; + this.metrics = metrics; + this.message = message; + } + + /** + * @return Run id returned by the submit call. Use it with {@code getRun} to retrieve or resume. + */ + public String getRunId() { + return runId; + } + + /** + * @return Run status after polling. Null when the request did not poll. IN_PROGRESS or QUEUED when the + * polling budget ran out before the run finished. + */ + public DetectRunStatus getStatus() { + return status; + } + + /** + * @return How output files are delivered. Null unless polled to SUCCESS. + */ + public DataSourceType getOutputType() { + return outputType; + } + + /** + * @return Output artifacts. Empty unless polled to SUCCESS, never null. + */ + public List getOutput() { + return output; + } + + /** + * @return Processing metrics. Null unless polled to SUCCESS. + */ + public Metrics getMetrics() { + return metrics; + } + + /** + * @return Failure detail when status is FAILED. + */ + public String getMessage() { + return message; + } + + @Override + public String toString() { + return "DeidentifyFileResponse{" + + "runId=" + runId + + ", status=" + status + + ", outputType=" + outputType + + ", output=" + output + + ", metrics=" + metrics + + ", message=" + message + + '}'; + } +} diff --git a/common/src/main/java/com/skyflow/detect/DeidentifyStringRequest.java b/common/src/main/java/com/skyflow/detect/DeidentifyStringRequest.java new file mode 100644 index 00000000..bde84343 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/DeidentifyStringRequest.java @@ -0,0 +1,94 @@ +package com.skyflow.detect; + +/** + * Request for {@code deidentifyString}. configurationId and configuration are both optional and are sent + * exactly as supplied; the server applies its own rules for their combination. + */ +public final class DeidentifyStringRequest { + private final String text; + private final String configurationId; + private final DetectConfiguration configuration; + + private DeidentifyStringRequest(DeidentifyStringRequestBuilder builder) { + this.text = builder.text; + this.configurationId = builder.configurationId; + this.configuration = builder.configuration; + } + + /** + * @return a new builder + */ + public static DeidentifyStringRequestBuilder builder() { + return new DeidentifyStringRequestBuilder(); + } + + /** + * @return Text to de-identify. Required. + */ + public String getText() { + return text; + } + + /** + * @return Id of a stored Detect configuration. + */ + public String getConfigurationId() { + return configurationId; + } + + /** + * @return Inline Detect configuration. + */ + public DetectConfiguration getConfiguration() { + return configuration; + } + + @Override + public String toString() { + return "DeidentifyStringRequest{" + + "text=" + text + + ", configurationId=" + configurationId + + ", configuration=" + configuration + + '}'; + } + + public static final class DeidentifyStringRequestBuilder { + private String text; + private String configurationId; + private DetectConfiguration configuration; + + private DeidentifyStringRequestBuilder() { + } + + /** + * @param text Text to de-identify. Required. + * @return this builder + */ + public DeidentifyStringRequestBuilder text(String text) { + this.text = text; + return this; + } + + /** + * @param configurationId Id of a stored Detect configuration. + * @return this builder + */ + public DeidentifyStringRequestBuilder configurationId(String configurationId) { + this.configurationId = configurationId; + return this; + } + + /** + * @param configuration Inline Detect configuration. + * @return this builder + */ + public DeidentifyStringRequestBuilder configuration(DetectConfiguration configuration) { + this.configuration = configuration; + return this; + } + + public DeidentifyStringRequest build() { + return new DeidentifyStringRequest(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/DeidentifyStringResponse.java b/common/src/main/java/com/skyflow/detect/DeidentifyStringResponse.java new file mode 100644 index 00000000..7ea68dcf --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/DeidentifyStringResponse.java @@ -0,0 +1,48 @@ +package com.skyflow.detect; + +import java.util.List; + +/** + * Response of {@code deidentifyString}. + */ +public final class DeidentifyStringResponse { + private final String processedText; + private final List entities; + private final Metrics metrics; + + public DeidentifyStringResponse(String processedText, List entities, Metrics metrics) { + this.processedText = processedText; + this.entities = entities; + this.metrics = metrics; + } + + /** + * @return De-identified text. + */ + public String getProcessedText() { + return processedText; + } + + /** + * @return Detected entities. Never null. + */ + public List getEntities() { + return entities; + } + + /** + * @return Processing metrics. + */ + public Metrics getMetrics() { + return metrics; + } + + @Override + public String toString() { + return "DeidentifyStringResponse{" + + "processedText=" + processedText + + ", entities=" + entities + + ", metrics=" + metrics + + '}'; + } +} diff --git a/common/src/main/java/com/skyflow/detect/Detect.java b/common/src/main/java/com/skyflow/detect/Detect.java new file mode 100644 index 00000000..dc998e57 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/Detect.java @@ -0,0 +1,155 @@ +package com.skyflow.detect; + +import java.util.List; + +/** + * Detection policy: which entities to look for and how to replace them. + */ +public final class Detect { + private final List entities; + private final List objectEntities; + private final List restrict; + private final List skip; + private final List customTypes; + private final ReturnEntitiesType returnEntities; + + private Detect(DetectBuilder builder) { + this.entities = builder.entities; + this.objectEntities = builder.objectEntities; + this.restrict = builder.restrict; + this.skip = builder.skip; + this.customTypes = builder.customTypes; + this.returnEntities = builder.returnEntities; + } + + /** + * @return a new builder + */ + public static DetectBuilder builder() { + return new DetectBuilder(); + } + + /** + * @return Per-entity rules. + */ + public List getEntities() { + return entities; + } + + /** + * @return Per-object rules. File inputs only. + */ + public List getObjectEntities() { + return objectEntities; + } + + /** + * @return Regular expressions that are always treated as sensitive. + */ + public List getRestrict() { + return restrict; + } + + /** + * @return Regular expressions that are never treated as sensitive. + */ + public List getSkip() { + return skip; + } + + /** + * @return Custom regex-defined entity types. + */ + public List getCustomTypes() { + return customTypes; + } + + /** + * @return Which entities to return. Defaults to EXCLUDE_SENSITIVE_DATA on the server. + */ + public ReturnEntitiesType getReturnEntities() { + return returnEntities; + } + + @Override + public String toString() { + return "Detect{" + + "entities=" + entities + + ", objectEntities=" + objectEntities + + ", restrict=" + restrict + + ", skip=" + skip + + ", customTypes=" + customTypes + + ", returnEntities=" + returnEntities + + '}'; + } + + public static final class DetectBuilder { + private List entities; + private List objectEntities; + private List restrict; + private List skip; + private List customTypes; + private ReturnEntitiesType returnEntities; + + private DetectBuilder() { + } + + /** + * @param entities Per-entity rules. + * @return this builder + */ + public DetectBuilder entities(List entities) { + this.entities = entities; + return this; + } + + /** + * @param objectEntities Per-object rules. File inputs only. + * @return this builder + */ + public DetectBuilder objectEntities(List objectEntities) { + this.objectEntities = objectEntities; + return this; + } + + /** + * @param restrict Regular expressions that are always treated as sensitive. + * @return this builder + */ + public DetectBuilder restrict(List restrict) { + this.restrict = restrict; + return this; + } + + /** + * @param skip Regular expressions that are never treated as sensitive. + * @return this builder + */ + public DetectBuilder skip(List skip) { + this.skip = skip; + return this; + } + + /** + * @param customTypes Custom regex-defined entity types. + * @return this builder + */ + public DetectBuilder customTypes(List customTypes) { + this.customTypes = customTypes; + return this; + } + + /** + * @param returnEntities Which entities to return. Defaults to EXCLUDE_SENSITIVE_DATA on the server. + * @return this builder + */ + public DetectBuilder returnEntities(ReturnEntitiesType returnEntities) { + this.returnEntities = returnEntities; + return this; + } + + public Detect build() { + return new Detect(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/DetectConfiguration.java b/common/src/main/java/com/skyflow/detect/DetectConfiguration.java new file mode 100644 index 00000000..e9209528 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/DetectConfiguration.java @@ -0,0 +1,135 @@ +package com.skyflow.detect; + +import java.util.List; + +/** + * An inline Detect configuration, used instead of a stored configuration id. + */ +public final class DetectConfiguration { + private final String name; + private final String description; + private final Detect detect; + private final List fileMapping; + private final Media media; + + private DetectConfiguration(DetectConfigurationBuilder builder) { + this.name = builder.name; + this.description = builder.description; + this.detect = builder.detect; + this.fileMapping = builder.fileMapping; + this.media = builder.media; + } + + /** + * @return a new builder + */ + public static DetectConfigurationBuilder builder() { + return new DetectConfigurationBuilder(); + } + + /** + * @return Configuration name. + */ + public String getName() { + return name; + } + + /** + * @return Configuration description. + */ + public String getDescription() { + return description; + } + + /** + * @return Detection policy. + */ + public Detect getDetect() { + return detect; + } + + /** + * @return File mappings. File inputs only. + */ + public List getFileMapping() { + return fileMapping; + } + + /** + * @return Media options. File inputs only. + */ + public Media getMedia() { + return media; + } + + @Override + public String toString() { + return "DetectConfiguration{" + + "name=" + name + + ", description=" + description + + ", detect=" + detect + + ", fileMapping=" + fileMapping + + ", media=" + media + + '}'; + } + + public static final class DetectConfigurationBuilder { + private String name; + private String description; + private Detect detect; + private List fileMapping; + private Media media; + + private DetectConfigurationBuilder() { + } + + /** + * @param name Configuration name. + * @return this builder + */ + public DetectConfigurationBuilder name(String name) { + this.name = name; + return this; + } + + /** + * @param description Configuration description. + * @return this builder + */ + public DetectConfigurationBuilder description(String description) { + this.description = description; + return this; + } + + /** + * @param detect Detection policy. + * @return this builder + */ + public DetectConfigurationBuilder detect(Detect detect) { + this.detect = detect; + return this; + } + + /** + * @param fileMapping File mappings. File inputs only. + * @return this builder + */ + public DetectConfigurationBuilder fileMapping(List fileMapping) { + this.fileMapping = fileMapping; + return this; + } + + /** + * @param media Media options. File inputs only. + * @return this builder + */ + public DetectConfigurationBuilder media(Media media) { + this.media = media; + return this; + } + + public DetectConfiguration build() { + return new DetectConfiguration(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/DetectRunStatus.java b/common/src/main/java/com/skyflow/detect/DetectRunStatus.java new file mode 100644 index 00000000..3bb05886 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/DetectRunStatus.java @@ -0,0 +1,12 @@ +package com.skyflow.detect; + +/** + * Lifecycle status of an asynchronous Detect file run. + */ +public enum DetectRunStatus { + UNKNOWN, + QUEUED, + IN_PROGRESS, + SUCCESS, + FAILED; +} diff --git a/common/src/main/java/com/skyflow/detect/DetectedEntity.java b/common/src/main/java/com/skyflow/detect/DetectedEntity.java new file mode 100644 index 00000000..6a443d1c --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/DetectedEntity.java @@ -0,0 +1,68 @@ +package com.skyflow.detect; + +import java.util.Map; + +/** + * An entity detected in the input text. + */ +public final class DetectedEntity { + private final String token; + private final String value; + private final EntityLocation location; + private final String entityType; + private final Map entityScores; + + public DetectedEntity(String token, String value, EntityLocation location, String entityType, Map entityScores) { + this.token = token; + this.value = value; + this.location = location; + this.entityType = entityType; + this.entityScores = entityScores; + } + + /** + * @return Replacement token, for example NAME_1. + */ + public String getToken() { + return token; + } + + /** + * @return Original value. Empty unless returnEntities is ALL. + */ + public String getValue() { + return value; + } + + /** + * @return Where the entity was found. + */ + public EntityLocation getLocation() { + return location; + } + + /** + * @return Entity type. Carries custom-type labels as well as built-in types. + */ + public String getEntityType() { + return entityType; + } + + /** + * @return Confidence scores by entity type, 0.0 to 1.0. + */ + public Map getEntityScores() { + return entityScores; + } + + @Override + public String toString() { + return "DetectedEntity{" + + "token=" + token + + ", value=" + value + + ", location=" + location + + ", entityType=" + entityType + + ", entityScores=" + entityScores + + '}'; + } +} diff --git a/common/src/main/java/com/skyflow/detect/Document.java b/common/src/main/java/com/skyflow/detect/Document.java new file mode 100644 index 00000000..2d461196 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/Document.java @@ -0,0 +1,53 @@ +package com.skyflow.detect; + +/** + * Document processing options. + */ +public final class Document { + private final Pdf pdf; + + private Document(DocumentBuilder builder) { + this.pdf = builder.pdf; + } + + /** + * @return a new builder + */ + public static DocumentBuilder builder() { + return new DocumentBuilder(); + } + + /** + * @return PDF options. + */ + public Pdf getPdf() { + return pdf; + } + + @Override + public String toString() { + return "Document{" + + "pdf=" + pdf + + '}'; + } + + public static final class DocumentBuilder { + private Pdf pdf; + + private DocumentBuilder() { + } + + /** + * @param pdf PDF options. + * @return this builder + */ + public DocumentBuilder pdf(Pdf pdf) { + this.pdf = pdf; + return this; + } + + public Document build() { + return new Document(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/Entity.java b/common/src/main/java/com/skyflow/detect/Entity.java new file mode 100644 index 00000000..992acafd --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/Entity.java @@ -0,0 +1,113 @@ +package com.skyflow.detect; + +/** + * Configures how a single entity type is detected and de-identified. + */ +public final class Entity { + private final EntityType entityType; + private final DeidentificationType deidentificationType; + private final String destination; + private final Transformation transformation; + + private Entity(EntityBuilder builder) { + this.entityType = builder.entityType; + this.deidentificationType = builder.deidentificationType; + this.destination = builder.destination; + this.transformation = builder.transformation; + } + + /** + * @return a new builder + */ + public static EntityBuilder builder() { + return new EntityBuilder(); + } + + /** + * @return Entity type this rule applies to. Defaults to ALL on the server. + */ + public EntityType getEntityType() { + return entityType; + } + + /** + * @return Replacement strategy. Defaults to ENTITY_UNIQUE_COUNTER on the server. + */ + public DeidentificationType getDeidentificationType() { + return deidentificationType; + } + + /** + * @return Where vault tokens are stored: a table.column (skyvault) or token-group name (flowvault). Only used with VAULT_TOKEN. + */ + public String getDestination() { + return destination; + } + + /** + * @return Optional transformation applied to the detected value. + */ + public Transformation getTransformation() { + return transformation; + } + + @Override + public String toString() { + return "Entity{" + + "entityType=" + entityType + + ", deidentificationType=" + deidentificationType + + ", destination=" + destination + + ", transformation=" + transformation + + '}'; + } + + public static final class EntityBuilder { + private EntityType entityType; + private DeidentificationType deidentificationType; + private String destination; + private Transformation transformation; + + private EntityBuilder() { + } + + /** + * @param entityType Entity type this rule applies to. Defaults to ALL on the server. + * @return this builder + */ + public EntityBuilder entityType(EntityType entityType) { + this.entityType = entityType; + return this; + } + + /** + * @param deidentificationType Replacement strategy. Defaults to ENTITY_UNIQUE_COUNTER on the server. + * @return this builder + */ + public EntityBuilder deidentificationType(DeidentificationType deidentificationType) { + this.deidentificationType = deidentificationType; + return this; + } + + /** + * @param destination Where vault tokens are stored: a table.column (skyvault) or token-group name (flowvault). Only used with VAULT_TOKEN. + * @return this builder + */ + public EntityBuilder destination(String destination) { + this.destination = destination; + return this; + } + + /** + * @param transformation Optional transformation applied to the detected value. + * @return this builder + */ + public EntityBuilder transformation(Transformation transformation) { + this.transformation = transformation; + return this; + } + + public Entity build() { + return new Entity(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/EntityLocation.java b/common/src/main/java/com/skyflow/detect/EntityLocation.java new file mode 100644 index 00000000..88b45048 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/EntityLocation.java @@ -0,0 +1,56 @@ +package com.skyflow.detect; + +/** + * Position of a detected entity in the original and processed text. + */ +public final class EntityLocation { + private final Integer startIndex; + private final Integer endIndex; + private final Integer startIndexProcessed; + private final Integer endIndexProcessed; + + public EntityLocation(Integer startIndex, Integer endIndex, Integer startIndexProcessed, Integer endIndexProcessed) { + this.startIndex = startIndex; + this.endIndex = endIndex; + this.startIndexProcessed = startIndexProcessed; + this.endIndexProcessed = endIndexProcessed; + } + + /** + * @return Start index in the original text. + */ + public Integer getStartIndex() { + return startIndex; + } + + /** + * @return End index in the original text. + */ + public Integer getEndIndex() { + return endIndex; + } + + /** + * @return Start index in the processed text. + */ + public Integer getStartIndexProcessed() { + return startIndexProcessed; + } + + /** + * @return End index in the processed text. + */ + public Integer getEndIndexProcessed() { + return endIndexProcessed; + } + + @Override + public String toString() { + return "EntityLocation{" + + "startIndex=" + startIndex + + ", endIndex=" + endIndex + + ", startIndexProcessed=" + startIndexProcessed + + ", endIndexProcessed=" + endIndexProcessed + + '}'; + } +} diff --git a/common/src/main/java/com/skyflow/detect/EntityType.java b/common/src/main/java/com/skyflow/detect/EntityType.java new file mode 100644 index 00000000..99cbbd8a --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/EntityType.java @@ -0,0 +1,78 @@ +package com.skyflow.detect; + +/** + * Entity types Skyflow Detect can identify. Also used to target entities in {@link RedactionLevel}. + */ +public enum EntityType { + NONE, + ACCOUNT_NUMBER, + AGE, + ALL, + BANK_ACCOUNT, + BLOOD_TYPE, + CONDITION, + CORPORATE_ACTION, + CREDIT_CARD, + CREDIT_CARD_EXPIRATION, + CVV, + DATE, + DATE_INTERVAL, + DAY, + DOB, + DOSE, + DRIVER_LICENSE, + DRUG, + DURATION, + EFFECT, + EMAIL_ADDRESS, + EVENT, + FILENAME, + FINANCIAL_METRIC, + GENDER, + HEALTHCARE_NUMBER, + INJURY, + IP_ADDRESS, + LANGUAGE, + LOCATION, + LOCATION_ADDRESS, + LOCATION_ADDRESS_STREET, + LOCATION_CITY, + LOCATION_COORDINATE, + LOCATION_COUNTRY, + LOCATION_STATE, + LOCATION_ZIP, + MARITAL_STATUS, + MEDICAL_CODE, + MEDICAL_PROCESS, + MONEY, + MONTH, + NAME, + NAME_FAMILY, + NAME_GIVEN, + NAME_MEDICAL_PROFESSIONAL, + NUMERICAL_PII, + OCCUPATION, + ORGANIZATION, + ORGANIZATION_ID, + ORGANIZATION_MEDICAL_FACILITY, + ORIGIN, + PASSPORT_NUMBER, + PASSWORD, + PHONE_NUMBER, + PHYSICAL_ATTRIBUTE, + POLITICAL_AFFILIATION, + PRODUCT, + PROJECT, + RELIGION, + ROUTING_NUMBER, + SEXUALITY, + SSN, + STATISTICS, + TIME, + TREND, + URL, + USERNAME, + VEHICLE_ID, + YEAR, + ZODIAC_SIGN; +} diff --git a/common/src/main/java/com/skyflow/detect/FileDataFormat.java b/common/src/main/java/com/skyflow/detect/FileDataFormat.java new file mode 100644 index 00000000..047cb54b --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/FileDataFormat.java @@ -0,0 +1,30 @@ +package com.skyflow.detect; + +/** + * File formats understood by Detect file operations. The wire value is the lower-case extension. + */ +public enum FileDataFormat { + BMP, + CSV, + DCM, + DOC, + DOCX, + GIF, + JPEG, + JPG, + JSON, + JSONL, + MP3, + PDF, + PNG, + PPT, + PPTX, + TIF, + TIFF, + TXT, + WAV, + XLS, + XLSX, + XML, + ZIP; +} diff --git a/common/src/main/java/com/skyflow/detect/FileMapping.java b/common/src/main/java/com/skyflow/detect/FileMapping.java new file mode 100644 index 00000000..da0663b0 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/FileMapping.java @@ -0,0 +1,133 @@ +package com.skyflow.detect; + +/** + * Maps an input file location to output locations. File inputs only. + */ +public final class FileMapping { + private final String source; + private final String destination; + private final String outputFileNameSuffix; + private final String entitiesDestination; + private final String objectEntitiesDestination; + + private FileMapping(FileMappingBuilder builder) { + this.source = builder.source; + this.destination = builder.destination; + this.outputFileNameSuffix = builder.outputFileNameSuffix; + this.entitiesDestination = builder.entitiesDestination; + this.objectEntitiesDestination = builder.objectEntitiesDestination; + } + + /** + * @return a new builder + */ + public static FileMappingBuilder builder() { + return new FileMappingBuilder(); + } + + /** + * @return Source location. + */ + public String getSource() { + return source; + } + + /** + * @return Destination for the processed file. + */ + public String getDestination() { + return destination; + } + + /** + * @return Suffix appended to the processed file name. + */ + public String getOutputFileNameSuffix() { + return outputFileNameSuffix; + } + + /** + * @return Destination for the detected entities file. + */ + public String getEntitiesDestination() { + return entitiesDestination; + } + + /** + * @return Destination for the detected object entities file. + */ + public String getObjectEntitiesDestination() { + return objectEntitiesDestination; + } + + @Override + public String toString() { + return "FileMapping{" + + "source=" + source + + ", destination=" + destination + + ", outputFileNameSuffix=" + outputFileNameSuffix + + ", entitiesDestination=" + entitiesDestination + + ", objectEntitiesDestination=" + objectEntitiesDestination + + '}'; + } + + public static final class FileMappingBuilder { + private String source; + private String destination; + private String outputFileNameSuffix; + private String entitiesDestination; + private String objectEntitiesDestination; + + private FileMappingBuilder() { + } + + /** + * @param source Source location. + * @return this builder + */ + public FileMappingBuilder source(String source) { + this.source = source; + return this; + } + + /** + * @param destination Destination for the processed file. + * @return this builder + */ + public FileMappingBuilder destination(String destination) { + this.destination = destination; + return this; + } + + /** + * @param outputFileNameSuffix Suffix appended to the processed file name. + * @return this builder + */ + public FileMappingBuilder outputFileNameSuffix(String outputFileNameSuffix) { + this.outputFileNameSuffix = outputFileNameSuffix; + return this; + } + + /** + * @param entitiesDestination Destination for the detected entities file. + * @return this builder + */ + public FileMappingBuilder entitiesDestination(String entitiesDestination) { + this.entitiesDestination = entitiesDestination; + return this; + } + + /** + * @param objectEntitiesDestination Destination for the detected object entities file. + * @return this builder + */ + public FileMappingBuilder objectEntitiesDestination(String objectEntitiesDestination) { + this.objectEntitiesDestination = objectEntitiesDestination; + return this; + } + + public FileMapping build() { + return new FileMapping(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/FileOutput.java b/common/src/main/java/com/skyflow/detect/FileOutput.java new file mode 100644 index 00000000..a33cafca --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/FileOutput.java @@ -0,0 +1,46 @@ +package com.skyflow.detect; + +/** + * One output artifact of a Detect file run. + */ +public final class FileOutput { + private final String processedFile; + private final String processedFileType; + private final FileDataFormat processedFileExtension; + + public FileOutput(String processedFile, String processedFileType, FileDataFormat processedFileExtension) { + this.processedFile = processedFile; + this.processedFileType = processedFileType; + this.processedFileExtension = processedFileExtension; + } + + /** + * @return Base64 content or presigned URL, according to the run's outputType. + */ + public String getProcessedFile() { + return processedFile; + } + + /** + * @return Kind of output, for example REDACTED_FILE or ENTITIES. Reported as a string because the API does not enumerate it. + */ + public String getProcessedFileType() { + return processedFileType; + } + + /** + * @return Format of the output, or null when not reported. + */ + public FileDataFormat getProcessedFileExtension() { + return processedFileExtension; + } + + @Override + public String toString() { + return "FileOutput{" + + "processedFile=" + processedFile + + ", processedFileType=" + processedFileType + + ", processedFileExtension=" + processedFileExtension + + '}'; + } +} diff --git a/common/src/main/java/com/skyflow/detect/GetRunRequest.java b/common/src/main/java/com/skyflow/detect/GetRunRequest.java new file mode 100644 index 00000000..21a02689 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/GetRunRequest.java @@ -0,0 +1,53 @@ +package com.skyflow.detect; + +/** + * Request for {@code getRun}: retrieves the status and outputs of a Detect file run. + */ +public final class GetRunRequest { + private final String runId; + + private GetRunRequest(GetRunRequestBuilder builder) { + this.runId = builder.runId; + } + + /** + * @return a new builder + */ + public static GetRunRequestBuilder builder() { + return new GetRunRequestBuilder(); + } + + /** + * @return Run id returned by {@code deidentifyFile}. Required. + */ + public String getRunId() { + return runId; + } + + @Override + public String toString() { + return "GetRunRequest{" + + "runId=" + runId + + '}'; + } + + public static final class GetRunRequestBuilder { + private String runId; + + private GetRunRequestBuilder() { + } + + /** + * @param runId Run id returned by {@code deidentifyFile}. Required. + * @return this builder + */ + public GetRunRequestBuilder runId(String runId) { + this.runId = runId; + return this; + } + + public GetRunRequest build() { + return new GetRunRequest(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/GetRunResponse.java b/common/src/main/java/com/skyflow/detect/GetRunResponse.java new file mode 100644 index 00000000..e8ffbc27 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/GetRunResponse.java @@ -0,0 +1,78 @@ +package com.skyflow.detect; + +import java.util.List; + +/** + * Response of {@code getRun}. Output, outputType and metrics are populated only once the run has succeeded. + */ +public final class GetRunResponse { + private final String runId; + private final DetectRunStatus status; + private final DataSourceType outputType; + private final List output; + private final Metrics metrics; + private final String message; + + public GetRunResponse(String runId, DetectRunStatus status, DataSourceType outputType, List output, Metrics metrics, String message) { + this.runId = runId; + this.status = status; + this.outputType = outputType; + this.output = output; + this.metrics = metrics; + this.message = message; + } + + /** + * @return Run id that was queried. + */ + public String getRunId() { + return runId; + } + + /** + * @return Run status. UNKNOWN when the API did not report one. + */ + public DetectRunStatus getStatus() { + return status; + } + + /** + * @return How output files are delivered. Null while the run is pending. + */ + public DataSourceType getOutputType() { + return outputType; + } + + /** + * @return Output artifacts. Empty while the run is pending, never null. + */ + public List getOutput() { + return output; + } + + /** + * @return Processing metrics. Null while the run is pending. + */ + public Metrics getMetrics() { + return metrics; + } + + /** + * @return Failure detail when status is FAILED. + */ + public String getMessage() { + return message; + } + + @Override + public String toString() { + return "GetRunResponse{" + + "runId=" + runId + + ", status=" + status + + ", outputType=" + outputType + + ", output=" + output + + ", metrics=" + metrics + + ", message=" + message + + '}'; + } +} diff --git a/common/src/main/java/com/skyflow/detect/GuardrailsValidation.java b/common/src/main/java/com/skyflow/detect/GuardrailsValidation.java new file mode 100644 index 00000000..1d6f0f8c --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/GuardrailsValidation.java @@ -0,0 +1,11 @@ +package com.skyflow.detect; + +/** + * Overall result of a guardrails check. + */ +public enum GuardrailsValidation { + PASSED, + FAILED, + /** The API reported a value this SDK version does not know. */ + UNKNOWN; +} diff --git a/common/src/main/java/com/skyflow/detect/Image.java b/common/src/main/java/com/skyflow/detect/Image.java new file mode 100644 index 00000000..f1e33135 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/Image.java @@ -0,0 +1,93 @@ +package com.skyflow.detect; + +/** + * Image processing options. + */ +public final class Image { + private final Boolean outputProcessedImage; + private final Boolean outputOcrText; + private final MaskingMethod maskingMethod; + + private Image(ImageBuilder builder) { + this.outputProcessedImage = builder.outputProcessedImage; + this.outputOcrText = builder.outputOcrText; + this.maskingMethod = builder.maskingMethod; + } + + /** + * @return a new builder + */ + public static ImageBuilder builder() { + return new ImageBuilder(); + } + + /** + * @return Whether to return the processed image. + */ + public Boolean getOutputProcessedImage() { + return outputProcessedImage; + } + + /** + * @return Whether to return OCR text. + */ + public Boolean getOutputOcrText() { + return outputOcrText; + } + + /** + * @return Masking method for detected regions. + */ + public MaskingMethod getMaskingMethod() { + return maskingMethod; + } + + @Override + public String toString() { + return "Image{" + + "outputProcessedImage=" + outputProcessedImage + + ", outputOcrText=" + outputOcrText + + ", maskingMethod=" + maskingMethod + + '}'; + } + + public static final class ImageBuilder { + private Boolean outputProcessedImage; + private Boolean outputOcrText; + private MaskingMethod maskingMethod; + + private ImageBuilder() { + } + + /** + * @param outputProcessedImage Whether to return the processed image. + * @return this builder + */ + public ImageBuilder outputProcessedImage(Boolean outputProcessedImage) { + this.outputProcessedImage = outputProcessedImage; + return this; + } + + /** + * @param outputOcrText Whether to return OCR text. + * @return this builder + */ + public ImageBuilder outputOcrText(Boolean outputOcrText) { + this.outputOcrText = outputOcrText; + return this; + } + + /** + * @param maskingMethod Masking method for detected regions. + * @return this builder + */ + public ImageBuilder maskingMethod(MaskingMethod maskingMethod) { + this.maskingMethod = maskingMethod; + return this; + } + + public Image build() { + return new Image(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/MaskingMethod.java b/common/src/main/java/com/skyflow/detect/MaskingMethod.java new file mode 100644 index 00000000..38402c52 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/MaskingMethod.java @@ -0,0 +1,10 @@ +package com.skyflow.detect; + +/** + * Masking method applied to detected regions in images. + */ +public enum MaskingMethod { + NONE, + BLUR, + BLACKBOX; +} diff --git a/common/src/main/java/com/skyflow/detect/Media.java b/common/src/main/java/com/skyflow/detect/Media.java new file mode 100644 index 00000000..af6f24ac --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/Media.java @@ -0,0 +1,93 @@ +package com.skyflow.detect; + +/** + * Media-type specific processing options. File inputs only. + */ +public final class Media { + private final Audio audio; + private final Document document; + private final Image image; + + private Media(MediaBuilder builder) { + this.audio = builder.audio; + this.document = builder.document; + this.image = builder.image; + } + + /** + * @return a new builder + */ + public static MediaBuilder builder() { + return new MediaBuilder(); + } + + /** + * @return Audio options. + */ + public Audio getAudio() { + return audio; + } + + /** + * @return Document options. + */ + public Document getDocument() { + return document; + } + + /** + * @return Image options. + */ + public Image getImage() { + return image; + } + + @Override + public String toString() { + return "Media{" + + "audio=" + audio + + ", document=" + document + + ", image=" + image + + '}'; + } + + public static final class MediaBuilder { + private Audio audio; + private Document document; + private Image image; + + private MediaBuilder() { + } + + /** + * @param audio Audio options. + * @return this builder + */ + public MediaBuilder audio(Audio audio) { + this.audio = audio; + return this; + } + + /** + * @param document Document options. + * @return this builder + */ + public MediaBuilder document(Document document) { + this.document = document; + return this; + } + + /** + * @param image Image options. + * @return this builder + */ + public MediaBuilder image(Image image) { + this.image = image; + return this; + } + + public Media build() { + return new Media(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/Metrics.java b/common/src/main/java/com/skyflow/detect/Metrics.java new file mode 100644 index 00000000..46becb12 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/Metrics.java @@ -0,0 +1,76 @@ +package com.skyflow.detect; + +/** + * Processing metrics returned by Detect operations. + */ +public final class Metrics { + private final Double size; + private final Integer wordCount; + private final Integer characterCount; + private final Integer slides; + private final Integer pages; + private final Double duration; + + public Metrics(Double size, Integer wordCount, Integer characterCount, Integer slides, Integer pages, Double duration) { + this.size = size; + this.wordCount = wordCount; + this.characterCount = characterCount; + this.slides = slides; + this.pages = pages; + this.duration = duration; + } + + /** + * @return Input size in kilobytes. + */ + public Double getSize() { + return size; + } + + /** + * @return Number of words. + */ + public Integer getWordCount() { + return wordCount; + } + + /** + * @return Number of characters. + */ + public Integer getCharacterCount() { + return characterCount; + } + + /** + * @return Number of slides. + */ + public Integer getSlides() { + return slides; + } + + /** + * @return Number of pages. + */ + public Integer getPages() { + return pages; + } + + /** + * @return Media duration in seconds. + */ + public Double getDuration() { + return duration; + } + + @Override + public String toString() { + return "Metrics{" + + "size=" + size + + ", wordCount=" + wordCount + + ", characterCount=" + characterCount + + ", slides=" + slides + + ", pages=" + pages + + ", duration=" + duration + + '}'; + } +} diff --git a/common/src/main/java/com/skyflow/detect/ObjectDeidentificationType.java b/common/src/main/java/com/skyflow/detect/ObjectDeidentificationType.java new file mode 100644 index 00000000..13f3fd6e --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/ObjectDeidentificationType.java @@ -0,0 +1,10 @@ +package com.skyflow.detect; + +/** + * How a detected visual object is handled. + */ +public enum ObjectDeidentificationType { + UNKNOWN, + REDACT, + UNREDACT; +} diff --git a/common/src/main/java/com/skyflow/detect/ObjectEntity.java b/common/src/main/java/com/skyflow/detect/ObjectEntity.java new file mode 100644 index 00000000..1f62853b --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/ObjectEntity.java @@ -0,0 +1,73 @@ +package com.skyflow.detect; + +/** + * Configures detection of a visual object type in images or video. + */ +public final class ObjectEntity { + private final ObjectEntityType entityType; + private final ObjectDeidentificationType deidentificationType; + + private ObjectEntity(ObjectEntityBuilder builder) { + this.entityType = builder.entityType; + this.deidentificationType = builder.deidentificationType; + } + + /** + * @return a new builder + */ + public static ObjectEntityBuilder builder() { + return new ObjectEntityBuilder(); + } + + /** + * @return Object type this rule applies to. + */ + public ObjectEntityType getEntityType() { + return entityType; + } + + /** + * @return How matches are handled. + */ + public ObjectDeidentificationType getDeidentificationType() { + return deidentificationType; + } + + @Override + public String toString() { + return "ObjectEntity{" + + "entityType=" + entityType + + ", deidentificationType=" + deidentificationType + + '}'; + } + + public static final class ObjectEntityBuilder { + private ObjectEntityType entityType; + private ObjectDeidentificationType deidentificationType; + + private ObjectEntityBuilder() { + } + + /** + * @param entityType Object type this rule applies to. + * @return this builder + */ + public ObjectEntityBuilder entityType(ObjectEntityType entityType) { + this.entityType = entityType; + return this; + } + + /** + * @param deidentificationType How matches are handled. + * @return this builder + */ + public ObjectEntityBuilder deidentificationType(ObjectDeidentificationType deidentificationType) { + this.deidentificationType = deidentificationType; + return this; + } + + public ObjectEntity build() { + return new ObjectEntity(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/ObjectEntityType.java b/common/src/main/java/com/skyflow/detect/ObjectEntityType.java new file mode 100644 index 00000000..679917c7 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/ObjectEntityType.java @@ -0,0 +1,13 @@ +package com.skyflow.detect; + +/** + * Visual objects Skyflow Detect can identify in images and video. + */ +public enum ObjectEntityType { + NONE, + FACE, + LICENSE_PLATE, + LOGO, + SIGNATURE, + ALL; +} diff --git a/common/src/main/java/com/skyflow/detect/OutputTranscriptionType.java b/common/src/main/java/com/skyflow/detect/OutputTranscriptionType.java new file mode 100644 index 00000000..ed2638ed --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/OutputTranscriptionType.java @@ -0,0 +1,12 @@ +package com.skyflow.detect; + +/** + * Transcription output produced for audio inputs. + */ +public enum OutputTranscriptionType { + NONE, + TRANSCRIPTION, + MEDICAL_TRANSCRIPTION, + DIARIZED_TRANSCRIPTION, + MEDICAL_DIARIZED_TRANSCRIPTION; +} diff --git a/common/src/main/java/com/skyflow/detect/Pdf.java b/common/src/main/java/com/skyflow/detect/Pdf.java new file mode 100644 index 00000000..d147d632 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/Pdf.java @@ -0,0 +1,93 @@ +package com.skyflow.detect; + +/** + * PDF processing options. + */ +public final class Pdf { + private final PdfProcessingMode processingMode; + private final Integer density; + private final Integer maxResolution; + + private Pdf(PdfBuilder builder) { + this.processingMode = builder.processingMode; + this.density = builder.density; + this.maxResolution = builder.maxResolution; + } + + /** + * @return a new builder + */ + public static PdfBuilder builder() { + return new PdfBuilder(); + } + + /** + * @return Processing mode. + */ + public PdfProcessingMode getProcessingMode() { + return processingMode; + } + + /** + * @return Rendering density (DPI). + */ + public Integer getDensity() { + return density; + } + + /** + * @return Maximum rendering resolution. + */ + public Integer getMaxResolution() { + return maxResolution; + } + + @Override + public String toString() { + return "Pdf{" + + "processingMode=" + processingMode + + ", density=" + density + + ", maxResolution=" + maxResolution + + '}'; + } + + public static final class PdfBuilder { + private PdfProcessingMode processingMode; + private Integer density; + private Integer maxResolution; + + private PdfBuilder() { + } + + /** + * @param processingMode Processing mode. + * @return this builder + */ + public PdfBuilder processingMode(PdfProcessingMode processingMode) { + this.processingMode = processingMode; + return this; + } + + /** + * @param density Rendering density (DPI). + * @return this builder + */ + public PdfBuilder density(Integer density) { + this.density = density; + return this; + } + + /** + * @param maxResolution Maximum rendering resolution. + * @return this builder + */ + public PdfBuilder maxResolution(Integer maxResolution) { + this.maxResolution = maxResolution; + return this; + } + + public Pdf build() { + return new Pdf(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/PdfProcessingMode.java b/common/src/main/java/com/skyflow/detect/PdfProcessingMode.java new file mode 100644 index 00000000..f32399ba --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/PdfProcessingMode.java @@ -0,0 +1,10 @@ +package com.skyflow.detect; + +/** + * How PDF documents are processed. + */ +public enum PdfProcessingMode { + NONE, + OCR, + TEXT_LAYER; +} diff --git a/common/src/main/java/com/skyflow/detect/PollOptions.java b/common/src/main/java/com/skyflow/detect/PollOptions.java new file mode 100644 index 00000000..c1aa656a --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/PollOptions.java @@ -0,0 +1,91 @@ +package com.skyflow.detect; + +/** + * Opts {@code deidentifyFile} into submit-and-poll mode. When present, the SDK polls the run with + * exponential backoff (1, 2, 4, 8, 16, then 32 seconds) until it reaches a terminal status, the + * wait time is used up, or the attempt limit is hit. An empty {@code PollOptions.builder().build()} + * uses the defaults. + */ +public final class PollOptions { + public static final int DEFAULT_WAIT_TIME_SECONDS = 60; + public static final int MAX_WAIT_TIME_SECONDS = 300; + public static final int DEFAULT_MAX_ATTEMPTS = 23; + public static final int MAX_MAX_ATTEMPTS = 23; + + private final Integer waitTime; + private final Integer maxAttempts; + + private PollOptions(PollOptionsBuilder builder) { + this.waitTime = builder.waitTime; + this.maxAttempts = builder.maxAttempts; + } + + /** + * @return a new builder + */ + public static PollOptionsBuilder builder() { + return new PollOptionsBuilder(); + } + + /** + * @return Maximum total seconds to keep polling, or null for the default of 60. At most 300. + */ + public Integer getWaitTime() { + return waitTime; + } + + /** + * @return Maximum number of run lookups, or null for the default of 23. At most 23. + */ + public Integer getMaxAttempts() { + return maxAttempts; + } + + /** @return the wait time with the default applied */ + public int effectiveWaitTime() { + return waitTime == null ? DEFAULT_WAIT_TIME_SECONDS : waitTime; + } + + /** @return the attempt limit with the default applied */ + public int effectiveMaxAttempts() { + return maxAttempts == null ? DEFAULT_MAX_ATTEMPTS : maxAttempts; + } + + @Override + public String toString() { + return "PollOptions{" + + "waitTime=" + waitTime + + ", maxAttempts=" + maxAttempts + + '}'; + } + + public static final class PollOptionsBuilder { + private Integer waitTime; + private Integer maxAttempts; + + private PollOptionsBuilder() { + } + + /** + * @param waitTime Maximum total seconds to keep polling. 1 to 300; default 60. + * @return this builder + */ + public PollOptionsBuilder waitTime(Integer waitTime) { + this.waitTime = waitTime; + return this; + } + + /** + * @param maxAttempts Maximum number of run lookups. 1 to 23; default 23. + * @return this builder + */ + public PollOptionsBuilder maxAttempts(Integer maxAttempts) { + this.maxAttempts = maxAttempts; + return this; + } + + public PollOptions build() { + return new PollOptions(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/RedactionLevel.java b/common/src/main/java/com/skyflow/detect/RedactionLevel.java new file mode 100644 index 00000000..74f04f1e --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/RedactionLevel.java @@ -0,0 +1,113 @@ +package com.skyflow.detect; + +/** + * Controls how one group of tokens is rendered when re-identifying. Supply exactly one source (entityName or tokenGroupName) and exactly one replacement (redactionType or redactionPattern). + */ +public final class RedactionLevel { + private final EntityType entityName; + private final RedactionType redactionType; + private final String tokenGroupName; + private final String redactionPattern; + + private RedactionLevel(RedactionLevelBuilder builder) { + this.entityName = builder.entityName; + this.redactionType = builder.redactionType; + this.tokenGroupName = builder.tokenGroupName; + this.redactionPattern = builder.redactionPattern; + } + + /** + * @return a new builder + */ + public static RedactionLevelBuilder builder() { + return new RedactionLevelBuilder(); + } + + /** + * @return Entity to target. skyvault vaults. + */ + public EntityType getEntityName() { + return entityName; + } + + /** + * @return Replacement rendering. skyvault vaults. + */ + public RedactionType getRedactionType() { + return redactionType; + } + + /** + * @return Token group to target. flowvault vaults. + */ + public String getTokenGroupName() { + return tokenGroupName; + } + + /** + * @return Named redaction pattern to apply. flowvault vaults. + */ + public String getRedactionPattern() { + return redactionPattern; + } + + @Override + public String toString() { + return "RedactionLevel{" + + "entityName=" + entityName + + ", redactionType=" + redactionType + + ", tokenGroupName=" + tokenGroupName + + ", redactionPattern=" + redactionPattern + + '}'; + } + + public static final class RedactionLevelBuilder { + private EntityType entityName; + private RedactionType redactionType; + private String tokenGroupName; + private String redactionPattern; + + private RedactionLevelBuilder() { + } + + /** + * @param entityName Entity to target. skyvault vaults. + * @return this builder + */ + public RedactionLevelBuilder entityName(EntityType entityName) { + this.entityName = entityName; + return this; + } + + /** + * @param redactionType Replacement rendering. skyvault vaults. + * @return this builder + */ + public RedactionLevelBuilder redactionType(RedactionType redactionType) { + this.redactionType = redactionType; + return this; + } + + /** + * @param tokenGroupName Token group to target. flowvault vaults. + * @return this builder + */ + public RedactionLevelBuilder tokenGroupName(String tokenGroupName) { + this.tokenGroupName = tokenGroupName; + return this; + } + + /** + * @param redactionPattern Named redaction pattern to apply. flowvault vaults. + * @return this builder + */ + public RedactionLevelBuilder redactionPattern(String redactionPattern) { + this.redactionPattern = redactionPattern; + return this; + } + + public RedactionLevel build() { + return new RedactionLevel(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/RedactionType.java b/common/src/main/java/com/skyflow/detect/RedactionType.java new file mode 100644 index 00000000..87b3e891 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/RedactionType.java @@ -0,0 +1,11 @@ +package com.skyflow.detect; + +/** + * How a token is rendered when re-identifying (skyvault vaults). + */ +public enum RedactionType { + PLAIN_TEXT, + MASKED, + DEFAULT, + REDACTED; +} diff --git a/common/src/main/java/com/skyflow/detect/ReidentifyStringRequest.java b/common/src/main/java/com/skyflow/detect/ReidentifyStringRequest.java new file mode 100644 index 00000000..23626297 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/ReidentifyStringRequest.java @@ -0,0 +1,75 @@ +package com.skyflow.detect; + +import java.util.List; + +/** + * Request for {@code reidentifyString}. + */ +public final class ReidentifyStringRequest { + private final String text; + private final List redactionLevel; + + private ReidentifyStringRequest(ReidentifyStringRequestBuilder builder) { + this.text = builder.text; + this.redactionLevel = builder.redactionLevel; + } + + /** + * @return a new builder + */ + public static ReidentifyStringRequestBuilder builder() { + return new ReidentifyStringRequestBuilder(); + } + + /** + * @return Text containing tokens to re-identify. Required. + */ + public String getText() { + return text; + } + + /** + * @return Optional per-group rendering rules. + */ + public List getRedactionLevel() { + return redactionLevel; + } + + @Override + public String toString() { + return "ReidentifyStringRequest{" + + "text=" + text + + ", redactionLevel=" + redactionLevel + + '}'; + } + + public static final class ReidentifyStringRequestBuilder { + private String text; + private List redactionLevel; + + private ReidentifyStringRequestBuilder() { + } + + /** + * @param text Text containing tokens to re-identify. Required. + * @return this builder + */ + public ReidentifyStringRequestBuilder text(String text) { + this.text = text; + return this; + } + + /** + * @param redactionLevel Optional per-group rendering rules. + * @return this builder + */ + public ReidentifyStringRequestBuilder redactionLevel(List redactionLevel) { + this.redactionLevel = redactionLevel; + return this; + } + + public ReidentifyStringRequest build() { + return new ReidentifyStringRequest(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/ReidentifyStringResponse.java b/common/src/main/java/com/skyflow/detect/ReidentifyStringResponse.java new file mode 100644 index 00000000..e1825bf2 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/ReidentifyStringResponse.java @@ -0,0 +1,36 @@ +package com.skyflow.detect; + +/** + * Response of {@code reidentifyString}. + */ +public final class ReidentifyStringResponse { + private final String processedText; + private final Metrics metrics; + + public ReidentifyStringResponse(String processedText, Metrics metrics) { + this.processedText = processedText; + this.metrics = metrics; + } + + /** + * @return Re-identified text. + */ + public String getProcessedText() { + return processedText; + } + + /** + * @return Processing metrics. + */ + public Metrics getMetrics() { + return metrics; + } + + @Override + public String toString() { + return "ReidentifyStringResponse{" + + "processedText=" + processedText + + ", metrics=" + metrics + + '}'; + } +} diff --git a/common/src/main/java/com/skyflow/detect/ReturnEntitiesType.java b/common/src/main/java/com/skyflow/detect/ReturnEntitiesType.java new file mode 100644 index 00000000..3ba4fa79 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/ReturnEntitiesType.java @@ -0,0 +1,10 @@ +package com.skyflow.detect; + +/** + * Controls which detected entities are returned in the response. + */ +public enum ReturnEntitiesType { + EXCLUDE_SENSITIVE_DATA, + ALL, + NONE; +} diff --git a/common/src/main/java/com/skyflow/detect/ShiftDates.java b/common/src/main/java/com/skyflow/detect/ShiftDates.java new file mode 100644 index 00000000..fd4adc24 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/ShiftDates.java @@ -0,0 +1,73 @@ +package com.skyflow.detect; + +/** + * Shifts detected dates by a random number of days within the given range. + */ +public final class ShiftDates { + private final Integer minDays; + private final Integer maxDays; + + private ShiftDates(ShiftDatesBuilder builder) { + this.minDays = builder.minDays; + this.maxDays = builder.maxDays; + } + + /** + * @return a new builder + */ + public static ShiftDatesBuilder builder() { + return new ShiftDatesBuilder(); + } + + /** + * @return Minimum number of days to shift. + */ + public Integer getMinDays() { + return minDays; + } + + /** + * @return Maximum number of days to shift. + */ + public Integer getMaxDays() { + return maxDays; + } + + @Override + public String toString() { + return "ShiftDates{" + + "minDays=" + minDays + + ", maxDays=" + maxDays + + '}'; + } + + public static final class ShiftDatesBuilder { + private Integer minDays; + private Integer maxDays; + + private ShiftDatesBuilder() { + } + + /** + * @param minDays Minimum number of days to shift. + * @return this builder + */ + public ShiftDatesBuilder minDays(Integer minDays) { + this.minDays = minDays; + return this; + } + + /** + * @param maxDays Maximum number of days to shift. + * @return this builder + */ + public ShiftDatesBuilder maxDays(Integer maxDays) { + this.maxDays = maxDays; + return this; + } + + public ShiftDates build() { + return new ShiftDates(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/Transformation.java b/common/src/main/java/com/skyflow/detect/Transformation.java new file mode 100644 index 00000000..5fb9cb69 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/Transformation.java @@ -0,0 +1,53 @@ +package com.skyflow.detect; + +/** + * Transformations applied to detected entities before they are written to the output. + */ +public final class Transformation { + private final ShiftDates shiftDates; + + private Transformation(TransformationBuilder builder) { + this.shiftDates = builder.shiftDates; + } + + /** + * @return a new builder + */ + public static TransformationBuilder builder() { + return new TransformationBuilder(); + } + + /** + * @return Date shifting configuration. + */ + public ShiftDates getShiftDates() { + return shiftDates; + } + + @Override + public String toString() { + return "Transformation{" + + "shiftDates=" + shiftDates + + '}'; + } + + public static final class TransformationBuilder { + private ShiftDates shiftDates; + + private TransformationBuilder() { + } + + /** + * @param shiftDates Date shifting configuration. + * @return this builder + */ + public TransformationBuilder shiftDates(ShiftDates shiftDates) { + this.shiftDates = shiftDates; + return this; + } + + public Transformation build() { + return new Transformation(this); + } + } +} diff --git a/common/src/main/java/com/skyflow/detect/internal/DeidentifyFileFlow.java b/common/src/main/java/com/skyflow/detect/internal/DeidentifyFileFlow.java new file mode 100644 index 00000000..4d825983 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/internal/DeidentifyFileFlow.java @@ -0,0 +1,41 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.DeidentifyFileRequest; +import com.skyflow.detect.DeidentifyFileResponse; +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.GetRunResponse; +import com.skyflow.detect.polling.RunPoller; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.BaseUtils; +import com.skyflow.utils.logger.LogUtil; + +/** + * Everything {@code deidentifyFile} does after the submit call, shared by every SDK: decide whether to + * poll, poll, merge the run into the response, and write outputs for file-based requests. Each + * controller performs the submit itself and hands the run id here. + */ +public final class DeidentifyFileFlow { + private DeidentifyFileFlow() { + } + + public static DeidentifyFileResponse complete(DeidentifyFileRequest request, String runId, RunPoller.RunFetcher fetcher) + throws SkyflowException { + return complete(request, runId, fetcher, Thread::sleep); + } + + static DeidentifyFileResponse complete(DeidentifyFileRequest request, String runId, RunPoller.RunFetcher fetcher, + RunPoller.Sleeper sleeper) throws SkyflowException { + if (request.getPollOptions() == null || runId == null) { + LogUtil.printInfoLog(BaseUtils.parameterizedString(InfoLogs.DEIDENTIFY_FILE_RUN_SUBMITTED.getLog(), String.valueOf(runId))); + return DetectResponseMapper.submittedDeidentifyFileResponse(runId); + } + GetRunResponse run = RunPoller.poll(runId, request.getPollOptions(), fetcher, sleeper); + DeidentifyFileResponse response = DetectResponseMapper.toDeidentifyFileResponse(runId, run); + if (response.getStatus() == DetectRunStatus.SUCCESS && request.getFile() != null) { + FileOutputWriter.write(response.getOutput(), response.getOutputType(), request.getFile().getName(), + request.getOutputDirectory()); + } + return response; + } +} diff --git a/common/src/main/java/com/skyflow/detect/internal/DetectRequestMapper.java b/common/src/main/java/com/skyflow/detect/internal/DetectRequestMapper.java new file mode 100644 index 00000000..42ef0faf --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/internal/DetectRequestMapper.java @@ -0,0 +1,407 @@ +package com.skyflow.detect.internal; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import com.skyflow.detect.Audio; +import com.skyflow.detect.Bleep; +import com.skyflow.detect.CheckGuardrailsRequest; +import com.skyflow.detect.CustomType; +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.DeidentifyFileRequest; +import com.skyflow.detect.DeidentifyStringRequest; +import com.skyflow.detect.FileDataFormat; +import com.skyflow.detect.Detect; +import com.skyflow.detect.DetectConfiguration; +import com.skyflow.detect.Document; +import com.skyflow.detect.Entity; +import com.skyflow.detect.FileMapping; +import com.skyflow.detect.Image; +import com.skyflow.detect.Media; +import com.skyflow.detect.ObjectEntity; +import com.skyflow.detect.Pdf; +import com.skyflow.detect.RedactionLevel; +import com.skyflow.detect.ReidentifyStringRequest; +import com.skyflow.detect.ShiftDates; +import com.skyflow.detect.Transformation; +import com.skyflow.errors.BaseErrorMessage; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.SkyflowException; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.DeidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.resources.filesv2.types.DeidentifyFileRequestV2DataFormat; +import com.skyflow.generated.detect.rest.resources.filesv2.types.DeidentifyFileRequestV2DataSource; +import com.skyflow.generated.detect.rest.resources.guardrailsv2.requests.DetectGuardrailsRequestV2; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.utils.BaseUtils; +import com.skyflow.utils.logger.LogUtil; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.DeidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.ReidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.types.AudioV2; +import com.skyflow.generated.detect.rest.types.AudioV2Bleep; +import com.skyflow.generated.detect.rest.types.AudioV2OutputTranscription; +import com.skyflow.generated.detect.rest.types.DetectConfigV2; +import com.skyflow.generated.detect.rest.types.DetectCustomTypes; +import com.skyflow.generated.detect.rest.types.DetectEntity; +import com.skyflow.generated.detect.rest.types.DetectEntityDeidentificationType; +import com.skyflow.generated.detect.rest.types.DetectEntityEntityType; +import com.skyflow.generated.detect.rest.types.DetectObjectEntity; +import com.skyflow.generated.detect.rest.types.DetectObjectEntityDeidentificationType; +import com.skyflow.generated.detect.rest.types.DetectObjectEntityEntityType; +import com.skyflow.generated.detect.rest.types.DetectReturnEntities; +import com.skyflow.generated.detect.rest.types.DocumentV2; +import com.skyflow.generated.detect.rest.types.DocumentV2Pdf; +import com.skyflow.generated.detect.rest.types.DocumentV2PdfProcessingMode; +import com.skyflow.generated.detect.rest.types.EntityTransformation; +import com.skyflow.generated.detect.rest.types.ImageV2; +import com.skyflow.generated.detect.rest.types.ImageV2MaskingMethod; +import com.skyflow.generated.detect.rest.types.RedactionLevelEntityName; +import com.skyflow.generated.detect.rest.types.RedactionLevelRedactionType; +import com.skyflow.generated.detect.rest.types.TransformationShiftDates; + +/** + * Converts public SDK request models into generated V2 API requests. Internal to the SDK. + */ +public final class DetectRequestMapper { + private DetectRequestMapper() { + } + + public static DeidentifyStringRequestV2 toDeidentifyStringRequest(DeidentifyStringRequest request, String vaultId) { + DeidentifyStringRequestV2._FinalStage builder = DeidentifyStringRequestV2.builder().text(request.getText()); + if (request.getConfigurationId() != null) { + builder.configurationId(request.getConfigurationId()); + } + if (request.getConfiguration() != null) { + builder.configuration(toDetectConfig(request.getConfiguration(), vaultId)); + } + return builder.build(); + } + + /** + * Builds the submit request. For file-based requests this reads and base64-encodes the file and infers + * the format from its extension when none was given. + */ + public static DeidentifyFileRequestV2 toDeidentifyFileRequest(DeidentifyFileRequest request, String vaultId) + throws SkyflowException { + String value = request.getValue(); + FileDataFormat format = request.getDataFormat(); + File file = request.getFile(); + if (file != null) { + value = encodeFile(file); + if (format == null) { + format = inferFormat(file.getName()); + } + } + // A local file is always sent as BASE64, whatever dataSource the builder was left with. + DataSourceType dataSource = file != null ? DataSourceType.BASE64 : request.getDataSource(); + DeidentifyFileRequestV2._FinalStage builder = DeidentifyFileRequestV2.builder() + .dataSource(DeidentifyFileRequestV2DataSource.valueOf(dataSource.name())) + .value(value); + if (format != null) { + // The wire value for a file format is the lower-case extension. + builder.dataFormat(DeidentifyFileRequestV2DataFormat.valueOf(format.name().toLowerCase())); + } + if (request.getConfigurationId() != null) { + builder.configurationId(request.getConfigurationId()); + } + if (request.getConfiguration() != null) { + builder.configuration(toDetectConfig(request.getConfiguration(), vaultId)); + } + return builder.build(); + } + + static String encodeFile(File file) throws SkyflowException { + try { + return Base64.getEncoder().encodeToString(Files.readAllBytes(file.toPath())); + } catch (IOException | OutOfMemoryError e) { + LogUtil.printErrorLog(BaseUtils.parameterizedString(ErrorLogs.FAILED_TO_ENCODE_FILE_IN_DEIDENTIFY_FILE.getLog(), "detect")); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.FailedToEncodeFile.getMessage()); + } + } + + /** @return the format matching the file extension, or null when unknown so the server infers it */ + static FileDataFormat inferFormat(String fileName) { + int dot = fileName == null ? -1 : fileName.lastIndexOf('.'); + if (dot < 0 || dot == fileName.length() - 1) { + return null; + } + try { + return FileDataFormat.valueOf(fileName.substring(dot + 1).toUpperCase()); + } catch (IllegalArgumentException unknown) { + return null; + } + } + + public static ReidentifyStringRequestV2 toReidentifyStringRequest(ReidentifyStringRequest request, String vaultId) { + ReidentifyStringRequestV2._FinalStage builder = ReidentifyStringRequestV2.builder() + .vaultId(vaultId) + .text(request.getText()); + if (request.getRedactionLevel() != null) { + builder.redactionLevel(toRedactionLevels(request.getRedactionLevel())); + } + return builder.build(); + } + + public static DetectGuardrailsRequestV2 toCheckGuardrailsRequest(CheckGuardrailsRequest request, String vaultId) { + DetectGuardrailsRequestV2._FinalStage builder = DetectGuardrailsRequestV2.builder() + .text(request.getText()) + .vaultId(vaultId); + if (request.getCheckToxicity() != null) { + builder.checkToxicity(request.getCheckToxicity()); + } + if (request.getDenyTopics() != null) { + builder.denyTopics(request.getDenyTopics()); + } + return builder.build(); + } + + public static DetectConfigV2 toDetectConfig(DetectConfiguration configuration, String vaultId) { + DetectConfigV2._FinalStage builder = DetectConfigV2.builder().vaultId(vaultId); + if (configuration.getName() != null) { + builder.name(configuration.getName()); + } + if (configuration.getDescription() != null) { + builder.description(configuration.getDescription()); + } + if (configuration.getDetect() != null) { + builder.detect(toDetect(configuration.getDetect())); + } + if (configuration.getFileMapping() != null) { + builder.fileMapping(toFileMappings(configuration.getFileMapping())); + } + if (configuration.getMedia() != null) { + builder.media(toMedia(configuration.getMedia())); + } + return builder.build(); + } + + static com.skyflow.generated.detect.rest.types.Detect toDetect(Detect detect) { + com.skyflow.generated.detect.rest.types.Detect.Builder builder = + com.skyflow.generated.detect.rest.types.Detect.builder(); + if (detect.getEntities() != null) { + List entities = new ArrayList<>(); + for (Entity entity : detect.getEntities()) { + if (entity != null) { + entities.add(toEntity(entity)); + } + } + builder.entities(entities); + } + if (detect.getObjectEntities() != null) { + List objectEntities = new ArrayList<>(); + for (ObjectEntity objectEntity : detect.getObjectEntities()) { + if (objectEntity != null) { + objectEntities.add(toObjectEntity(objectEntity)); + } + } + builder.objectEntities(objectEntities); + } + if (detect.getRestrict() != null) { + builder.restrict(detect.getRestrict()); + } + if (detect.getSkip() != null) { + builder.skip(detect.getSkip()); + } + if (detect.getCustomTypes() != null) { + List customTypes = new ArrayList<>(); + for (CustomType customType : detect.getCustomTypes()) { + if (customType != null) { + customTypes.add(toCustomType(customType)); + } + } + builder.customTypes(customTypes); + } + if (detect.getReturnEntities() != null) { + builder.returnEntities(DetectReturnEntities.valueOf(detect.getReturnEntities().name())); + } + return builder.build(); + } + + static DetectEntity toEntity(Entity entity) { + DetectEntity.Builder builder = DetectEntity.builder(); + if (entity.getEntityType() != null) { + builder.entityType(DetectEntityEntityType.valueOf(entity.getEntityType().name())); + } + if (entity.getDeidentificationType() != null) { + builder.deidentificationType(DetectEntityDeidentificationType.valueOf(entity.getDeidentificationType().name())); + } + if (entity.getDestination() != null) { + builder.destination(entity.getDestination()); + } + if (entity.getTransformation() != null) { + builder.transformation(toTransformation(entity.getTransformation())); + } + return builder.build(); + } + + static EntityTransformation toTransformation(Transformation transformation) { + EntityTransformation.Builder builder = EntityTransformation.builder(); + ShiftDates shiftDates = transformation.getShiftDates(); + if (shiftDates != null) { + TransformationShiftDates.Builder shift = TransformationShiftDates.builder(); + if (shiftDates.getMinDays() != null) { + shift.minDays(shiftDates.getMinDays()); + } + if (shiftDates.getMaxDays() != null) { + shift.maxDays(shiftDates.getMaxDays()); + } + builder.shiftDates(shift.build()); + } + return builder.build(); + } + + static DetectCustomTypes toCustomType(CustomType customType) { + DetectCustomTypes.Builder builder = DetectCustomTypes.builder(); + if (customType.getLabel() != null) { + builder.label(customType.getLabel()); + } + if (customType.getMatch() != null) { + builder.match(customType.getMatch()); + } + return builder.build(); + } + + static DetectObjectEntity toObjectEntity(ObjectEntity objectEntity) { + DetectObjectEntity.Builder builder = DetectObjectEntity.builder(); + if (objectEntity.getEntityType() != null) { + builder.entityType(DetectObjectEntityEntityType.valueOf(objectEntity.getEntityType().name())); + } + if (objectEntity.getDeidentificationType() != null) { + builder.deidentificationType( + DetectObjectEntityDeidentificationType.valueOf(objectEntity.getDeidentificationType().name())); + } + return builder.build(); + } + + static List toFileMappings(List fileMappings) { + List result = new ArrayList<>(); + for (FileMapping mapping : fileMappings) { + if (mapping == null) { + continue; + } + com.skyflow.generated.detect.rest.types.FileMapping.Builder builder = + com.skyflow.generated.detect.rest.types.FileMapping.builder(); + if (mapping.getSource() != null) { + builder.source(mapping.getSource()); + } + if (mapping.getDestination() != null) { + builder.destination(mapping.getDestination()); + } + if (mapping.getOutputFileNameSuffix() != null) { + builder.outputFileNameSuffix(mapping.getOutputFileNameSuffix()); + } + if (mapping.getEntitiesDestination() != null) { + builder.entitiesDestination(mapping.getEntitiesDestination()); + } + if (mapping.getObjectEntitiesDestination() != null) { + builder.objectEntitiesDestination(mapping.getObjectEntitiesDestination()); + } + result.add(builder.build()); + } + return result; + } + + static com.skyflow.generated.detect.rest.types.Media toMedia(Media media) { + com.skyflow.generated.detect.rest.types.Media.Builder builder = + com.skyflow.generated.detect.rest.types.Media.builder(); + if (media.getAudio() != null) { + builder.audio(toAudio(media.getAudio())); + } + if (media.getDocument() != null) { + builder.document(toDocument(media.getDocument())); + } + if (media.getImage() != null) { + builder.image(toImage(media.getImage())); + } + return builder.build(); + } + + static AudioV2 toAudio(Audio audio) { + AudioV2.Builder builder = AudioV2.builder(); + if (audio.getOutputProcessedAudio() != null) { + builder.outputProcessedAudio(audio.getOutputProcessedAudio()); + } + if (audio.getOutputTranscription() != null) { + builder.outputTranscription(AudioV2OutputTranscription.valueOf(audio.getOutputTranscription().name())); + } + Bleep bleep = audio.getBleep(); + if (bleep != null) { + AudioV2Bleep.Builder bleepBuilder = AudioV2Bleep.builder(); + if (bleep.getStartPadding() != null) { + bleepBuilder.startPadding(bleep.getStartPadding()); + } + if (bleep.getStopPadding() != null) { + bleepBuilder.stopPadding(bleep.getStopPadding()); + } + if (bleep.getFrequency() != null) { + bleepBuilder.frequency(bleep.getFrequency()); + } + if (bleep.getGain() != null) { + bleepBuilder.gain(bleep.getGain()); + } + builder.bleep(bleepBuilder.build()); + } + return builder.build(); + } + + static DocumentV2 toDocument(Document document) { + DocumentV2.Builder builder = DocumentV2.builder(); + Pdf pdf = document.getPdf(); + if (pdf != null) { + DocumentV2Pdf.Builder pdfBuilder = DocumentV2Pdf.builder(); + if (pdf.getProcessingMode() != null) { + pdfBuilder.processingMode(DocumentV2PdfProcessingMode.valueOf(pdf.getProcessingMode().name())); + } + if (pdf.getDensity() != null) { + pdfBuilder.density(pdf.getDensity()); + } + if (pdf.getMaxResolution() != null) { + pdfBuilder.maxResolution(pdf.getMaxResolution()); + } + builder.pdf(pdfBuilder.build()); + } + return builder.build(); + } + + static ImageV2 toImage(Image image) { + ImageV2.Builder builder = ImageV2.builder(); + if (image.getOutputProcessedImage() != null) { + builder.outputProcessedImage(image.getOutputProcessedImage()); + } + if (image.getOutputOcrText() != null) { + builder.outputOcrText(image.getOutputOcrText()); + } + if (image.getMaskingMethod() != null) { + builder.maskingMethod(ImageV2MaskingMethod.valueOf(image.getMaskingMethod().name())); + } + return builder.build(); + } + + public static List toRedactionLevels(List levels) { + List result = new ArrayList<>(); + for (RedactionLevel level : levels) { + if (level == null) { + continue; + } + com.skyflow.generated.detect.rest.types.RedactionLevel.Builder builder = + com.skyflow.generated.detect.rest.types.RedactionLevel.builder(); + if (level.getEntityName() != null) { + builder.entityName(RedactionLevelEntityName.valueOf(level.getEntityName().name())); + } + if (level.getRedactionType() != null) { + builder.redactionType(RedactionLevelRedactionType.valueOf(level.getRedactionType().name())); + } + if (level.getTokenGroupName() != null) { + builder.tokenGroupName(level.getTokenGroupName()); + } + if (level.getRedactionPattern() != null) { + builder.redactionPattern(level.getRedactionPattern()); + } + result.add(builder.build()); + } + return result; + } +} diff --git a/common/src/main/java/com/skyflow/detect/internal/DetectResponseMapper.java b/common/src/main/java/com/skyflow/detect/internal/DetectResponseMapper.java new file mode 100644 index 00000000..f4d2484b --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/internal/DetectResponseMapper.java @@ -0,0 +1,145 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.CheckGuardrailsResponse; +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.DeidentifyFileResponse; +import com.skyflow.detect.DeidentifyStringResponse; +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.FileDataFormat; +import com.skyflow.detect.FileOutput; +import com.skyflow.detect.GetRunResponse; +import com.skyflow.detect.GuardrailsValidation; +import com.skyflow.detect.DetectedEntity; +import com.skyflow.detect.EntityLocation; +import com.skyflow.detect.Metrics; +import com.skyflow.detect.ReidentifyStringResponse; +import com.skyflow.generated.detect.rest.types.DeidentifyStringResponseV2; +import com.skyflow.generated.detect.rest.types.DeidentifyStringResponseV2DetectedEntity; +import com.skyflow.generated.detect.rest.types.DeidentifyStringResponseV2EntityLocation; +import com.skyflow.generated.detect.rest.types.DetectGuardrailsResponseV2; +import com.skyflow.generated.detect.rest.types.DetectRunsResponseV2; +import com.skyflow.generated.detect.rest.types.ReidentifyStringResponseV2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Converts generated V2 API responses into public SDK response models. Internal to the SDK. + */ +public final class DetectResponseMapper { + private DetectResponseMapper() { + } + + public static DeidentifyStringResponse toDeidentifyStringResponse(DeidentifyStringResponseV2 response) { + List entities = new ArrayList<>(); + List apiEntities = response.getEntities().orElse(null); + if (apiEntities != null) { + for (DeidentifyStringResponseV2DetectedEntity apiEntity : apiEntities) { + if (apiEntity != null) { + entities.add(toDetectedEntity(apiEntity)); + } + } + } + return new DeidentifyStringResponse( + response.getProcessedText().orElse(null), + Collections.unmodifiableList(entities), + response.getMetrics().map(DetectResponseMapper::toMetrics).orElse(null)); + } + + public static ReidentifyStringResponse toReidentifyStringResponse(ReidentifyStringResponseV2 response) { + return new ReidentifyStringResponse( + response.getProcessedText().orElse(null), + response.getMetrics().map(DetectResponseMapper::toMetrics).orElse(null)); + } + + static DetectedEntity toDetectedEntity(DeidentifyStringResponseV2DetectedEntity apiEntity) { + Map scores = apiEntity.getEntityScores().orElse(null); + return new DetectedEntity( + apiEntity.getToken().orElse(null), + apiEntity.getValue().orElse(null), + apiEntity.getLocation().map(DetectResponseMapper::toEntityLocation).orElse(null), + apiEntity.getEntityType().orElse(null), + scores == null ? Collections.emptyMap() : Collections.unmodifiableMap(scores)); + } + + static EntityLocation toEntityLocation(DeidentifyStringResponseV2EntityLocation location) { + return new EntityLocation( + location.getStartIndex().orElse(null), + location.getEndIndex().orElse(null), + location.getStartIndexProcessed().orElse(null), + location.getEndIndexProcessed().orElse(null)); + } + + public static CheckGuardrailsResponse toCheckGuardrailsResponse(DetectGuardrailsResponseV2 response) { + return new CheckGuardrailsResponse( + response.getText(), + enumOrDefault(GuardrailsValidation.class, + response.getValidation() == null ? null : response.getValidation().toString(), GuardrailsValidation.UNKNOWN), + response.getToxic().orElse(null), + response.getDeniedTopic().orElse(null)); + } + + public static GetRunResponse toGetRunResponse(String runId, DetectRunsResponseV2 response) { + List outputs = new ArrayList<>(); + List apiOutputs = response.getOutput().orElse(null); + if (apiOutputs != null) { + for (com.skyflow.generated.detect.rest.types.FileOutput apiOutput : apiOutputs) { + if (apiOutput != null) { + outputs.add(toFileOutput(apiOutput)); + } + } + } + return new GetRunResponse( + runId, + response.getStatus().map(s -> enumOrDefault(DetectRunStatus.class, s.toString(), DetectRunStatus.UNKNOWN)) + .orElse(DetectRunStatus.UNKNOWN), + response.getOutputType().map(t -> enumOrDefault(DataSourceType.class, t.toString(), null)).orElse(null), + Collections.unmodifiableList(outputs), + response.getMetrics().map(DetectResponseMapper::toMetrics).orElse(null), + response.getMessage().orElse(null)); + } + + /** Response for a submit-only call: run id and nothing else. */ + public static DeidentifyFileResponse submittedDeidentifyFileResponse(String runId) { + return new DeidentifyFileResponse(runId, null, null, Collections.emptyList(), null, null); + } + + /** Merges the submit's run id with the polled run. */ + public static DeidentifyFileResponse toDeidentifyFileResponse(String runId, GetRunResponse run) { + return new DeidentifyFileResponse(runId, run.getStatus(), run.getOutputType(), run.getOutput(), + run.getMetrics(), run.getMessage()); + } + + public static FileOutput toFileOutput(com.skyflow.generated.detect.rest.types.FileOutput apiOutput) { + return new FileOutput( + apiOutput.getProcessedFile().orElse(null), + apiOutput.getProcessedFileType().map(Object::toString).orElse(null), + apiOutput.getProcessedFileExtension() + .map(e -> enumOrDefault(FileDataFormat.class, e.toString().toUpperCase(), null)) + .orElse(null)); + } + + /** Maps a wire value onto a public enum, tolerating values this SDK version does not know. */ + static > E enumOrDefault(Class type, String value, E fallback) { + if (value == null) { + return fallback; + } + try { + return Enum.valueOf(type, value); + } catch (IllegalArgumentException unknown) { + return fallback; + } + } + + public static Metrics toMetrics(com.skyflow.generated.detect.rest.types.Metrics metrics) { + return new Metrics( + metrics.getSize().orElse(null), + metrics.getWordCount().orElse(null), + metrics.getCharacterCount().orElse(null), + metrics.getSlides().orElse(null), + metrics.getPages().orElse(null), + metrics.getDuration().orElse(null)); + } +} diff --git a/common/src/main/java/com/skyflow/detect/internal/DetectValidations.java b/common/src/main/java/com/skyflow/detect/internal/DetectValidations.java new file mode 100644 index 00000000..6836dcff --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/internal/DetectValidations.java @@ -0,0 +1,114 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.CheckGuardrailsRequest; +import com.skyflow.detect.DeidentifyFileRequest; +import com.skyflow.detect.DeidentifyStringRequest; +import com.skyflow.detect.PollOptions; +import com.skyflow.detect.GetRunRequest; +import com.skyflow.detect.ReidentifyStringRequest; +import com.skyflow.errors.BaseErrorMessage; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.utils.BaseUtils; +import com.skyflow.utils.logger.LogUtil; + + +/** + * Client-side validation for Detect V2 requests. Internal to the SDK. + */ +public final class DetectValidations { + static final String INTERFACE_NAME = "detect"; + + private DetectValidations() { + } + + public static void validateDeidentifyStringRequest(DeidentifyStringRequest request) throws SkyflowException { + if (request == null) { + throw invalid(ErrorLogs.DEIDENTIFY_STRING_REQUEST_NULL, BaseErrorMessage.DeidentifyStringRequestNull); + } + if (isBlank(request.getText())) { + throw invalid(ErrorLogs.INVALID_TEXT_IN_DEIDENTIFY_STRING, BaseErrorMessage.InvalidTextInDeidentifyString); + } + // configurationId and configuration are both optional on the API and are passed through as given. + } + + public static void validateDeidentifyFileRequest(DeidentifyFileRequest request) throws SkyflowException { + if (request == null) { + throw invalid(ErrorLogs.DEIDENTIFY_FILE_REQUEST_NULL, BaseErrorMessage.DeidentifyFileRequestNull); + } + if (request.getFile() != null) { + if (!isBlank(request.getValue())) { + throw invalid(ErrorLogs.MULTIPLE_FILE_SOURCES_IN_DEIDENTIFY_FILE, BaseErrorMessage.MultipleFileSourcesInDeidentifyFile); + } + if (!request.getFile().isFile()) { + throw invalid(ErrorLogs.FILE_NOT_FOUND_IN_DEIDENTIFY_FILE, BaseErrorMessage.FileNotFoundToDeidentify); + } + } else { + if (request.getDataSource() == null) { + throw invalid(ErrorLogs.INVALID_DATA_SOURCE_IN_DEIDENTIFY_FILE, BaseErrorMessage.InvalidDataSourceInDeidentifyFile); + } + if (isBlank(request.getValue())) { + throw invalid(ErrorLogs.INVALID_VALUE_IN_DEIDENTIFY_FILE, BaseErrorMessage.InvalidValueInDeidentifyFile); + } + } + // configurationId and configuration are both optional on the API and are passed through as given. + validatePollOptions(request.getPollOptions()); + if (request.getPollOptions() == null && !isBlank(request.getOutputDirectory())) { + throw invalid(ErrorLogs.OUTPUT_DIRECTORY_WITHOUT_POLL_OPTIONS, BaseErrorMessage.OutputDirectoryWithoutPollOptions); + } + } + + static void validatePollOptions(PollOptions options) throws SkyflowException { + if (options == null) { + return; + } + Integer waitTime = options.getWaitTime(); + if (waitTime != null && (waitTime < 1 || waitTime > PollOptions.MAX_WAIT_TIME_SECONDS)) { + throw invalid(ErrorLogs.INVALID_WAIT_TIME_IN_POLL_OPTIONS, BaseErrorMessage.InvalidWaitTimeInPollOptions); + } + Integer maxAttempts = options.getMaxAttempts(); + if (maxAttempts != null && (maxAttempts < 1 || maxAttempts > PollOptions.MAX_MAX_ATTEMPTS)) { + throw invalid(ErrorLogs.INVALID_MAX_ATTEMPTS_IN_POLL_OPTIONS, BaseErrorMessage.InvalidMaxAttemptsInPollOptions); + } + } + + public static void validateReidentifyStringRequest(ReidentifyStringRequest request) throws SkyflowException { + if (request == null) { + throw invalid(ErrorLogs.REIDENTIFY_STRING_REQUEST_NULL, BaseErrorMessage.ReidentifyStringRequestNull); + } + if (isBlank(request.getText())) { + throw invalid(ErrorLogs.INVALID_TEXT_IN_REIDENTIFY_STRING, BaseErrorMessage.InvalidTextInReidentifyString); + } + // redactionLevel entries are optional on the API and are passed through as given. + } + + public static void validateGetRunRequest(GetRunRequest request) throws SkyflowException { + if (request == null) { + throw invalid(ErrorLogs.GET_RUN_REQUEST_NULL, BaseErrorMessage.GetRunRequestNull); + } + if (isBlank(request.getRunId())) { + throw invalid(ErrorLogs.INVALID_RUN_ID_IN_GET_RUN, BaseErrorMessage.InvalidRunIdInGetRun); + } + } + + public static void validateCheckGuardrailsRequest(CheckGuardrailsRequest request) throws SkyflowException { + if (request == null) { + throw invalid(ErrorLogs.CHECK_GUARDRAILS_REQUEST_NULL, BaseErrorMessage.CheckGuardrailsRequestNull); + } + if (isBlank(request.getText())) { + throw invalid(ErrorLogs.INVALID_TEXT_IN_CHECK_GUARDRAILS, BaseErrorMessage.InvalidTextInCheckGuardrails); + } + // checkToxicity and denyTopics are optional on the API and are passed through as given. + } + + + static boolean isBlank(String value) { + return value == null || value.trim().isEmpty(); + } + + private static SkyflowException invalid(ErrorLogs log, BaseErrorMessage message) { + LogUtil.printErrorLog(BaseUtils.parameterizedString(log.getLog(), INTERFACE_NAME)); + return new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), message.getMessage()); + } +} diff --git a/common/src/main/java/com/skyflow/detect/internal/FileOutputWriter.java b/common/src/main/java/com/skyflow/detect/internal/FileOutputWriter.java new file mode 100644 index 00000000..f82f0957 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/internal/FileOutputWriter.java @@ -0,0 +1,118 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.FileOutput; +import com.skyflow.errors.BaseErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.BaseConstants; +import com.skyflow.utils.BaseUtils; +import com.skyflow.utils.logger.LogUtil; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Writes the base64 outputs of a successful file run next to, or into a chosen directory for, the + * local file that was submitted. Internal to the SDK. + * + *

Each output becomes {@code processed-.}. Entity outputs carry their + * type in the name so they never collide with a processed file of the same extension: ENTITIES + * becomes {@code processed-entities-.json} and OBJECT_ENTITIES becomes + * {@code processed-object-entities-.json}. If two outputs would still share a name + * the file type is added before the extension. Presigned-URL outputs are not fetched and nothing + * is written for them. + */ +public final class FileOutputWriter { + private static final String ENTITIES_TYPE = "ENTITIES"; + + private FileOutputWriter() { + } + + /** + * @return the files written, in output order; empty when the output type is not BASE64 + * @throws SkyflowException if the directory cannot be created or a file cannot be decoded or written + */ + public static List write(List outputs, DataSourceType outputType, String inputFileName, + String outputDirectory) throws SkyflowException { + List written = new ArrayList<>(); + if (outputs == null || outputs.isEmpty()) { + return written; + } + if (outputType != DataSourceType.BASE64) { + LogUtil.printInfoLog(BaseUtils.parameterizedString(InfoLogs.OUTPUT_NOT_WRITTEN_FOR_OUTPUT_TYPE.getLog(), + String.valueOf(outputType))); + return written; + } + File directory = outputDirectory == null || outputDirectory.trim().isEmpty() ? null : new File(outputDirectory); + if (directory != null && !directory.isDirectory() && !directory.mkdirs()) { + throw writeFailure(ErrorLogs.OUTPUT_DIRECTORY_NOT_CREATED, directory.getPath(), null); + } + String base = stripExtension(inputFileName); + Set used = new HashSet<>(); + for (FileOutput output : outputs) { + if (output == null || output.getProcessedFile() == null) { + continue; + } + String name = fileName(base, output, used); + File target = directory == null ? new File(name) : new File(directory, name); + try { + byte[] bytes = Base64.getDecoder().decode(output.getProcessedFile()); + Files.write(target.toPath(), bytes); + } catch (IllegalArgumentException | IOException e) { + throw writeFailure(ErrorLogs.OUTPUT_FILE_NOT_WRITTEN, target.getPath(), e); + } + LogUtil.printInfoLog(BaseUtils.parameterizedString(InfoLogs.OUTPUT_FILE_WRITTEN.getLog(), target.getPath())); + written.add(target); + } + return written; + } + + /** + * @param base the input file name without its extension + * @return {@code processed-.}, or {@code processed--.json} for entity outputs + */ + static String fileName(String base, FileOutput output, Set used) { + String type = output.getProcessedFileType() == null ? "" : output.getProcessedFileType(); + boolean entities = type.toUpperCase().contains(ENTITIES_TYPE); + String extension; + if (output.getProcessedFileExtension() != null) { + extension = output.getProcessedFileExtension().name().toLowerCase(); + } else if (entities) { + extension = "json"; + } else { + extension = "bin"; + } + // ENTITIES -> processed-entities-, OBJECT_ENTITIES -> processed-object-entities- + String stem = entities + ? BaseConstants.PROCESSED_FILE_NAME_PREFIX + type.toLowerCase().replace('_', '-') + "-" + base + : BaseConstants.PROCESSED_FILE_NAME_PREFIX + base; + String name = stem + "." + extension; + if (!used.add(name)) { + name = stem + "." + (type.isEmpty() ? String.valueOf(used.size()) : type.toLowerCase()) + "." + extension; + used.add(name); + } + return name; + } + + static String stripExtension(String fileName) { + if (fileName == null || fileName.isEmpty()) { + return "output"; + } + int dot = fileName.lastIndexOf('.'); + return dot > 0 ? fileName.substring(0, dot) : fileName; + } + + private static SkyflowException writeFailure(ErrorLogs log, String path, Exception cause) { + LogUtil.printErrorLog(BaseUtils.parameterizedString(log.getLog(), path)); + String message = BaseUtils.parameterizedString(BaseErrorMessage.FailedToWriteOutputFile.getMessage(), path); + return cause == null ? new SkyflowException(message) : new SkyflowException(message, cause); + } +} diff --git a/common/src/main/java/com/skyflow/detect/polling/RunPoller.java b/common/src/main/java/com/skyflow/detect/polling/RunPoller.java new file mode 100644 index 00000000..4aff5a31 --- /dev/null +++ b/common/src/main/java/com/skyflow/detect/polling/RunPoller.java @@ -0,0 +1,99 @@ +package com.skyflow.detect.polling; + +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.FileOutput; +import com.skyflow.detect.GetRunResponse; +import com.skyflow.detect.PollOptions; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.BaseUtils; +import com.skyflow.utils.logger.LogUtil; + +import java.util.Collections; + +/** + * Polls a Detect file run until it reaches a terminal status or the {@link PollOptions} budget is used + * up. Shared by every SDK: the caller supplies how a run is fetched, so each controller plugs in its + * own {@code getRun}. + * + *

Backoff is exponential, 1, 2, 4, 8, 16 then 32 seconds between lookups, with the last sleep + * trimmed so the total never exceeds {@code waitTime}. The first lookup happens immediately after the + * submit. SUCCESS, FAILED and UNKNOWN are terminal; every other status keeps polling. + */ +public final class RunPoller { + /** Longest single pause between two lookups, in seconds. */ + public static final long MAX_DELAY_SECONDS = 32L; + + /** Fetches the current state of a run. */ + public interface RunFetcher { + GetRunResponse fetch(String runId) throws SkyflowException; + } + + /** Pauses the calling thread. Replaceable in tests. */ + public interface Sleeper { + void sleep(long millis) throws InterruptedException; + } + + private RunPoller() { + } + + /** + * Polls on the calling thread. + * + * @param runId run to poll + * @param options wait time and attempt limit, defaults applied + * @param fetcher how to look the run up + * @return the terminal run, or the last non-terminal run seen when the budget ran out or the thread was interrupted + * @throws SkyflowException if a lookup fails + */ + public static GetRunResponse poll(String runId, PollOptions options, RunFetcher fetcher) throws SkyflowException { + return poll(runId, options, fetcher, Thread::sleep); + } + + public static GetRunResponse poll(String runId, PollOptions options, RunFetcher fetcher, Sleeper sleeper) + throws SkyflowException { + int waitTime = options.effectiveWaitTime(); + int maxAttempts = options.effectiveMaxAttempts(); + LogUtil.printInfoLog(BaseUtils.parameterizedString(InfoLogs.POLL_RUN_STARTED.getLog(), runId, + String.valueOf(waitTime), String.valueOf(maxAttempts))); + + long elapsedSeconds = 0; + long delaySeconds = 1; + int attempts = 0; + while (true) { + GetRunResponse run = fetcher.fetch(runId); + attempts++; + if (run == null || run.getStatus() == null) { + // A fetcher that reports nothing is treated as an unknown, terminal state. + run = new GetRunResponse(runId, DetectRunStatus.UNKNOWN, null, Collections.emptyList(), null, null); + } + switch (run.getStatus()) { + case SUCCESS: + case FAILED: + case UNKNOWN: + LogUtil.printInfoLog(BaseUtils.parameterizedString(InfoLogs.POLL_RUN_TERMINAL.getLog(), runId, + run.getStatus().name(), String.valueOf(attempts))); + return run; + default: + // QUEUED, IN_PROGRESS: keep polling while the budget allows. + break; + } + if (attempts >= maxAttempts || elapsedSeconds >= waitTime) { + LogUtil.printInfoLog(BaseUtils.parameterizedString(InfoLogs.POLL_RUN_BUDGET_EXHAUSTED.getLog(), runId, + run.getStatus().name(), String.valueOf(attempts), String.valueOf(elapsedSeconds))); + return run; + } + long sleepSeconds = Math.min(delaySeconds, waitTime - elapsedSeconds); + try { + sleeper.sleep(sleepSeconds * 1000L); + } catch (InterruptedException interrupted) { + Thread.currentThread().interrupt(); + LogUtil.printInfoLog(BaseUtils.parameterizedString(InfoLogs.POLL_RUN_INTERRUPTED.getLog(), runId, + run.getStatus().name())); + return run; + } + elapsedSeconds += sleepSeconds; + delaySeconds = Math.min(delaySeconds * 2, MAX_DELAY_SECONDS); + } + } +} diff --git a/common/src/main/java/com/skyflow/errors/ErrorMessage.java b/common/src/main/java/com/skyflow/errors/BaseErrorMessage.java similarity index 72% rename from common/src/main/java/com/skyflow/errors/ErrorMessage.java rename to common/src/main/java/com/skyflow/errors/BaseErrorMessage.java index 1e408cca..d3c9c388 100644 --- a/common/src/main/java/com/skyflow/errors/ErrorMessage.java +++ b/common/src/main/java/com/skyflow/errors/BaseErrorMessage.java @@ -2,7 +2,12 @@ import com.skyflow.utils.SdkVersion; -public enum ErrorMessage { +/** + * Validation and client-side error messages shared by every SDK. Named {@code BaseErrorMessage} + * rather than {@code ErrorMessage} so it can never be shadowed by a module's own + * {@code com.skyflow.errors.ErrorMessage}: code in common must reference this enum. + */ +public enum BaseErrorMessage { // Client initialization VaultIdAlreadyInConfigList("%s0 Validation error. VaultId is present in an existing config. Specify a new vaultId in config."), VaultIdNotInConfigList("%s0 Validation error. VaultId is missing from the config. Specify the vaultIds from configs."), @@ -12,18 +17,11 @@ public enum ErrorMessage { ConnectionIdNotInConfigList("%s0 Validation error. ConnectionId is missing from the config. Specify the connectionIds from configs."), EmptyCredentials("%s0 Validation error. Invalid credentials. Credentials must not be empty."), NullCredentials("%s0 Validation error. Credentials object is null. Specify a valid Credentials object."), - TableSpecifiedInRequestAndRecordObject("%s0 Validation error. Table name cannot be specified at both the request and record levels. Please specify the table name in only one place."), - UpsertTableRequestAtRecordLevel("%s0 Validation error. Table name should be present at each record level when upsert is present at record level."), - UpsertTableRequestAtRequestLevel("%s0 Validation error. Upsert should be present at each record level when table name is present at record level."), - TableNotSpecifiedInRequestAndRecordObject("%s0 Validation error. Table name is missing. Table name should be specified at one place either at the request level or record level. Please specify the table name at one place."), // Vault config InvalidVaultId("%s0 Initialization failed. Invalid vault ID. Specify a valid vault ID."), EmptyVaultId("%s0 Initialization failed. Invalid vault ID. Vault ID must not be empty."), InvalidClusterId("%s0 Initialization failed. Invalid cluster ID. Specify cluster ID."), EmptyClusterId("%s0 Initialization failed. Invalid cluster ID. Specify a valid cluster ID."), - EmptyVaultUrl("%s0 Initialization failed. Vault URL is empty. Specify a valid vault URL."), - InvalidVaultUrlFormat("%s0 Initialization failed. Vault URL must start with 'https://'."), - EitherVaultUrlOrClusterIdRequired("%s0 Initialization failed. Specify either 'clusterId' or 'vaultURL'."), // Connection config InvalidConnectionId("%s0 Initialization failed. Invalid connection ID. Specify a valid connection ID."), @@ -69,20 +67,11 @@ public enum ErrorMessage { TableKeyError("%s0 Validation error. 'table' key is missing from the payload. Specify a 'table' key."), EmptyTable("%s0 Validation error. 'table' can't be empty. Specify a table."), ValuesKeyError("%s0 Validation error. 'values' key is missing from the payload. Specify a 'values' key."), - EmptyRecords("%s0 Validation error. 'records' can't be empty. Specify records."), - EmptyKeyInRecords("%s0 Validation error. Invalid key in data in records. Specify a valid key."), - EmptyValueInRecords("%s0 Validation error. Invalid value in records. Specify a valid value."), - RecordsKeyError("%s0 Validation error. 'records' key is missing from the payload. Specify a 'records' key."), - EmptyValues("%s0 Validation error. 'values' can't be empty. Specify values."), EmptyKeyInValues("%s0 Validation error. Invalid key in values. Specify a valid key."), - EmptyValueInValues("%s0 Validation error. Invalid value in values. Specify a valid value."), TokensKeyError("%s0 Validation error. 'tokens' key is missing from the payload. Specify a 'tokens' key."), EmptyTokens("%s0 Validation error. The 'tokens' field is empty. Specify tokens for one or more fields."), EmptyKeyInTokens("%s0 Validation error. Invalid key tokens. Specify a valid key."), - EmptyValueInTokens("%s0 Validation error. Invalid value in tokens. Specify a valid value."), EmptyUpsert("%s0 Validation error. 'upsert' key can't be empty. Specify an upsert column."), - InvalidUpsertUpdateType("%s0 Validation error. Invalid upsert updateType. Specify either 'UPDATE' or 'REPLACE'."), - EmptyUpsertValues("%s0 Validation error. Upsert column values can't be empty. Specify at least one upsert column."), HomogenousNotSupportedWithUpsert("%s0 Validation error. 'homogenous' is not supported with 'upsert'. Specify either 'homogenous' or 'upsert'."), TokensPassedForTokenModeDisable("%s0 Validation error. 'tokenMode' wasn't specified. Set 'tokenMode' to 'ENABLE' to insert tokens."), NoTokensWithTokenMode("%s0 Validation error. Tokens weren't specified for records while 'tokenMode' was %s1. Specify tokens."), @@ -90,22 +79,15 @@ public enum ErrorMessage { InsufficientTokensPassedForTokenModeEnableStrict("%s0 Validation error. 'tokenMode' is set to 'ENABLE_STRICT', but some fields are missing tokens. Specify tokens for all fields."), BatchInsertPartialSuccess("%s0 Insert operation completed with partial success."), BatchInsertFailure("%s0 Insert operation failed."), - RecordSizeExceedError("%s0 Maximum number of records exceeded. The limit is 100000."), // Detokenize InvalidDetokenizeData("%s0 Validation error. Invalid detokenize data. Specify valid detokenize data."), EmptyDetokenizeData("%s0 Validation error. Invalid data tokens. Specify at least one data token."), EmptyTokenInDetokenizeData("%s0 Validation error. Invalid data tokens. Specify a valid data token."), - TokensSizeExceedError("%s0 Maximum number of tokens exceeded. The limit is 100000."), // Delete DeleteRequestNull("%s0 Validation error. DeleteRequest object is null. Specify a valid DeleteRequest object."), - // Delete Tokens - DeleteTokensRequestNull("%s0 Validation error. DeleteTokensRequest object is null. Specify a valid DeleteTokensRequest object."), - EmptyDeleteTokensData("%s0 Validation error. Tokens list is empty. Specify at least one token to delete."), - EmptyTokenInDeleteTokensData("%s0 Validation error. Invalid token in delete tokens request. Specify a valid token."), - DeleteTokensSizeExceedError("%s0 Maximum number of tokens exceeded. The limit is 100000."), // Get GetRequestNull("%s0 Validation error. GetRequest object is null. Specify a valid GetRequest object."), @@ -126,15 +108,6 @@ public enum ErrorMessage { ColumnValuesKeyErrorGet("%s0 Validation error. 'columnValues' aren't specified whereas 'columnName' is specified. Either add 'columnValues' or remove 'columnName'."), EmptyColumnValues("%s0 Validation error. 'columnValues' can't be empty. Specify at least one column value"), EmptyValueInColumnValues("%s0 Validation error. Invalid value in column values. Specify a valid column value."), - IdsOrUniqueValuesKeyError("%s0 Validation error. 'ids' or 'uniqueValues' key is missing from the payload. Specify ids or uniqueValues in payload."), - BothIdsAndUniqueValuesSpecified("%s0 Validation error. Both Skyflow IDs and unique values can't be specified. Either specify Skyflow IDs or unique values."), - EmptyUniqueValues("%s0 Validation error. 'uniqueValues' can't be empty. Specify at least one unique value."), - EmptyUniqueValueInUniqueValues("%s0 Validation error. Invalid unique value in 'uniqueValues'. Specify a valid unique value."), - NullColumnRedactions("%s0 Validation error. Column redaction object can not be null. Specify a valid column redaction object."), - NullColumnNameInColumnRedaction("%s0 Validation error. Column name can not be null or empty in column redaction. Specify a valid column name."), - NullRedactionInColumnRedaction("%s0 Validation error. Redaction can not be null or empty in column redaction. Specify a valid redaction."), - BothSingleTableFieldsAndRecordsSpecified("%s0 Validation error. Both single-table lookup fields ('table', 'ids', 'fields', 'uniqueValues', 'columnRedactions') and 'records' can't be specified. Either specify single-table fields or 'records'."), - NullGetRecordRequest("%s0 Validation error. Record in 'records' is null. Specify a valid record."), TokenKeyError("%s0 Validation error. 'token' key is missing from the payload. Specify a 'token' key."), PartialSuccess("%s0 Validation error. Check 'SkyflowError.data' for details."), @@ -146,9 +119,6 @@ public enum ErrorMessage { InvalidSkyflowIdType("%s0 Validation error. Invalid type for 'skyflow_id' in data payload. Specify 'skyflow_id' as a string."), EmptySkyflowId("%s0 Validation error. 'skyflow_id' can't be empty. Specify a skyflow id."), UpdateRequestNull("%s0 Validation error. UpdateRequest object is null. Specify a valid UpdateRequest object."), - UpdateRecordNull("%s0 Validation error. UpdateRequestRecord object in the list is null. Specify a valid UpdateRequestRecord object."), - RecordSkyflowIdKeyError("%s0 Validation error. 'skyflowId' key is missing from the record. Specify a 'skyflowId' key."), - EmptySkyflowIdInRecord("%s0 Validation error. 'skyflowId' can't be empty in the record. Specify a valid skyflow ID."), // Query QueryRequestNull("%s0 Validation error. QueryRequest object is null. Specify a valid QueryRequest object."), @@ -159,15 +129,6 @@ public enum ErrorMessage { ColumnValuesKeyErrorTokenize("%s0 Validation error. 'columnValues' key is missing from the payload. Specify a 'columnValues' key."), EmptyColumnGroupInColumnValue("%s0 Validation error. Invalid column group in column value. Specify a valid column group."), TokenizeRequestNull("%s0 Validation error. TokenizeRequest object is null. Specify a valid TokenizeRequest object."), - EmptyTokenizeData("%s0 Validation error. Tokenize data is empty. Specify at least one tokenize record."), - TokenizeRecordNull("%s0 Validation error. TokenizeRecord in the list is null. Specify a valid TokenizeRecord object."), - InvalidBulkTokenizeRecordType("%s0 Validation error. Record in the list must be of type BulkTokenizeRequestRecord. Specify a valid BulkTokenizeRequestRecord object."), - EmptyValueInTokenizeRecord("%s0 Validation error. Value in TokenizeRecord is null or empty. Specify a valid value."), - EmptyTokenGroupNamesInTokenizeRecord("%s0 Validation error. TokenGroupNames in TokenizeRecord is null or empty. Specify at least one token group name."), - EmptyTokenGroupNameInTokenizeRecord("%s0 Validation error. Token group name in TokenizeRecord is null or empty. Specify a valid token group name."), - TokenizeDataSizeExceedError("%s0 Maximum number of tokenize records exceeded. The limit is 100000."), - MissingIndexInBulkTokenizeRecord("%s0 Validation error. Index in BulkTokenizeRequestRecord is null. Specify an index for every record."), - DuplicateIndexInBulkTokenizeRecord("%s0 Validation error. Duplicate index in BulkTokenizeRequestRecord. Specify a unique index for every record."), // Connection InvalidRequestHeaders("%s0 Validation error. Request headers aren't valid. Specify valid request headers."), @@ -215,17 +176,30 @@ public enum ErrorMessage { DetokenizeRequestNull("%s0 Validation error. DetokenizeRequest object is null. Specify a valid DetokenizeRequest object."), - NullTokenGroupRedactions("%s0 Validation error. TokenGroupRedaction in the list is null. Specify a valid TokenGroupRedactions object."), - NullRedactionInTokenGroup("%s0 Validation error. Redaction in TokenGroupRedactions is null or empty. Specify a valid redaction."), - NullTokenGroupNameInTokenGroup("%s0 Validation error. TokenGroupName in TokenGroupRedactions is null or empty. Specify a valid tokenGroupName."), - InvalidRecord("%s0 Validation error. InsertRecord object in the list is invalid. Specify a valid InsertRecord object."), + // Detect + DeidentifyStringRequestNull("%s0 Validation error. DeidentifyString request cannot be null."), + InvalidTextInDeidentifyString("%s0 Validation error. Invalid text in deidentify string request. Specify a non-empty text."), + ReidentifyStringRequestNull("%s0 Validation error. ReidentifyString request cannot be null."), + InvalidTextInReidentifyString("%s0 Validation error. Invalid text in reidentify string request. Specify a non-empty text."), + DeidentifyFileRequestNull("%s0 Validation error. DeidentifyFile request cannot be null."), + InvalidDataSourceInDeidentifyFile("%s0 Validation error. Invalid dataSource in deidentify file request. Specify BASE64, SKYFLOW_ID or PRESIGNED_URL, or supply a file."), + InvalidValueInDeidentifyFile("%s0 Validation error. Invalid value in deidentify file request. Specify the non-empty base64 content, skyflow id or presigned URL of the file, or supply a file."), + MultipleFileSourcesInDeidentifyFile("%s0 Validation error. Specify either value or file in deidentify file request, not both."), + InvalidWaitTimeInPollOptions("%s0 Validation error. waitTime in pollOptions must be between 1 and 300 seconds."), + InvalidMaxAttemptsInPollOptions("%s0 Validation error. maxAttempts in pollOptions must be between 1 and 23."), + OutputDirectoryWithoutPollOptions("%s0 Validation error. outputDirectory in deidentify file request requires pollOptions."), + FailedToWriteOutputFile("%s0 Failed to write the processed output to %s1."), + GetRunRequestNull("%s0 Validation error. GetRun request cannot be null."), + InvalidRunIdInGetRun("%s0 Validation error. Invalid runId in get run request. Specify a non-empty run id."), + CheckGuardrailsRequestNull("%s0 Validation error. CheckGuardrails request cannot be null."), + InvalidTextInCheckGuardrails("%s0 Validation error. Invalid text in check guardrails request. Specify a non-empty text."), ; private final String message; - ErrorMessage(String message) { + BaseErrorMessage(String message) { this.message = message; } diff --git a/common/src/main/java/com/skyflow/errors/SkyflowException.java b/common/src/main/java/com/skyflow/errors/SkyflowException.java index 2b043f92..f8c595ba 100644 --- a/common/src/main/java/com/skyflow/errors/SkyflowException.java +++ b/common/src/main/java/com/skyflow/errors/SkyflowException.java @@ -89,7 +89,7 @@ public SkyflowException(int httpCode, Throwable cause, Map> } catch (Exception e) { this.httpStatus = HttpStatus.BAD_REQUEST.getHttpStatus(); String fullMessage = responseBody != null ? responseBody : - (cause.getLocalizedMessage() != null ? cause.getMessage() : ErrorMessage.ErrorOccurred.getMessage()); + (cause.getLocalizedMessage() != null ? cause.getMessage() : BaseErrorMessage.ErrorOccurred.getMessage()); this.message = fullMessage.split("HTTP response code:")[0].trim(); } } diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/ApiClientApiException.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/ApiClientApiException.java new file mode 100644 index 00000000..d864c404 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/ApiClientApiException.java @@ -0,0 +1,73 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Response; + +/** + * This exception type will be thrown for any non-2XX API responses. + */ +public class ApiClientApiException extends ApiClientException { + /** + * The error code of the response that triggered the exception. + */ + private final int statusCode; + + /** + * The body of the response that triggered the exception. + */ + private final Object body; + + private final Map> headers; + + public ApiClientApiException(String message, int statusCode, Object body) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + } + + public ApiClientApiException(String message, int statusCode, Object body, Response rawResponse) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + this.headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + } + + /** + * @return the statusCode + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @return the body + */ + public Object body() { + return this.body; + } + + /** + * @return the headers + */ + public Map> headers() { + return this.headers; + } + + @Override + public String toString() { + return "ApiClientApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " + + ObjectMappers.stringify(body) + "}"; + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/ApiClientException.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/ApiClientException.java new file mode 100644 index 00000000..0f99fbc6 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/ApiClientException.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +/** + * This class serves as the base exception for all errors in the SDK. + */ +public class ApiClientException extends RuntimeException { + public ApiClientException(String message) { + super(message); + } + + public ApiClientException(String message, Exception e) { + super(message, e); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/ApiClientHttpResponse.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/ApiClientHttpResponse.java new file mode 100644 index 00000000..4b251a98 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/ApiClientHttpResponse.java @@ -0,0 +1,37 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.Response; + +public final class ApiClientHttpResponse { + + private final T body; + + private final Map> headers; + + public ApiClientHttpResponse(T body, Response rawResponse) { + this.body = body; + + Map> headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + this.headers = headers; + } + + public T body() { + return this.body; + } + + public Map> headers() { + return headers; + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/ClientOptions.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/ClientOptions.java new file mode 100644 index 00000000..cf61d5db --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/ClientOptions.java @@ -0,0 +1,320 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import okhttp3.OkHttpClient; + +public final class ClientOptions { + private final Environment environment; + + private final Map headers; + + private final Map> headerSuppliers; + + private final OkHttpClient httpClient; + + private final boolean ownsHttpClient; + + private final int timeout; + + private final int maxRetries; + + private final Optional initialRetryDelayMillis; + + private final Optional maxRetryDelayMillis; + + private final Optional retryJitterFactor; + + private final Optional logging; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient, + boolean ownsHttpClient, + int timeout, + int maxRetries, + Optional initialRetryDelayMillis, + Optional maxRetryDelayMillis, + Optional retryJitterFactor, + Optional logging) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("X-Fern-Language", "JAVA"); + put("X-Fern-SDK-Name", "com.skyflow.fern:api-sdk"); + put("X-Fern-SDK-Version", "0.0.98"); + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + this.ownsHttpClient = ownsHttpClient; + this.timeout = timeout; + this.maxRetries = maxRetries; + this.initialRetryDelayMillis = initialRetryDelayMillis; + this.maxRetryDelayMillis = maxRetryDelayMillis; + this.retryJitterFactor = retryJitterFactor; + this.logging = logging; + } + + public Environment environment() { + return this.environment; + } + + public Map headers(RequestOptions requestOptions) { + Map values = new HashMap<>(this.headers); + headerSuppliers.forEach((key, supplier) -> { + values.put(key, supplier.get()); + }); + if (requestOptions != null) { + values.putAll(requestOptions.getHeaders()); + } + return values; + } + + public int timeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.timeout; + } + return requestOptions.getTimeout().orElse(this.timeout); + } + + public OkHttpClient httpClient() { + return this.httpClient; + } + + public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + + public int maxRetries() { + return this.maxRetries; + } + + public Optional initialRetryDelayMillis() { + return this.initialRetryDelayMillis; + } + + public Optional maxRetryDelayMillis() { + return this.maxRetryDelayMillis; + } + + public Optional retryJitterFactor() { + return this.retryJitterFactor; + } + + /** + * Releases resources owned by this client. Only shuts down the underlying OkHttpClient's + * dispatcher executor and evicts its connection pool when this client created that + * OkHttpClient itself; an OkHttpClient supplied via httpClient is left running, since the + * caller owns its lifecycle. + *

+ * In-flight calls are not cancelled or awaited, and any request issued after this method + * returns fails with a {@code RejectedExecutionException}. Options derived from this one via + * {@code Builder.from(...)} share the same dispatcher and connection pool, so closing either + * releases them for both. Calling this method more than once has no further effect. + */ + public void close() { + if (!this.ownsHttpClient) { + return; + } + this.httpClient.dispatcher().executorService().shutdown(); + this.httpClient.connectionPool().evictAll(); + } + + public Optional logging() { + return this.logging; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Environment environment; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + private int maxRetries = 2; + + private Optional initialRetryDelayMillis = Optional.empty(); + + private Optional maxRetryDelayMillis = Optional.empty(); + + private Optional retryJitterFactor = Optional.empty(); + + private Optional timeout = Optional.empty(); + + private OkHttpClient httpClient = null; + + private boolean ownsHttpClient = true; + + private Optional logging = Optional.empty(); + + public Builder environment(Environment environment) { + this.environment = environment; + return this; + } + + public Builder addHeader(String key, String value) { + if (value != null) { + this.headers.put(key, value); + } + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(Optional timeout) { + this.timeout = timeout; + return this; + } + + /** + * Override the maximum number of retries. Defaults to 2 retries. + */ + public Builder maxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + /** + * Override the initial delay (in milliseconds) used for exponential backoff between retries. Defaults to 1000 milliseconds. + */ + public Builder initialRetryDelayMillis(long initialRetryDelayMillis) { + this.initialRetryDelayMillis = Optional.of(initialRetryDelayMillis); + return this; + } + + /** + * Override the maximum delay (in milliseconds) between retries. Defaults to 60000 milliseconds. + */ + public Builder maxRetryDelayMillis(long maxRetryDelayMillis) { + this.maxRetryDelayMillis = Optional.of(maxRetryDelayMillis); + return this; + } + + /** + * Override the jitter factor (between 0 and 1) applied to retry delays. Defaults to 0.2. + */ + public Builder retryJitterFactor(double retryJitterFactor) { + this.retryJitterFactor = Optional.of(retryJitterFactor); + return this; + } + + /** + * Sets the underlying OkHttp client. The caller retains ownership of its lifecycle: + * close() will not shut down its dispatcher executor or evict its connection pool. + */ + public Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + this.ownsHttpClient = httpClient == null; + return this; + } + + /** + * Configure logging for the SDK. Silent by default — no log output unless explicitly configured. + */ + public Builder logging(LogConfig logging) { + this.logging = Optional.of(logging); + return this; + } + + public ClientOptions build() { + OkHttpClient.Builder httpClientBuilder = + this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder(); + + if (this.httpClient != null) { + timeout.ifPresent(timeout -> httpClientBuilder + .callTimeout(timeout, TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS)); + } else { + httpClientBuilder + .callTimeout(this.timeout.orElse(60), TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .addInterceptor(new RetryInterceptor( + this.maxRetries, + this.initialRetryDelayMillis, + this.maxRetryDelayMillis, + this.retryJitterFactor)); + } + + Logger logger = Logger.from(this.logging); + httpClientBuilder.addInterceptor(new LoggingInterceptor(logger)); + httpClientBuilder.addInterceptor(new ResponseDecompressionInterceptor()); + + this.httpClient = httpClientBuilder.build(); + this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + + return new ClientOptions( + environment, + headers, + headerSuppliers, + httpClient, + this.ownsHttpClient, + this.timeout.get(), + this.maxRetries, + this.initialRetryDelayMillis, + this.maxRetryDelayMillis, + this.retryJitterFactor, + this.logging); + } + + /** + * Create a new Builder initialized with values from an existing ClientOptions + */ + public static Builder from(ClientOptions clientOptions) { + Builder builder = new Builder(); + builder.environment = clientOptions.environment(); + builder.timeout = Optional.of(clientOptions.timeout(null)); + builder.httpClient = clientOptions.httpClient(); + builder.ownsHttpClient = clientOptions.ownsHttpClient; + builder.headers.putAll(clientOptions.headers); + builder.headerSuppliers.putAll(clientOptions.headerSuppliers); + builder.maxRetries = clientOptions.maxRetries(); + builder.initialRetryDelayMillis = clientOptions.initialRetryDelayMillis(); + builder.maxRetryDelayMillis = clientOptions.maxRetryDelayMillis(); + builder.retryJitterFactor = clientOptions.retryJitterFactor(); + builder.logging = clientOptions.logging(); + return builder; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/ConsoleLogger.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/ConsoleLogger.java new file mode 100644 index 00000000..e4feb843 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/ConsoleLogger.java @@ -0,0 +1,52 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import java.util.logging.Level; + +/** + * Default logger implementation that writes to the console using {@link java.util.logging.Logger}. + * + *

Uses the "fern" logger name with a simple format of "LEVEL - message". + */ +public final class ConsoleLogger implements ILogger { + + private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger("fern"); + + static { + if (logger.getHandlers().length == 0) { + java.util.logging.ConsoleHandler handler = new java.util.logging.ConsoleHandler(); + handler.setFormatter(new java.util.logging.SimpleFormatter() { + @Override + public String format(java.util.logging.LogRecord record) { + return record.getLevel() + " - " + record.getMessage() + System.lineSeparator(); + } + }); + handler.setLevel(Level.ALL); + logger.addHandler(handler); + logger.setUseParentHandlers(false); + logger.setLevel(Level.ALL); + } + } + + @Override + public void debug(String message) { + logger.log(Level.FINE, message); + } + + @Override + public void info(String message) { + logger.log(Level.INFO, message); + } + + @Override + public void warn(String message) { + logger.log(Level.WARNING, message); + } + + @Override + public void error(String message) { + logger.log(Level.SEVERE, message); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/DateTimeDeserializer.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/DateTimeDeserializer.java new file mode 100644 index 00000000..f2c9053a --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/DateTimeDeserializer.java @@ -0,0 +1,70 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting date-time strings into {@link OffsetDateTime} objects. + * Supports ISO 8601 format, space-separated variants, and RFC 1123 (RFC 2822) format. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + String value = parser.getValueAsString(); + TemporalAccessor temporal; + try { + temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest(value, OffsetDateTime::from, LocalDateTime::from); + } catch (DateTimeParseException e) { + try { + // Fall back to space-separated format (e.g. "2025-02-15 10:30:00+00:00"). + temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + value.replace(' ', 'T'), OffsetDateTime::from, LocalDateTime::from); + } catch (DateTimeParseException e2) { + // Fall back to RFC 1123 format (e.g. "Thu, 07 May 2026 14:23:38 +0000"). + temporal = DateTimeFormatter.RFC_1123_DATE_TIME.parseBest( + value, OffsetDateTime::from, LocalDateTime::from); + } + } + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/DoubleSerializer.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/DoubleSerializer.java new file mode 100644 index 00000000..635869a6 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/DoubleSerializer.java @@ -0,0 +1,43 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; + +/** + * Custom serializer that writes integer-valued doubles without a decimal point. + * For example, {@code 24000.0} is serialized as {@code 24000} instead of {@code 24000.0}. + * Non-integer values like {@code 3.14} are serialized normally. + */ +class DoubleSerializer extends JsonSerializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule() + .addSerializer(Double.class, new DoubleSerializer()) + .addSerializer(double.class, new DoubleSerializer()); + } + + /** + * Gets a module wrapping this serializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if (value != null && value == Math.floor(value) && !Double.isInfinite(value) && !Double.isNaN(value)) { + gen.writeNumber(value.longValue()); + } else { + gen.writeNumber(value); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/Environment.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/Environment.java new file mode 100644 index 00000000..c95311b0 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/Environment.java @@ -0,0 +1,25 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +public final class Environment { + public static final Environment PRODUCTION = new Environment("https://%7B%7BvaultUri%7D%7D.vault.skyflowapis.com"); + + public static final Environment SANDBOX = + new Environment("https://%7B%7BvaultUri%7D%7D.vault.skyflowapis-preview.com"); + + private final String url; + + private Environment(String url) { + this.url = url; + } + + public String getUrl() { + return this.url; + } + + public static Environment custom(String url) { + return new Environment(url); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/ILogger.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/ILogger.java new file mode 100644 index 00000000..0df7a4f5 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/ILogger.java @@ -0,0 +1,38 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +/** + * Interface for custom logger implementations. + * + *

Implement this interface to provide a custom logging backend for the SDK. + * The SDK will call the appropriate method based on the log level. + * + *

Example: + *

{@code
+ * public class MyCustomLogger implements ILogger {
+ *     public void debug(String message) {
+ *         System.out.println("[DBG] " + message);
+ *     }
+ *     public void info(String message) {
+ *         System.out.println("[INF] " + message);
+ *     }
+ *     public void warn(String message) {
+ *         System.out.println("[WRN] " + message);
+ *     }
+ *     public void error(String message) {
+ *         System.out.println("[ERR] " + message);
+ *     }
+ * }
+ * }
+ */ +public interface ILogger { + void debug(String message); + + void info(String message); + + void warn(String message); + + void error(String message); +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/LogConfig.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/LogConfig.java new file mode 100644 index 00000000..e613616b --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/LogConfig.java @@ -0,0 +1,98 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +/** + * Configuration for SDK logging. + * + *

Use the builder to configure logging behavior: + *

{@code
+ * LogConfig config = LogConfig.builder()
+ *     .level(LogLevel.DEBUG)
+ *     .silent(false)
+ *     .build();
+ * }
+ * + *

Or with a custom logger: + *

{@code
+ * LogConfig config = LogConfig.builder()
+ *     .level(LogLevel.DEBUG)
+ *     .logger(new MyCustomLogger())
+ *     .silent(false)
+ *     .build();
+ * }
+ * + *

Defaults: + *

    + *
  • {@code level} — {@link LogLevel#INFO}
  • + *
  • {@code logger} — {@link ConsoleLogger} (writes to stderr via java.util.logging)
  • + *
  • {@code silent} — {@code true} (no output unless explicitly enabled)
  • + *
+ */ +public final class LogConfig { + + private final LogLevel level; + private final ILogger logger; + private final boolean silent; + + private LogConfig(LogLevel level, ILogger logger, boolean silent) { + this.level = level; + this.logger = logger; + this.silent = silent; + } + + public LogLevel level() { + return level; + } + + public ILogger logger() { + return logger; + } + + public boolean silent() { + return silent; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private LogLevel level = LogLevel.INFO; + private ILogger logger = new ConsoleLogger(); + private boolean silent = true; + + private Builder() {} + + /** + * Set the minimum log level. Only messages at this level or above will be logged. + * Defaults to {@link LogLevel#INFO}. + */ + public Builder level(LogLevel level) { + this.level = level; + return this; + } + + /** + * Set a custom logger implementation. Defaults to {@link ConsoleLogger}. + */ + public Builder logger(ILogger logger) { + this.logger = logger; + return this; + } + + /** + * Set whether logging is silent (disabled). Defaults to {@code true}. + * Set to {@code false} to enable log output. + */ + public Builder silent(boolean silent) { + this.silent = silent; + return this; + } + + public LogConfig build() { + return new LogConfig(level, logger, silent); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/LogLevel.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/LogLevel.java new file mode 100644 index 00000000..b37806c0 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/LogLevel.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +/** + * Log levels for SDK logging configuration. + * Silent by default — no log output unless explicitly configured. + */ +public enum LogLevel { + DEBUG(1), + INFO(2), + WARN(3), + ERROR(4); + + private final int value; + + LogLevel(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + /** + * Parse a log level from a string (case-insensitive). + * + * @param level the level string (debug, info, warn, error) + * @return the corresponding LogLevel + * @throws IllegalArgumentException if the string does not match any level + */ + public static LogLevel fromString(String level) { + return LogLevel.valueOf(level.toUpperCase()); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/Logger.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/Logger.java new file mode 100644 index 00000000..19a9d1fe --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/Logger.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +/** + * SDK logger that filters messages based on level and silent mode. + * + *

Silent by default — no log output unless explicitly configured. + * Create via {@link LogConfig} or directly: + *

{@code
+ * Logger logger = new Logger(LogLevel.DEBUG, new ConsoleLogger(), false);
+ * logger.debug("request sent");
+ * }
+ */ +public final class Logger { + + private static final Logger DEFAULT = new Logger(LogLevel.INFO, new ConsoleLogger(), true); + + private final LogLevel level; + private final ILogger logger; + private final boolean silent; + + public Logger(LogLevel level, ILogger logger, boolean silent) { + this.level = level; + this.logger = logger; + this.silent = silent; + } + + /** + * Returns a default silent logger (no output). + */ + public static Logger getDefault() { + return DEFAULT; + } + + /** + * Creates a Logger from a {@link LogConfig}. If config is {@code null}, returns the default silent logger. + */ + public static Logger from(LogConfig config) { + if (config == null) { + return DEFAULT; + } + return new Logger(config.level(), config.logger(), config.silent()); + } + + /** + * Creates a Logger from an {@code Optional}. If empty, returns the default silent logger. + */ + public static Logger from(java.util.Optional config) { + return config.map(Logger::from).orElse(DEFAULT); + } + + private boolean shouldLog(LogLevel messageLevel) { + return !silent && level.getValue() <= messageLevel.getValue(); + } + + public boolean isDebug() { + return shouldLog(LogLevel.DEBUG); + } + + public boolean isInfo() { + return shouldLog(LogLevel.INFO); + } + + public boolean isWarn() { + return shouldLog(LogLevel.WARN); + } + + public boolean isError() { + return shouldLog(LogLevel.ERROR); + } + + public void debug(String message) { + if (isDebug()) { + logger.debug(message); + } + } + + public void info(String message) { + if (isInfo()) { + logger.info(message); + } + } + + public void warn(String message) { + if (isWarn()) { + logger.warn(message); + } + } + + public void error(String message) { + if (isError()) { + logger.error(message); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/LoggingInterceptor.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/LoggingInterceptor.java new file mode 100644 index 00000000..33c1d6aa --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/LoggingInterceptor.java @@ -0,0 +1,104 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * OkHttp interceptor that logs HTTP requests and responses. + * + *

Logs request method, URL, and headers (with sensitive values redacted) at debug level. + * Logs response status at debug level, and 4xx/5xx responses at error level. + * Does nothing if the logger is silent. + */ +public final class LoggingInterceptor implements Interceptor { + + private static final Set SENSITIVE_HEADERS = new HashSet<>(Arrays.asList( + "authorization", + "www-authenticate", + "x-api-key", + "api-key", + "apikey", + "x-api-token", + "x-auth-token", + "auth-token", + "proxy-authenticate", + "proxy-authorization", + "cookie", + "set-cookie", + "x-csrf-token", + "x-xsrf-token", + "x-session-token", + "x-access-token")); + + private final Logger logger; + + public LoggingInterceptor(Logger logger) { + this.logger = logger; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + + if (logger.isDebug()) { + StringBuilder sb = new StringBuilder(); + sb.append("HTTP Request: ").append(request.method()).append(" ").append(request.url()); + sb.append(" headers={"); + boolean first = true; + for (String name : request.headers().names()) { + if (!first) { + sb.append(", "); + } + sb.append(name).append("="); + if (SENSITIVE_HEADERS.contains(name.toLowerCase())) { + sb.append("[REDACTED]"); + } else { + sb.append(request.header(name)); + } + first = false; + } + sb.append("}"); + sb.append(" has_body=").append(request.body() != null); + logger.debug(sb.toString()); + } + + Response response = chain.proceed(request); + + if (logger.isDebug()) { + StringBuilder sb = new StringBuilder(); + sb.append("HTTP Response: status=").append(response.code()); + sb.append(" url=").append(response.request().url()); + sb.append(" headers={"); + boolean first = true; + for (String name : response.headers().names()) { + if (!first) { + sb.append(", "); + } + sb.append(name).append("="); + if (SENSITIVE_HEADERS.contains(name.toLowerCase())) { + sb.append("[REDACTED]"); + } else { + sb.append(response.header(name)); + } + first = false; + } + sb.append("}"); + logger.debug(sb.toString()); + } + + if (response.code() >= 400 && logger.isError()) { + logger.error("HTTP Error: status=" + response.code() + " url=" + + response.request().url()); + } + + return response; + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/MediaTypes.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/MediaTypes.java new file mode 100644 index 00000000..b2d084be --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/MediaTypes.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import okhttp3.MediaType; + +public final class MediaTypes { + + public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); + + private MediaTypes() {} +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/ObjectMappers.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/ObjectMappers.java new file mode 100644 index 00000000..7b4660ad --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/ObjectMappers.java @@ -0,0 +1,46 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .addModule(DoubleSerializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() {} + + public static String stringify(Object o) { + try { + return JSON_MAPPER + .setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } + + public static Object parseErrorBody(String responseBodyString) { + try { + return JSON_MAPPER.readValue(responseBodyString, Object.class); + } catch (JsonProcessingException ignored) { + return responseBodyString; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/QueryStringMapper.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/QueryStringMapper.java new file mode 100644 index 00000000..ba2dadc4 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/QueryStringMapper.java @@ -0,0 +1,142 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import okhttp3.HttpUrl; +import okhttp3.MultipartBody; + +public class QueryStringMapper { + + private static final ObjectMapper MAPPER = ObjectMappers.JSON_MAPPER; + + public static void addQueryParameter(HttpUrl.Builder httpUrl, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + httpUrl.addQueryParameter(key, valueNode.textValue()); + } else { + httpUrl.addQueryParameter(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().textValue()); + } else { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().toString()); + } + } + } + + public static void addFormDataPart( + MultipartBody.Builder multipartBody, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + multipartBody.addFormDataPart(key, valueNode.textValue()); + } else { + multipartBody.addFormDataPart(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().textValue()); + } else { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().toString()); + } + } + } + + public static List> flattenObject(ObjectNode object, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator> fields = object.fields(); + while (fields.hasNext()) { + Map.Entry field = fields.next(); + + String key = "[" + field.getKey() + "]"; + + if (field.getValue().isObject()) { + List> flatField = + flattenObject((ObjectNode) field.getValue(), arraysAsRepeats); + addAll(flat, flatField, key); + } else if (field.getValue().isArray()) { + List> flatField = + flattenArray((ArrayNode) field.getValue(), key, arraysAsRepeats); + addAll(flat, flatField, ""); + } else { + flat.add(new AbstractMap.SimpleEntry<>(key, field.getValue())); + } + } + + return flat; + } + + private static List> flattenArray( + ArrayNode array, String key, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator elements = array.elements(); + + int index = 0; + while (elements.hasNext()) { + JsonNode element = elements.next(); + + String indexKey = key + "[" + index + "]"; + + if (arraysAsRepeats) { + indexKey = key; + } + + if (element.isObject()) { + List> flatField = flattenObject((ObjectNode) element, arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else if (element.isArray()) { + List> flatField = flattenArray((ArrayNode) element, "", arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else { + flat.add(new AbstractMap.SimpleEntry<>(indexKey, element)); + } + + index++; + } + + return flat; + } + + private static void addAll( + List> target, List> source, String prefix) { + for (Map.Entry entry : source) { + Map.Entry entryToAdd = + new AbstractMap.SimpleEntry<>(prefix + entry.getKey(), entry.getValue()); + target.add(entryToAdd); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/RequestOptions.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/RequestOptions.java new file mode 100644 index 00000000..7f5ccea0 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/RequestOptions.java @@ -0,0 +1,154 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public final class RequestOptions { + private final String token; + + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private final Optional maxRetries; + + private final Map headers; + + private final Map> headerSuppliers; + + private final Map queryParameters; + + private final Map> queryParameterSuppliers; + + private RequestOptions( + String token, + Optional timeout, + TimeUnit timeoutTimeUnit, + Optional maxRetries, + Map headers, + Map> headerSuppliers, + Map queryParameters, + Map> queryParameterSuppliers) { + this.token = token; + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + this.maxRetries = maxRetries; + this.headers = headers; + this.headerSuppliers = headerSuppliers; + this.queryParameters = queryParameters; + this.queryParameterSuppliers = queryParameterSuppliers; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; + } + + public Optional getMaxRetries() { + return maxRetries; + } + + public Map getHeaders() { + Map headers = new HashMap<>(); + if (this.token != null) { + headers.put("Authorization", "Bearer " + this.token); + } + headers.putAll(this.headers); + this.headerSuppliers.forEach((key, supplier) -> { + headers.put(key, supplier.get()); + }); + return headers; + } + + public Map getQueryParameters() { + Map queryParameters = new HashMap<>(this.queryParameters); + this.queryParameterSuppliers.forEach((key, supplier) -> { + queryParameters.put(key, supplier.get()); + }); + return queryParameters; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String token = null; + + private Optional timeout = Optional.empty(); + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + + private Optional maxRetries = Optional.empty(); + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + private final Map queryParameters = new HashMap<>(); + + private final Map> queryParameterSuppliers = new HashMap<>(); + + public Builder token(String token) { + this.token = token; + return this; + } + + public Builder timeout(Integer timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { + this.timeout = Optional.of(timeout); + this.timeoutTimeUnit = timeoutTimeUnit; + return this; + } + + public Builder maxRetries(Integer maxRetries) { + this.maxRetries = Optional.of(maxRetries); + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + public Builder addQueryParameter(String key, String value) { + this.queryParameters.put(key, value); + return this; + } + + public Builder addQueryParameter(String key, Supplier value) { + this.queryParameterSuppliers.put(key, value); + return this; + } + + public RequestOptions build() { + return new RequestOptions( + token, + timeout, + timeoutTimeUnit, + maxRetries, + headers, + headerSuppliers, + queryParameters, + queryParameterSuppliers); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/ResponseDecompressionInterceptor.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/ResponseDecompressionInterceptor.java new file mode 100644 index 00000000..9988a7fc --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/ResponseDecompressionInterceptor.java @@ -0,0 +1,56 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import java.io.IOException; +import java.util.zip.Inflater; +import okhttp3.Interceptor; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.GzipSource; +import okio.InflaterSource; +import okio.Okio; +import okio.Source; + +/** + * Decompresses gzip and deflate encoded response bodies. OkHttp only performs + * transparent decompression when it adds the Accept-Encoding header itself, so + * responses to requests with an explicitly set Accept-Encoding header would + * otherwise be returned as raw compressed bytes. + */ +public class ResponseDecompressionInterceptor implements Interceptor { + + @Override + public Response intercept(Chain chain) throws IOException { + return decompress(chain.proceed(chain.request())); + } + + private Response decompress(Response response) { + ResponseBody body = response.body(); + if (body == null) { + return response; + } + String encoding = response.header("Content-Encoding"); + if (encoding == null) { + return response; + } + Source decompressedSource; + if (encoding.equalsIgnoreCase("gzip") || encoding.equalsIgnoreCase("x-gzip")) { + decompressedSource = new GzipSource(body.source()); + } else if (encoding.equalsIgnoreCase("deflate")) { + decompressedSource = new InflaterSource(body.source(), new Inflater()); + } else { + return response; + } + ResponseBody decompressedBody = ResponseBody.create(body.contentType(), -1L, Okio.buffer(decompressedSource)); + return response.newBuilder() + .headers(response.headers() + .newBuilder() + .removeAll("Content-Encoding") + .removeAll("Content-Length") + .build()) + .body(decompressedBody) + .build(); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/core/RetryInterceptor.java b/common/src/main/java/com/skyflow/generated/detect/rest/core/RetryInterceptor.java new file mode 100644 index 00000000..c46a526e --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/core/RetryInterceptor.java @@ -0,0 +1,242 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.core; + +import java.io.IOException; +import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Optional; +import java.util.Random; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class RetryInterceptor implements Interceptor { + + private static final Duration DEFAULT_INITIAL_RETRY_DELAY = Duration.ofMillis(1000); + private static final Duration DEFAULT_MAX_RETRY_DELAY = Duration.ofMillis(60000); + private static final double DEFAULT_JITTER_FACTOR = 0.2; + + private final int maxRetries; + private final Duration initialRetryDelay; + private final Duration maxRetryDelay; + private final double jitterFactor; + private final Random random = new Random(); + + public RetryInterceptor(int maxRetries) { + this(maxRetries, Optional.empty(), Optional.empty(), Optional.empty()); + } + + public RetryInterceptor( + int maxRetries, + Optional initialRetryDelayMillis, + Optional maxRetryDelayMillis, + Optional jitterFactor) { + initialRetryDelayMillis.ifPresent(delay -> { + if (delay < 0) { + throw new IllegalArgumentException("initialRetryDelayMillis must be non-negative"); + } + }); + maxRetryDelayMillis.ifPresent(delay -> { + if (delay < 0) { + throw new IllegalArgumentException("maxRetryDelayMillis must be non-negative"); + } + }); + jitterFactor.ifPresent(factor -> { + if (factor < 0 || factor > 1) { + throw new IllegalArgumentException("jitterFactor must be between 0 and 1"); + } + }); + this.maxRetries = maxRetries; + this.initialRetryDelay = initialRetryDelayMillis.map(Duration::ofMillis).orElse(DEFAULT_INITIAL_RETRY_DELAY); + this.maxRetryDelay = maxRetryDelayMillis.map(Duration::ofMillis).orElse(DEFAULT_MAX_RETRY_DELAY); + this.jitterFactor = jitterFactor.orElse(DEFAULT_JITTER_FACTOR); + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request request = chain.request(); + int effectiveMaxRetries = resolveMaxRetries(request); + Response response = chain.proceed(request); + + if (shouldRetry(response.code())) { + return retryChain(response, chain, effectiveMaxRetries); + } + + return response; + } + + private int resolveMaxRetries(Request request) { + MaxRetriesOverride override = request.tag(MaxRetriesOverride.class); + return override != null ? override.getValue() : this.maxRetries; + } + + private Response retryChain(Response response, Chain chain, int maxRetries) throws IOException { + ExponentialBackoff backoff = new ExponentialBackoff(maxRetries); + Optional nextBackoff = backoff.nextBackoff(response); + while (nextBackoff.isPresent()) { + try { + Thread.sleep(nextBackoff.get().toMillis()); + } catch (InterruptedException e) { + throw new IOException("Interrupted while trying request", e); + } + response.close(); + response = chain.proceed(chain.request()); + if (shouldRetry(response.code())) { + nextBackoff = backoff.nextBackoff(response); + } else { + return response; + } + } + + return response; + } + + /** + * Calculates the retry delay from response headers, with fallback to exponential backoff. + * Priority: Retry-After > X-RateLimit-Reset > Exponential Backoff + */ + private Duration getRetryDelayFromHeaders(Response response, int retryAttempt) { + // Check for Retry-After header first (RFC 7231), with no jitter + String retryAfter = response.header("Retry-After"); + if (retryAfter != null) { + // Parse as number of seconds... + Optional secondsDelay = tryParseLong(retryAfter) + .map(seconds -> seconds * 1000) + .filter(delayMs -> delayMs > 0) + .map(delayMs -> Math.min(delayMs, maxRetryDelay.toMillis())) + .map(Duration::ofMillis); + if (secondsDelay.isPresent()) { + return secondsDelay.get(); + } + + // ...or as an HTTP date; both are valid + Optional dateDelay = tryParseHttpDate(retryAfter) + .map(resetTime -> resetTime.toInstant().toEpochMilli() - System.currentTimeMillis()) + .filter(delayMs -> delayMs > 0) + .map(delayMs -> Math.min(delayMs, maxRetryDelay.toMillis())) + .map(Duration::ofMillis); + if (dateDelay.isPresent()) { + return dateDelay.get(); + } + } + + // Then check for industry-standard X-RateLimit-Reset header, with positive jitter + String rateLimitReset = response.header("X-RateLimit-Reset"); + if (rateLimitReset != null) { + // Assume Unix timestamp in epoch seconds + Optional rateLimitDelay = tryParseLong(rateLimitReset) + .map(resetTimeSeconds -> (resetTimeSeconds * 1000) - System.currentTimeMillis()) + .filter(delayMs -> delayMs > 0) + .map(delayMs -> Math.min(delayMs, maxRetryDelay.toMillis())) + .map(this::addPositiveJitter) + .map(Duration::ofMillis); + if (rateLimitDelay.isPresent()) { + return rateLimitDelay.get(); + } + } + + // Fall back to exponential backoff, with symmetric jitter + long initialDelayMillis = initialRetryDelay.toMillis(); + long maxDelayMillis = maxRetryDelay.toMillis(); + long cappedDelay; + if (retryAttempt >= Long.SIZE - 1 || initialDelayMillis > (maxDelayMillis >> retryAttempt)) { + // initialDelayMillis * 2^retryAttempt would exceed maxDelayMillis (or overflow) + cappedDelay = maxDelayMillis; + } else { + cappedDelay = Math.min(initialDelayMillis << retryAttempt, maxDelayMillis); // 2^retryAttempt + } + return Duration.ofMillis(addSymmetricJitter(cappedDelay)); + } + + /** + * Attempts to parse a string as a long, returning empty Optional on failure. + */ + private Optional tryParseLong(String value) { + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(Long.parseLong(value)); + } catch (NumberFormatException e) { + return Optional.empty(); + } + } + + /** + * Attempts to parse a string as an HTTP date (RFC 1123), returning empty Optional on failure. + */ + private Optional tryParseHttpDate(String value) { + if (value == null) { + return Optional.empty(); + } + try { + return Optional.of(ZonedDateTime.parse(value, DateTimeFormatter.RFC_1123_DATE_TIME)); + } catch (DateTimeParseException e) { + return Optional.empty(); + } + } + + /** + * Adds positive jitter (100-120% of original value) to prevent thundering herd. + * Used for X-RateLimit-Reset header delays. + */ + private long addPositiveJitter(long delayMs) { + double jitterMultiplier = 1.0 + (random.nextDouble() * jitterFactor); + return (long) (delayMs * jitterMultiplier); + } + + /** + * Adds symmetric jitter (90-110% of original value) to prevent thundering herd. + * Used for exponential backoff delays. + */ + private long addSymmetricJitter(long delayMs) { + double jitterMultiplier = 1.0 + ((random.nextDouble() - 0.5) * jitterFactor); + return (long) (delayMs * jitterMultiplier); + } + + private static boolean shouldRetry(int statusCode) { + return statusCode == 408 || statusCode == 429 || statusCode >= 500; + } + + /** + * Per-request override carried on the OkHttp {@link Request} as a tag. + * When present, the interceptor uses this value instead of the client-wide + * {@code maxRetries} configured at construction time. + */ + public static final class MaxRetriesOverride { + private final int value; + + public MaxRetriesOverride(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + private final class ExponentialBackoff { + + private final int maxNumRetries; + + private int retryNumber = 0; + + ExponentialBackoff(int maxNumRetries) { + this.maxNumRetries = maxNumRetries; + } + + public Optional nextBackoff(Response response) { + if (retryNumber >= maxNumRetries) { + return Optional.empty(); + } + + Duration delay = getRetryDelayFromHeaders(response, retryNumber); + retryNumber += 1; + return Optional.of(delay); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/errors/BadRequestError.java b/common/src/main/java/com/skyflow/generated/detect/rest/errors/BadRequestError.java new file mode 100644 index 00000000..7858ef63 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/errors/BadRequestError.java @@ -0,0 +1,33 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.errors; + +import com.skyflow.generated.detect.rest.core.ApiClientApiException; +import com.skyflow.generated.detect.rest.types.ErrorResponse; +import okhttp3.Response; + +public final class BadRequestError extends ApiClientApiException { + /** + * The body of the response that triggered the exception. + */ + private final ErrorResponse body; + + public BadRequestError(ErrorResponse body) { + super("BadRequestError", 400, body); + this.body = body; + } + + public BadRequestError(ErrorResponse body, Response rawResponse) { + super("BadRequestError", 400, body, rawResponse); + this.body = body; + } + + /** + * @return the body + */ + @java.lang.Override + public ErrorResponse body() { + return this.body; + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/errors/InternalServerError.java b/common/src/main/java/com/skyflow/generated/detect/rest/errors/InternalServerError.java new file mode 100644 index 00000000..c21d7766 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/errors/InternalServerError.java @@ -0,0 +1,33 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.errors; + +import com.skyflow.generated.detect.rest.core.ApiClientApiException; +import com.skyflow.generated.detect.rest.types.ErrorResponse; +import okhttp3.Response; + +public final class InternalServerError extends ApiClientApiException { + /** + * The body of the response that triggered the exception. + */ + private final ErrorResponse body; + + public InternalServerError(ErrorResponse body) { + super("InternalServerError", 500, body); + this.body = body; + } + + public InternalServerError(ErrorResponse body, Response rawResponse) { + super("InternalServerError", 500, body, rawResponse); + this.body = body; + } + + /** + * @return the body + */ + @java.lang.Override + public ErrorResponse body() { + return this.body; + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/errors/UnauthorizedError.java b/common/src/main/java/com/skyflow/generated/detect/rest/errors/UnauthorizedError.java new file mode 100644 index 00000000..63754690 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/errors/UnauthorizedError.java @@ -0,0 +1,33 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.errors; + +import com.skyflow.generated.detect.rest.core.ApiClientApiException; +import com.skyflow.generated.detect.rest.types.ErrorResponse; +import okhttp3.Response; + +public final class UnauthorizedError extends ApiClientApiException { + /** + * The body of the response that triggered the exception. + */ + private final ErrorResponse body; + + public UnauthorizedError(ErrorResponse body) { + super("UnauthorizedError", 401, body); + this.body = body; + } + + public UnauthorizedError(ErrorResponse body, Response rawResponse) { + super("UnauthorizedError", 401, body, rawResponse); + this.body = body; + } + + /** + * @return the body + */ + @java.lang.Override + public ErrorResponse body() { + return this.body; + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/AsyncFilesV2Client.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/AsyncFilesV2Client.java new file mode 100644 index 00000000..c032d144 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/AsyncFilesV2Client.java @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.filesv2; + +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.DeidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.GetRunV2Request; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.ReidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.types.DeidentifyFileResponseV2; +import com.skyflow.generated.detect.rest.types.DetectRunsResponseV2; +import com.skyflow.generated.detect.rest.types.ReidentifyFileResponseV2; +import java.util.concurrent.CompletableFuture; + +public class AsyncFilesV2Client { + protected final ClientOptions clientOptions; + + private final AsyncRawFilesV2Client rawClient; + + public AsyncFilesV2Client(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new AsyncRawFilesV2Client(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public AsyncRawFilesV2Client withRawResponse() { + return this.rawClient; + } + + /** + * De-identifies sensitive data from a file.

Note: + *

    + *
  • Layout may vary with token-based redaction for MS Office files (PPT, DOCX, and XLS) and PDFs when token-based PDF processing is selected.
  • + *
  • V2 API is in beta.
  • + *
+ */ + public CompletableFuture deidentifyFileV2(DeidentifyFileRequestV2 request) { + return this.rawClient.deidentifyFileV2(request).thenApply(response -> response.body()); + } + + /** + * De-identifies sensitive data from a file.

Note: + *
    + *
  • Layout may vary with token-based redaction for MS Office files (PPT, DOCX, and XLS) and PDFs when token-based PDF processing is selected.
  • + *
  • V2 API is in beta.
  • + *
+ */ + public CompletableFuture deidentifyFileV2( + DeidentifyFileRequestV2 request, RequestOptions requestOptions) { + return this.rawClient.deidentifyFileV2(request, requestOptions).thenApply(response -> response.body()); + } + + /** + * Re-identifies tokens in a file.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public CompletableFuture reidentifyFileV2(ReidentifyFileRequestV2 request) { + return this.rawClient.reidentifyFileV2(request).thenApply(response -> response.body()); + } + + /** + * Re-identifies tokens in a file.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public CompletableFuture reidentifyFileV2( + ReidentifyFileRequestV2 request, RequestOptions requestOptions) { + return this.rawClient.reidentifyFileV2(request, requestOptions).thenApply(response -> response.body()); + } + + /** + * Returns the status of a detect run.

Note: V2 API is in beta. + */ + public CompletableFuture getRunV2(String runId, GetRunV2Request request) { + return this.rawClient.getRunV2(runId, request).thenApply(response -> response.body()); + } + + /** + * Returns the status of a detect run.

Note: V2 API is in beta. + */ + public CompletableFuture getRunV2( + String runId, GetRunV2Request request, RequestOptions requestOptions) { + return this.rawClient.getRunV2(runId, request, requestOptions).thenApply(response -> response.body()); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/AsyncRawFilesV2Client.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/AsyncRawFilesV2Client.java new file mode 100644 index 00000000..6764e32b --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/AsyncRawFilesV2Client.java @@ -0,0 +1,350 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.filesv2; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.skyflow.generated.detect.rest.core.ApiClientApiException; +import com.skyflow.generated.detect.rest.core.ApiClientException; +import com.skyflow.generated.detect.rest.core.ApiClientHttpResponse; +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.MediaTypes; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.core.QueryStringMapper; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.core.RetryInterceptor; +import com.skyflow.generated.detect.rest.errors.BadRequestError; +import com.skyflow.generated.detect.rest.errors.InternalServerError; +import com.skyflow.generated.detect.rest.errors.UnauthorizedError; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.DeidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.GetRunV2Request; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.ReidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.types.DeidentifyFileResponseV2; +import com.skyflow.generated.detect.rest.types.DetectRunsResponseV2; +import com.skyflow.generated.detect.rest.types.ErrorResponse; +import com.skyflow.generated.detect.rest.types.ReidentifyFileResponseV2; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; + +public class AsyncRawFilesV2Client { + protected final ClientOptions clientOptions; + + public AsyncRawFilesV2Client(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + /** + * De-identifies sensitive data from a file.

Note: + *
    + *
  • Layout may vary with token-based redaction for MS Office files (PPT, DOCX, and XLS) and PDFs when token-based PDF processing is selected.
  • + *
  • V2 API is in beta.
  • + *
+ */ + public CompletableFuture> deidentifyFileV2( + DeidentifyFileRequestV2 request) { + return deidentifyFileV2(request, null); + } + + /** + * De-identifies sensitive data from a file.

Note: + *
    + *
  • Layout may vary with token-based redaction for MS Office files (PPT, DOCX, and XLS) and PDFs when token-based PDF processing is selected.
  • + *
  • V2 API is in beta.
  • + *
+ */ + public CompletableFuture> deidentifyFileV2( + DeidentifyFileRequestV2 request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/detect/deidentify/file"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) { + okhttpRequest = okhttpRequest + .newBuilder() + .tag( + RetryInterceptor.MaxRetriesOverride.class, + new RetryInterceptor.MaxRetriesOverride( + requestOptions.getMaxRetries().get())) + .build(); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, DeidentifyFileResponseV2.class), + response)); + return; + } + try { + switch (response.code()) { + case 400: + future.completeExceptionally(new BadRequestError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + case 401: + future.completeExceptionally(new UnauthorizedError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + case 500: + future.completeExceptionally(new InternalServerError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (JsonProcessingException e) { + future.completeExceptionally( + new ApiClientException("Failed to deserialize response: " + e.getMessage(), e)); + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } + + /** + * Re-identifies tokens in a file.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public CompletableFuture> reidentifyFileV2( + ReidentifyFileRequestV2 request) { + return reidentifyFileV2(request, null); + } + + /** + * Re-identifies tokens in a file.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public CompletableFuture> reidentifyFileV2( + ReidentifyFileRequestV2 request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/detect/reidentify/file"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) { + okhttpRequest = okhttpRequest + .newBuilder() + .tag( + RetryInterceptor.MaxRetriesOverride.class, + new RetryInterceptor.MaxRetriesOverride( + requestOptions.getMaxRetries().get())) + .build(); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ReidentifyFileResponseV2.class), + response)); + return; + } + try { + switch (response.code()) { + case 400: + future.completeExceptionally(new BadRequestError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + case 401: + future.completeExceptionally(new UnauthorizedError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + case 500: + future.completeExceptionally(new InternalServerError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (JsonProcessingException e) { + future.completeExceptionally( + new ApiClientException("Failed to deserialize response: " + e.getMessage(), e)); + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } + + /** + * Returns the status of a detect run.

Note: V2 API is in beta. + */ + public CompletableFuture> getRunV2( + String runId, GetRunV2Request request) { + return getRunV2(runId, request, null); + } + + /** + * Returns the status of a detect run.

Note: V2 API is in beta. + */ + public CompletableFuture> getRunV2( + String runId, GetRunV2Request request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/detect/runs") + .addPathSegment(runId); + QueryStringMapper.addQueryParameter(httpUrl, "vaultId", request.getVaultId(), false); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request.Builder _requestBuilder = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json"); + Request okhttpRequest = _requestBuilder.build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) { + okhttpRequest = okhttpRequest + .newBuilder() + .tag( + RetryInterceptor.MaxRetriesOverride.class, + new RetryInterceptor.MaxRetriesOverride( + requestOptions.getMaxRetries().get())) + .build(); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, DetectRunsResponseV2.class), + response)); + return; + } + try { + switch (response.code()) { + case 400: + future.completeExceptionally(new BadRequestError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + case 401: + future.completeExceptionally(new UnauthorizedError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + case 500: + future.completeExceptionally(new InternalServerError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (JsonProcessingException e) { + future.completeExceptionally( + new ApiClientException("Failed to deserialize response: " + e.getMessage(), e)); + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/FilesV2Client.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/FilesV2Client.java new file mode 100644 index 00000000..9f9f8c1a --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/FilesV2Client.java @@ -0,0 +1,81 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.filesv2; + +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.DeidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.GetRunV2Request; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.ReidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.types.DeidentifyFileResponseV2; +import com.skyflow.generated.detect.rest.types.DetectRunsResponseV2; +import com.skyflow.generated.detect.rest.types.ReidentifyFileResponseV2; + +public class FilesV2Client { + protected final ClientOptions clientOptions; + + private final RawFilesV2Client rawClient; + + public FilesV2Client(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new RawFilesV2Client(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public RawFilesV2Client withRawResponse() { + return this.rawClient; + } + + /** + * De-identifies sensitive data from a file.

Note: + *
    + *
  • Layout may vary with token-based redaction for MS Office files (PPT, DOCX, and XLS) and PDFs when token-based PDF processing is selected.
  • + *
  • V2 API is in beta.
  • + *
+ */ + public DeidentifyFileResponseV2 deidentifyFileV2(DeidentifyFileRequestV2 request) { + return this.rawClient.deidentifyFileV2(request).body(); + } + + /** + * De-identifies sensitive data from a file.

Note: + *
    + *
  • Layout may vary with token-based redaction for MS Office files (PPT, DOCX, and XLS) and PDFs when token-based PDF processing is selected.
  • + *
  • V2 API is in beta.
  • + *
+ */ + public DeidentifyFileResponseV2 deidentifyFileV2(DeidentifyFileRequestV2 request, RequestOptions requestOptions) { + return this.rawClient.deidentifyFileV2(request, requestOptions).body(); + } + + /** + * Re-identifies tokens in a file.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public ReidentifyFileResponseV2 reidentifyFileV2(ReidentifyFileRequestV2 request) { + return this.rawClient.reidentifyFileV2(request).body(); + } + + /** + * Re-identifies tokens in a file.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public ReidentifyFileResponseV2 reidentifyFileV2(ReidentifyFileRequestV2 request, RequestOptions requestOptions) { + return this.rawClient.reidentifyFileV2(request, requestOptions).body(); + } + + /** + * Returns the status of a detect run.

Note: V2 API is in beta. + */ + public DetectRunsResponseV2 getRunV2(String runId, GetRunV2Request request) { + return this.rawClient.getRunV2(runId, request).body(); + } + + /** + * Returns the status of a detect run.

Note: V2 API is in beta. + */ + public DetectRunsResponseV2 getRunV2(String runId, GetRunV2Request request, RequestOptions requestOptions) { + return this.rawClient.getRunV2(runId, request, requestOptions).body(); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/RawFilesV2Client.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/RawFilesV2Client.java new file mode 100644 index 00000000..2ffe352d --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/RawFilesV2Client.java @@ -0,0 +1,282 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.filesv2; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.skyflow.generated.detect.rest.core.ApiClientApiException; +import com.skyflow.generated.detect.rest.core.ApiClientException; +import com.skyflow.generated.detect.rest.core.ApiClientHttpResponse; +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.MediaTypes; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.core.QueryStringMapper; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.core.RetryInterceptor; +import com.skyflow.generated.detect.rest.errors.BadRequestError; +import com.skyflow.generated.detect.rest.errors.InternalServerError; +import com.skyflow.generated.detect.rest.errors.UnauthorizedError; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.DeidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.GetRunV2Request; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.ReidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.types.DeidentifyFileResponseV2; +import com.skyflow.generated.detect.rest.types.DetectRunsResponseV2; +import com.skyflow.generated.detect.rest.types.ErrorResponse; +import com.skyflow.generated.detect.rest.types.ReidentifyFileResponseV2; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class RawFilesV2Client { + protected final ClientOptions clientOptions; + + public RawFilesV2Client(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + /** + * De-identifies sensitive data from a file.

Note: + *
    + *
  • Layout may vary with token-based redaction for MS Office files (PPT, DOCX, and XLS) and PDFs when token-based PDF processing is selected.
  • + *
  • V2 API is in beta.
  • + *
+ */ + public ApiClientHttpResponse deidentifyFileV2(DeidentifyFileRequestV2 request) { + return deidentifyFileV2(request, null); + } + + /** + * De-identifies sensitive data from a file.

Note: + *
    + *
  • Layout may vary with token-based redaction for MS Office files (PPT, DOCX, and XLS) and PDFs when token-based PDF processing is selected.
  • + *
  • V2 API is in beta.
  • + *
+ */ + public ApiClientHttpResponse deidentifyFileV2( + DeidentifyFileRequestV2 request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/detect/deidentify/file"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) { + okhttpRequest = okhttpRequest + .newBuilder() + .tag( + RetryInterceptor.MaxRetriesOverride.class, + new RetryInterceptor.MaxRetriesOverride( + requestOptions.getMaxRetries().get())) + .build(); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, DeidentifyFileResponseV2.class), + response); + } + try { + switch (response.code()) { + case 400: + throw new BadRequestError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + case 401: + throw new UnauthorizedError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + case 500: + throw new InternalServerError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new ApiClientApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to deserialize response: " + e.getMessage(), e); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } + + /** + * Re-identifies tokens in a file.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public ApiClientHttpResponse reidentifyFileV2(ReidentifyFileRequestV2 request) { + return reidentifyFileV2(request, null); + } + + /** + * Re-identifies tokens in a file.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public ApiClientHttpResponse reidentifyFileV2( + ReidentifyFileRequestV2 request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/detect/reidentify/file"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) { + okhttpRequest = okhttpRequest + .newBuilder() + .tag( + RetryInterceptor.MaxRetriesOverride.class, + new RetryInterceptor.MaxRetriesOverride( + requestOptions.getMaxRetries().get())) + .build(); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ReidentifyFileResponseV2.class), + response); + } + try { + switch (response.code()) { + case 400: + throw new BadRequestError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + case 401: + throw new UnauthorizedError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + case 500: + throw new InternalServerError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new ApiClientApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to deserialize response: " + e.getMessage(), e); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } + + /** + * Returns the status of a detect run.

Note: V2 API is in beta. + */ + public ApiClientHttpResponse getRunV2(String runId, GetRunV2Request request) { + return getRunV2(runId, request, null); + } + + /** + * Returns the status of a detect run.

Note: V2 API is in beta. + */ + public ApiClientHttpResponse getRunV2( + String runId, GetRunV2Request request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/detect/runs") + .addPathSegment(runId); + QueryStringMapper.addQueryParameter(httpUrl, "vaultId", request.getVaultId(), false); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + Request.Builder _requestBuilder = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Accept", "application/json"); + Request okhttpRequest = _requestBuilder.build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) { + okhttpRequest = okhttpRequest + .newBuilder() + .tag( + RetryInterceptor.MaxRetriesOverride.class, + new RetryInterceptor.MaxRetriesOverride( + requestOptions.getMaxRetries().get())) + .build(); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, DetectRunsResponseV2.class), response); + } + try { + switch (response.code()) { + case 400: + throw new BadRequestError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + case 401: + throw new UnauthorizedError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + case 500: + throw new InternalServerError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new ApiClientApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to deserialize response: " + e.getMessage(), e); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/requests/DeidentifyFileRequestV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/requests/DeidentifyFileRequestV2.java new file mode 100644 index 00000000..f95ee7ab --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/requests/DeidentifyFileRequestV2.java @@ -0,0 +1,286 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.filesv2.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.resources.filesv2.types.DeidentifyFileRequestV2DataFormat; +import com.skyflow.generated.detect.rest.resources.filesv2.types.DeidentifyFileRequestV2DataSource; +import com.skyflow.generated.detect.rest.types.DetectConfigV2; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DeidentifyFileRequestV2.Builder.class) +public final class DeidentifyFileRequestV2 { + private final DeidentifyFileRequestV2DataSource dataSource; + + private final String value; + + private final Optional dataFormat; + + private final Optional configurationId; + + private final Optional configuration; + + private final Map additionalProperties; + + private DeidentifyFileRequestV2( + DeidentifyFileRequestV2DataSource dataSource, + String value, + Optional dataFormat, + Optional configurationId, + Optional configuration, + Map additionalProperties) { + this.dataSource = dataSource; + this.value = value; + this.dataFormat = dataFormat; + this.configurationId = configurationId; + this.configuration = configuration; + this.additionalProperties = additionalProperties; + } + + /** + * @return Data source of the input file. BASE64: Base64-encoded file string. SKYFLOW_ID: Reference file by vault ID. PRESIGNED_URL: S3 presigned URL of input file. + */ + @JsonProperty("dataSource") + public DeidentifyFileRequestV2DataSource getDataSource() { + return dataSource; + } + + /** + * @return File data corresponding to the specified dataSource type. + */ + @JsonProperty("value") + public String getValue() { + return value; + } + + /** + * @return Format of the input file. The VAULT_TOKEN de-identification type is supported only for txt, json, csv, xml, jsonl, docx, xlsx, and pptx files. + */ + @JsonProperty("dataFormat") + public Optional getDataFormat() { + return dataFormat; + } + + /** + * @return ID of the Detect configuration to use for de-identification. + */ + @JsonProperty("configurationId") + public Optional getConfigurationId() { + return configurationId; + } + + @JsonProperty("configuration") + public Optional getConfiguration() { + return configuration; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DeidentifyFileRequestV2 && equalTo((DeidentifyFileRequestV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DeidentifyFileRequestV2 other) { + return dataSource.equals(other.dataSource) + && value.equals(other.value) + && dataFormat.equals(other.dataFormat) + && configurationId.equals(other.configurationId) + && configuration.equals(other.configuration); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.dataSource, this.value, this.dataFormat, this.configurationId, this.configuration); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static DataSourceStage builder() { + return new Builder(); + } + + public interface DataSourceStage { + /** + *

Data source of the input file. BASE64: Base64-encoded file string. SKYFLOW_ID: Reference file by vault ID. PRESIGNED_URL: S3 presigned URL of input file.

+ */ + ValueStage dataSource(@NotNull DeidentifyFileRequestV2DataSource dataSource); + + Builder from(DeidentifyFileRequestV2 other); + } + + public interface ValueStage { + /** + *

File data corresponding to the specified dataSource type.

+ */ + _FinalStage value(@NotNull String value); + } + + public interface _FinalStage { + DeidentifyFileRequestV2 build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Format of the input file. The VAULT_TOKEN de-identification type is supported only for txt, json, csv, xml, jsonl, docx, xlsx, and pptx files.

+ */ + _FinalStage dataFormat(Optional dataFormat); + + _FinalStage dataFormat(DeidentifyFileRequestV2DataFormat dataFormat); + + /** + *

ID of the Detect configuration to use for de-identification.

+ */ + _FinalStage configurationId(Optional configurationId); + + _FinalStage configurationId(String configurationId); + + _FinalStage configuration(Optional configuration); + + _FinalStage configuration(DetectConfigV2 configuration); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements DataSourceStage, ValueStage, _FinalStage { + private DeidentifyFileRequestV2DataSource dataSource; + + private String value; + + private Optional configuration = Optional.empty(); + + private Optional configurationId = Optional.empty(); + + private Optional dataFormat = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(DeidentifyFileRequestV2 other) { + dataSource(other.getDataSource()); + value(other.getValue()); + dataFormat(other.getDataFormat()); + configurationId(other.getConfigurationId()); + configuration(other.getConfiguration()); + return this; + } + + /** + *

Data source of the input file. BASE64: Base64-encoded file string. SKYFLOW_ID: Reference file by vault ID. PRESIGNED_URL: S3 presigned URL of input file.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("dataSource") + public ValueStage dataSource(@NotNull DeidentifyFileRequestV2DataSource dataSource) { + this.dataSource = Objects.requireNonNull(dataSource, "dataSource must not be null"); + return this; + } + + /** + *

File data corresponding to the specified dataSource type.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("value") + public _FinalStage value(@NotNull String value) { + this.value = Objects.requireNonNull(value, "value must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage configuration(DetectConfigV2 configuration) { + this.configuration = Optional.ofNullable(configuration); + return this; + } + + @java.lang.Override + @JsonSetter(value = "configuration", nulls = Nulls.SKIP) + public _FinalStage configuration(Optional configuration) { + this.configuration = configuration; + return this; + } + + /** + *

ID of the Detect configuration to use for de-identification.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage configurationId(String configurationId) { + this.configurationId = Optional.ofNullable(configurationId); + return this; + } + + /** + *

ID of the Detect configuration to use for de-identification.

+ */ + @java.lang.Override + @JsonSetter(value = "configurationId", nulls = Nulls.SKIP) + public _FinalStage configurationId(Optional configurationId) { + this.configurationId = configurationId; + return this; + } + + /** + *

Format of the input file. The VAULT_TOKEN de-identification type is supported only for txt, json, csv, xml, jsonl, docx, xlsx, and pptx files.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage dataFormat(DeidentifyFileRequestV2DataFormat dataFormat) { + this.dataFormat = Optional.ofNullable(dataFormat); + return this; + } + + /** + *

Format of the input file. The VAULT_TOKEN de-identification type is supported only for txt, json, csv, xml, jsonl, docx, xlsx, and pptx files.

+ */ + @java.lang.Override + @JsonSetter(value = "dataFormat", nulls = Nulls.SKIP) + public _FinalStage dataFormat(Optional dataFormat) { + this.dataFormat = dataFormat; + return this; + } + + @java.lang.Override + public DeidentifyFileRequestV2 build() { + return new DeidentifyFileRequestV2( + dataSource, value, dataFormat, configurationId, configuration, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/requests/GetRunV2Request.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/requests/GetRunV2Request.java new file mode 100644 index 00000000..f90f7153 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/requests/GetRunV2Request.java @@ -0,0 +1,118 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.filesv2.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = GetRunV2Request.Builder.class) +public final class GetRunV2Request { + private final String vaultId; + + private final Map additionalProperties; + + private GetRunV2Request(String vaultId, Map additionalProperties) { + this.vaultId = vaultId; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("vaultId") + public String getVaultId() { + return vaultId; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof GetRunV2Request && equalTo((GetRunV2Request) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(GetRunV2Request other) { + return vaultId.equals(other.vaultId); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.vaultId); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static VaultIdStage builder() { + return new Builder(); + } + + public interface VaultIdStage { + _FinalStage vaultId(@NotNull String vaultId); + + Builder from(GetRunV2Request other); + } + + public interface _FinalStage { + GetRunV2Request build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements VaultIdStage, _FinalStage { + private String vaultId; + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(GetRunV2Request other) { + vaultId(other.getVaultId()); + return this; + } + + @java.lang.Override + @JsonSetter("vaultId") + public _FinalStage vaultId(@NotNull String vaultId) { + this.vaultId = Objects.requireNonNull(vaultId, "vaultId must not be null"); + return this; + } + + @java.lang.Override + public GetRunV2Request build() { + return new GetRunV2Request(vaultId, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/requests/ReidentifyFileRequestV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/requests/ReidentifyFileRequestV2.java new file mode 100644 index 00000000..29751862 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/requests/ReidentifyFileRequestV2.java @@ -0,0 +1,291 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.filesv2.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.resources.filesv2.types.ReidentifyFileRequestV2DataFormat; +import com.skyflow.generated.detect.rest.resources.filesv2.types.ReidentifyFileRequestV2DataSource; +import com.skyflow.generated.detect.rest.types.RedactionLevel; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ReidentifyFileRequestV2.Builder.class) +public final class ReidentifyFileRequestV2 { + private final ReidentifyFileRequestV2DataSource dataSource; + + private final String value; + + private final Optional dataFormat; + + private final String vaultId; + + private final Optional> redactionLevel; + + private final Map additionalProperties; + + private ReidentifyFileRequestV2( + ReidentifyFileRequestV2DataSource dataSource, + String value, + Optional dataFormat, + String vaultId, + Optional> redactionLevel, + Map additionalProperties) { + this.dataSource = dataSource; + this.value = value; + this.dataFormat = dataFormat; + this.vaultId = vaultId; + this.redactionLevel = redactionLevel; + this.additionalProperties = additionalProperties; + } + + /** + * @return Data source of the input file. BASE64: Base64-encoded file string. SKYFLOW_ID: Reference file by vault ID. PRESIGNED_URL: S3 presigned URL of input file. + */ + @JsonProperty("dataSource") + public ReidentifyFileRequestV2DataSource getDataSource() { + return dataSource; + } + + /** + * @return File data corresponding to the specified dataSource type. + */ + @JsonProperty("value") + public String getValue() { + return value; + } + + /** + * @return Format of the input file. Limited to the formats that support the VAULT_TOKEN de-identification type. + */ + @JsonProperty("dataFormat") + public Optional getDataFormat() { + return dataFormat; + } + + /** + * @return ID of the vault used for de-identification. + */ + @JsonProperty("vaultId") + public String getVaultId() { + return vaultId; + } + + /** + * @return Replacement patterns or types applied to entity types or token groups during re-identification. Specifies a replacement pattern/type and the token group or entity type it applies to during re-identification. Set tokenGroupName for FlowVault and entityName for SkyVault. Similarly, set redactionType for FlowVault and redactionPattern for SkyVaultto specify the replacement. + */ + @JsonProperty("redactionLevel") + public Optional> getRedactionLevel() { + return redactionLevel; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ReidentifyFileRequestV2 && equalTo((ReidentifyFileRequestV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ReidentifyFileRequestV2 other) { + return dataSource.equals(other.dataSource) + && value.equals(other.value) + && dataFormat.equals(other.dataFormat) + && vaultId.equals(other.vaultId) + && redactionLevel.equals(other.redactionLevel); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.dataSource, this.value, this.dataFormat, this.vaultId, this.redactionLevel); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static DataSourceStage builder() { + return new Builder(); + } + + public interface DataSourceStage { + /** + *

Data source of the input file. BASE64: Base64-encoded file string. SKYFLOW_ID: Reference file by vault ID. PRESIGNED_URL: S3 presigned URL of input file.

+ */ + ValueStage dataSource(@NotNull ReidentifyFileRequestV2DataSource dataSource); + + Builder from(ReidentifyFileRequestV2 other); + } + + public interface ValueStage { + /** + *

File data corresponding to the specified dataSource type.

+ */ + VaultIdStage value(@NotNull String value); + } + + public interface VaultIdStage { + /** + *

ID of the vault used for de-identification.

+ */ + _FinalStage vaultId(@NotNull String vaultId); + } + + public interface _FinalStage { + ReidentifyFileRequestV2 build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Format of the input file. Limited to the formats that support the VAULT_TOKEN de-identification type.

+ */ + _FinalStage dataFormat(Optional dataFormat); + + _FinalStage dataFormat(ReidentifyFileRequestV2DataFormat dataFormat); + + /** + *

Replacement patterns or types applied to entity types or token groups during re-identification. Specifies a replacement pattern/type and the token group or entity type it applies to during re-identification. Set tokenGroupName for FlowVault and entityName for SkyVault. Similarly, set redactionType for FlowVault and redactionPattern for SkyVaultto specify the replacement.

+ */ + _FinalStage redactionLevel(Optional> redactionLevel); + + _FinalStage redactionLevel(List redactionLevel); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements DataSourceStage, ValueStage, VaultIdStage, _FinalStage { + private ReidentifyFileRequestV2DataSource dataSource; + + private String value; + + private String vaultId; + + private Optional> redactionLevel = Optional.empty(); + + private Optional dataFormat = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(ReidentifyFileRequestV2 other) { + dataSource(other.getDataSource()); + value(other.getValue()); + dataFormat(other.getDataFormat()); + vaultId(other.getVaultId()); + redactionLevel(other.getRedactionLevel()); + return this; + } + + /** + *

Data source of the input file. BASE64: Base64-encoded file string. SKYFLOW_ID: Reference file by vault ID. PRESIGNED_URL: S3 presigned URL of input file.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("dataSource") + public ValueStage dataSource(@NotNull ReidentifyFileRequestV2DataSource dataSource) { + this.dataSource = Objects.requireNonNull(dataSource, "dataSource must not be null"); + return this; + } + + /** + *

File data corresponding to the specified dataSource type.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("value") + public VaultIdStage value(@NotNull String value) { + this.value = Objects.requireNonNull(value, "value must not be null"); + return this; + } + + /** + *

ID of the vault used for de-identification.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("vaultId") + public _FinalStage vaultId(@NotNull String vaultId) { + this.vaultId = Objects.requireNonNull(vaultId, "vaultId must not be null"); + return this; + } + + /** + *

Replacement patterns or types applied to entity types or token groups during re-identification. Specifies a replacement pattern/type and the token group or entity type it applies to during re-identification. Set tokenGroupName for FlowVault and entityName for SkyVault. Similarly, set redactionType for FlowVault and redactionPattern for SkyVaultto specify the replacement.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage redactionLevel(List redactionLevel) { + this.redactionLevel = Optional.ofNullable(redactionLevel); + return this; + } + + /** + *

Replacement patterns or types applied to entity types or token groups during re-identification. Specifies a replacement pattern/type and the token group or entity type it applies to during re-identification. Set tokenGroupName for FlowVault and entityName for SkyVault. Similarly, set redactionType for FlowVault and redactionPattern for SkyVaultto specify the replacement.

+ */ + @java.lang.Override + @JsonSetter(value = "redactionLevel", nulls = Nulls.SKIP) + public _FinalStage redactionLevel(Optional> redactionLevel) { + this.redactionLevel = redactionLevel; + return this; + } + + /** + *

Format of the input file. Limited to the formats that support the VAULT_TOKEN de-identification type.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage dataFormat(ReidentifyFileRequestV2DataFormat dataFormat) { + this.dataFormat = Optional.ofNullable(dataFormat); + return this; + } + + /** + *

Format of the input file. Limited to the formats that support the VAULT_TOKEN de-identification type.

+ */ + @java.lang.Override + @JsonSetter(value = "dataFormat", nulls = Nulls.SKIP) + public _FinalStage dataFormat(Optional dataFormat) { + this.dataFormat = dataFormat; + return this; + } + + @java.lang.Override + public ReidentifyFileRequestV2 build() { + return new ReidentifyFileRequestV2( + dataSource, value, dataFormat, vaultId, redactionLevel, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/DeidentifyFileRequestV2DataFormat.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/DeidentifyFileRequestV2DataFormat.java new file mode 100644 index 00000000..6a4aaf01 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/DeidentifyFileRequestV2DataFormat.java @@ -0,0 +1,301 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.filesv2.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DeidentifyFileRequestV2DataFormat { + public static final DeidentifyFileRequestV2DataFormat BMP = new DeidentifyFileRequestV2DataFormat(Value.BMP, "bmp"); + + public static final DeidentifyFileRequestV2DataFormat PPTX = + new DeidentifyFileRequestV2DataFormat(Value.PPTX, "pptx"); + + public static final DeidentifyFileRequestV2DataFormat WAV = new DeidentifyFileRequestV2DataFormat(Value.WAV, "wav"); + + public static final DeidentifyFileRequestV2DataFormat DOC = new DeidentifyFileRequestV2DataFormat(Value.DOC, "doc"); + + public static final DeidentifyFileRequestV2DataFormat XML = new DeidentifyFileRequestV2DataFormat(Value.XML, "xml"); + + public static final DeidentifyFileRequestV2DataFormat ZIP = new DeidentifyFileRequestV2DataFormat(Value.ZIP, "zip"); + + public static final DeidentifyFileRequestV2DataFormat MP3 = new DeidentifyFileRequestV2DataFormat(Value.MP3, "mp3"); + + public static final DeidentifyFileRequestV2DataFormat PPT = new DeidentifyFileRequestV2DataFormat(Value.PPT, "ppt"); + + public static final DeidentifyFileRequestV2DataFormat JPEG = + new DeidentifyFileRequestV2DataFormat(Value.JPEG, "jpeg"); + + public static final DeidentifyFileRequestV2DataFormat TIFF = + new DeidentifyFileRequestV2DataFormat(Value.TIFF, "tiff"); + + public static final DeidentifyFileRequestV2DataFormat PDF = new DeidentifyFileRequestV2DataFormat(Value.PDF, "pdf"); + + public static final DeidentifyFileRequestV2DataFormat DOCX = + new DeidentifyFileRequestV2DataFormat(Value.DOCX, "docx"); + + public static final DeidentifyFileRequestV2DataFormat PNG = new DeidentifyFileRequestV2DataFormat(Value.PNG, "png"); + + public static final DeidentifyFileRequestV2DataFormat JSONL = + new DeidentifyFileRequestV2DataFormat(Value.JSONL, "jsonl"); + + public static final DeidentifyFileRequestV2DataFormat CSV = new DeidentifyFileRequestV2DataFormat(Value.CSV, "csv"); + + public static final DeidentifyFileRequestV2DataFormat JPG = new DeidentifyFileRequestV2DataFormat(Value.JPG, "jpg"); + + public static final DeidentifyFileRequestV2DataFormat DCM = new DeidentifyFileRequestV2DataFormat(Value.DCM, "dcm"); + + public static final DeidentifyFileRequestV2DataFormat JSON = + new DeidentifyFileRequestV2DataFormat(Value.JSON, "json"); + + public static final DeidentifyFileRequestV2DataFormat GIF = new DeidentifyFileRequestV2DataFormat(Value.GIF, "gif"); + + public static final DeidentifyFileRequestV2DataFormat TXT = new DeidentifyFileRequestV2DataFormat(Value.TXT, "txt"); + + public static final DeidentifyFileRequestV2DataFormat XLS = new DeidentifyFileRequestV2DataFormat(Value.XLS, "xls"); + + public static final DeidentifyFileRequestV2DataFormat XLSX = + new DeidentifyFileRequestV2DataFormat(Value.XLSX, "xlsx"); + + public static final DeidentifyFileRequestV2DataFormat TIF = new DeidentifyFileRequestV2DataFormat(Value.TIF, "tif"); + + private final Value value; + + private final String string; + + DeidentifyFileRequestV2DataFormat(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DeidentifyFileRequestV2DataFormat + && this.string.equals(((DeidentifyFileRequestV2DataFormat) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case BMP: + return visitor.visitBmp(); + case PPTX: + return visitor.visitPptx(); + case WAV: + return visitor.visitWav(); + case DOC: + return visitor.visitDoc(); + case XML: + return visitor.visitXml(); + case ZIP: + return visitor.visitZip(); + case MP3: + return visitor.visitMp3(); + case PPT: + return visitor.visitPpt(); + case JPEG: + return visitor.visitJpeg(); + case TIFF: + return visitor.visitTiff(); + case PDF: + return visitor.visitPdf(); + case DOCX: + return visitor.visitDocx(); + case PNG: + return visitor.visitPng(); + case JSONL: + return visitor.visitJsonl(); + case CSV: + return visitor.visitCsv(); + case JPG: + return visitor.visitJpg(); + case DCM: + return visitor.visitDcm(); + case JSON: + return visitor.visitJson(); + case GIF: + return visitor.visitGif(); + case TXT: + return visitor.visitTxt(); + case XLS: + return visitor.visitXls(); + case XLSX: + return visitor.visitXlsx(); + case TIF: + return visitor.visitTif(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DeidentifyFileRequestV2DataFormat valueOf(String value) { + switch (value) { + case "bmp": + return BMP; + case "pptx": + return PPTX; + case "wav": + return WAV; + case "doc": + return DOC; + case "xml": + return XML; + case "zip": + return ZIP; + case "mp3": + return MP3; + case "ppt": + return PPT; + case "jpeg": + return JPEG; + case "tiff": + return TIFF; + case "pdf": + return PDF; + case "docx": + return DOCX; + case "png": + return PNG; + case "jsonl": + return JSONL; + case "csv": + return CSV; + case "jpg": + return JPG; + case "dcm": + return DCM; + case "json": + return JSON; + case "gif": + return GIF; + case "txt": + return TXT; + case "xls": + return XLS; + case "xlsx": + return XLSX; + case "tif": + return TIF; + default: + return new DeidentifyFileRequestV2DataFormat(Value.UNKNOWN, value); + } + } + + public enum Value { + MP3, + + WAV, + + PDF, + + TXT, + + CSV, + + JSON, + + JPG, + + JPEG, + + TIF, + + TIFF, + + PNG, + + BMP, + + XLS, + + XLSX, + + DOC, + + DOCX, + + PPT, + + PPTX, + + XML, + + DCM, + + JSONL, + + ZIP, + + GIF, + + UNKNOWN + } + + public interface Visitor { + T visitMp3(); + + T visitWav(); + + T visitPdf(); + + T visitTxt(); + + T visitCsv(); + + T visitJson(); + + T visitJpg(); + + T visitJpeg(); + + T visitTif(); + + T visitTiff(); + + T visitPng(); + + T visitBmp(); + + T visitXls(); + + T visitXlsx(); + + T visitDoc(); + + T visitDocx(); + + T visitPpt(); + + T visitPptx(); + + T visitXml(); + + T visitDcm(); + + T visitJsonl(); + + T visitZip(); + + T visitGif(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/DeidentifyFileRequestV2DataSource.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/DeidentifyFileRequestV2DataSource.java new file mode 100644 index 00000000..83c9aed3 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/DeidentifyFileRequestV2DataSource.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.filesv2.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DeidentifyFileRequestV2DataSource { + public static final DeidentifyFileRequestV2DataSource BASE64 = + new DeidentifyFileRequestV2DataSource(Value.BASE64, "BASE64"); + + public static final DeidentifyFileRequestV2DataSource SKYFLOW_ID = + new DeidentifyFileRequestV2DataSource(Value.SKYFLOW_ID, "SKYFLOW_ID"); + + public static final DeidentifyFileRequestV2DataSource PRESIGNED_URL = + new DeidentifyFileRequestV2DataSource(Value.PRESIGNED_URL, "PRESIGNED_URL"); + + private final Value value; + + private final String string; + + DeidentifyFileRequestV2DataSource(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DeidentifyFileRequestV2DataSource + && this.string.equals(((DeidentifyFileRequestV2DataSource) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case BASE64: + return visitor.visitBase64(); + case SKYFLOW_ID: + return visitor.visitSkyflowId(); + case PRESIGNED_URL: + return visitor.visitPresignedUrl(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DeidentifyFileRequestV2DataSource valueOf(String value) { + switch (value) { + case "BASE64": + return BASE64; + case "SKYFLOW_ID": + return SKYFLOW_ID; + case "PRESIGNED_URL": + return PRESIGNED_URL; + default: + return new DeidentifyFileRequestV2DataSource(Value.UNKNOWN, value); + } + } + + public enum Value { + BASE64, + + SKYFLOW_ID, + + PRESIGNED_URL, + + UNKNOWN + } + + public interface Visitor { + T visitBase64(); + + T visitSkyflowId(); + + T visitPresignedUrl(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/ReidentifyFileRequestV2DataFormat.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/ReidentifyFileRequestV2DataFormat.java new file mode 100644 index 00000000..df3c688f --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/ReidentifyFileRequestV2DataFormat.java @@ -0,0 +1,149 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.filesv2.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class ReidentifyFileRequestV2DataFormat { + public static final ReidentifyFileRequestV2DataFormat PPTX = + new ReidentifyFileRequestV2DataFormat(Value.PPTX, "pptx"); + + public static final ReidentifyFileRequestV2DataFormat JSONL = + new ReidentifyFileRequestV2DataFormat(Value.JSONL, "jsonl"); + + public static final ReidentifyFileRequestV2DataFormat CSV = new ReidentifyFileRequestV2DataFormat(Value.CSV, "csv"); + + public static final ReidentifyFileRequestV2DataFormat XML = new ReidentifyFileRequestV2DataFormat(Value.XML, "xml"); + + public static final ReidentifyFileRequestV2DataFormat JSON = + new ReidentifyFileRequestV2DataFormat(Value.JSON, "json"); + + public static final ReidentifyFileRequestV2DataFormat TXT = new ReidentifyFileRequestV2DataFormat(Value.TXT, "txt"); + + public static final ReidentifyFileRequestV2DataFormat DOCX = + new ReidentifyFileRequestV2DataFormat(Value.DOCX, "docx"); + + public static final ReidentifyFileRequestV2DataFormat XLSX = + new ReidentifyFileRequestV2DataFormat(Value.XLSX, "xlsx"); + + private final Value value; + + private final String string; + + ReidentifyFileRequestV2DataFormat(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof ReidentifyFileRequestV2DataFormat + && this.string.equals(((ReidentifyFileRequestV2DataFormat) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case PPTX: + return visitor.visitPptx(); + case JSONL: + return visitor.visitJsonl(); + case CSV: + return visitor.visitCsv(); + case XML: + return visitor.visitXml(); + case JSON: + return visitor.visitJson(); + case TXT: + return visitor.visitTxt(); + case DOCX: + return visitor.visitDocx(); + case XLSX: + return visitor.visitXlsx(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static ReidentifyFileRequestV2DataFormat valueOf(String value) { + switch (value) { + case "pptx": + return PPTX; + case "jsonl": + return JSONL; + case "csv": + return CSV; + case "xml": + return XML; + case "json": + return JSON; + case "txt": + return TXT; + case "docx": + return DOCX; + case "xlsx": + return XLSX; + default: + return new ReidentifyFileRequestV2DataFormat(Value.UNKNOWN, value); + } + } + + public enum Value { + TXT, + + CSV, + + JSON, + + XLSX, + + DOCX, + + PPTX, + + XML, + + JSONL, + + UNKNOWN + } + + public interface Visitor { + T visitTxt(); + + T visitCsv(); + + T visitJson(); + + T visitXlsx(); + + T visitDocx(); + + T visitPptx(); + + T visitXml(); + + T visitJsonl(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/ReidentifyFileRequestV2DataSource.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/ReidentifyFileRequestV2DataSource.java new file mode 100644 index 00000000..538cbc49 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/filesv2/types/ReidentifyFileRequestV2DataSource.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.filesv2.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class ReidentifyFileRequestV2DataSource { + public static final ReidentifyFileRequestV2DataSource BASE64 = + new ReidentifyFileRequestV2DataSource(Value.BASE64, "BASE64"); + + public static final ReidentifyFileRequestV2DataSource SKYFLOW_ID = + new ReidentifyFileRequestV2DataSource(Value.SKYFLOW_ID, "SKYFLOW_ID"); + + public static final ReidentifyFileRequestV2DataSource PRESIGNED_URL = + new ReidentifyFileRequestV2DataSource(Value.PRESIGNED_URL, "PRESIGNED_URL"); + + private final Value value; + + private final String string; + + ReidentifyFileRequestV2DataSource(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof ReidentifyFileRequestV2DataSource + && this.string.equals(((ReidentifyFileRequestV2DataSource) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case BASE64: + return visitor.visitBase64(); + case SKYFLOW_ID: + return visitor.visitSkyflowId(); + case PRESIGNED_URL: + return visitor.visitPresignedUrl(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static ReidentifyFileRequestV2DataSource valueOf(String value) { + switch (value) { + case "BASE64": + return BASE64; + case "SKYFLOW_ID": + return SKYFLOW_ID; + case "PRESIGNED_URL": + return PRESIGNED_URL; + default: + return new ReidentifyFileRequestV2DataSource(Value.UNKNOWN, value); + } + } + + public enum Value { + BASE64, + + SKYFLOW_ID, + + PRESIGNED_URL, + + UNKNOWN + } + + public interface Visitor { + T visitBase64(); + + T visitSkyflowId(); + + T visitPresignedUrl(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/AsyncGuardrailsV2Client.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/AsyncGuardrailsV2Client.java new file mode 100644 index 00000000..fe74a2d3 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/AsyncGuardrailsV2Client.java @@ -0,0 +1,43 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.guardrailsv2; + +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.resources.guardrailsv2.requests.DetectGuardrailsRequestV2; +import com.skyflow.generated.detect.rest.types.DetectGuardrailsResponseV2; +import java.util.concurrent.CompletableFuture; + +public class AsyncGuardrailsV2Client { + protected final ClientOptions clientOptions; + + private final AsyncRawGuardrailsV2Client rawClient; + + public AsyncGuardrailsV2Client(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new AsyncRawGuardrailsV2Client(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public AsyncRawGuardrailsV2Client withRawResponse() { + return this.rawClient; + } + + /** + * Preserve safety and compliance with usage policies.

Note: V2 API is in beta. + */ + public CompletableFuture checkGuardrailsV2(DetectGuardrailsRequestV2 request) { + return this.rawClient.checkGuardrailsV2(request).thenApply(response -> response.body()); + } + + /** + * Preserve safety and compliance with usage policies.

Note: V2 API is in beta. + */ + public CompletableFuture checkGuardrailsV2( + DetectGuardrailsRequestV2 request, RequestOptions requestOptions) { + return this.rawClient.checkGuardrailsV2(request, requestOptions).thenApply(response -> response.body()); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/AsyncRawGuardrailsV2Client.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/AsyncRawGuardrailsV2Client.java new file mode 100644 index 00000000..c6b84c0e --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/AsyncRawGuardrailsV2Client.java @@ -0,0 +1,142 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.guardrailsv2; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.skyflow.generated.detect.rest.core.ApiClientApiException; +import com.skyflow.generated.detect.rest.core.ApiClientException; +import com.skyflow.generated.detect.rest.core.ApiClientHttpResponse; +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.MediaTypes; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.core.RetryInterceptor; +import com.skyflow.generated.detect.rest.errors.BadRequestError; +import com.skyflow.generated.detect.rest.errors.InternalServerError; +import com.skyflow.generated.detect.rest.errors.UnauthorizedError; +import com.skyflow.generated.detect.rest.resources.guardrailsv2.requests.DetectGuardrailsRequestV2; +import com.skyflow.generated.detect.rest.types.DetectGuardrailsResponseV2; +import com.skyflow.generated.detect.rest.types.ErrorResponse; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; + +public class AsyncRawGuardrailsV2Client { + protected final ClientOptions clientOptions; + + public AsyncRawGuardrailsV2Client(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + /** + * Preserve safety and compliance with usage policies.

Note: V2 API is in beta. + */ + public CompletableFuture> checkGuardrailsV2( + DetectGuardrailsRequestV2 request) { + return checkGuardrailsV2(request, null); + } + + /** + * Preserve safety and compliance with usage policies.

Note: V2 API is in beta. + */ + public CompletableFuture> checkGuardrailsV2( + DetectGuardrailsRequestV2 request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/detect/guardrails"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) { + okhttpRequest = okhttpRequest + .newBuilder() + .tag( + RetryInterceptor.MaxRetriesOverride.class, + new RetryInterceptor.MaxRetriesOverride( + requestOptions.getMaxRetries().get())) + .build(); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue( + responseBodyString, DetectGuardrailsResponseV2.class), + response)); + return; + } + try { + switch (response.code()) { + case 400: + future.completeExceptionally(new BadRequestError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + case 401: + future.completeExceptionally(new UnauthorizedError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + case 500: + future.completeExceptionally(new InternalServerError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (JsonProcessingException e) { + future.completeExceptionally( + new ApiClientException("Failed to deserialize response: " + e.getMessage(), e)); + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/GuardrailsV2Client.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/GuardrailsV2Client.java new file mode 100644 index 00000000..2d3a9bf7 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/GuardrailsV2Client.java @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.guardrailsv2; + +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.resources.guardrailsv2.requests.DetectGuardrailsRequestV2; +import com.skyflow.generated.detect.rest.types.DetectGuardrailsResponseV2; + +public class GuardrailsV2Client { + protected final ClientOptions clientOptions; + + private final RawGuardrailsV2Client rawClient; + + public GuardrailsV2Client(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new RawGuardrailsV2Client(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public RawGuardrailsV2Client withRawResponse() { + return this.rawClient; + } + + /** + * Preserve safety and compliance with usage policies.

Note: V2 API is in beta. + */ + public DetectGuardrailsResponseV2 checkGuardrailsV2(DetectGuardrailsRequestV2 request) { + return this.rawClient.checkGuardrailsV2(request).body(); + } + + /** + * Preserve safety and compliance with usage policies.

Note: V2 API is in beta. + */ + public DetectGuardrailsResponseV2 checkGuardrailsV2( + DetectGuardrailsRequestV2 request, RequestOptions requestOptions) { + return this.rawClient.checkGuardrailsV2(request, requestOptions).body(); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/RawGuardrailsV2Client.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/RawGuardrailsV2Client.java new file mode 100644 index 00000000..c124c516 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/RawGuardrailsV2Client.java @@ -0,0 +1,116 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.guardrailsv2; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.skyflow.generated.detect.rest.core.ApiClientApiException; +import com.skyflow.generated.detect.rest.core.ApiClientException; +import com.skyflow.generated.detect.rest.core.ApiClientHttpResponse; +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.MediaTypes; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.core.RetryInterceptor; +import com.skyflow.generated.detect.rest.errors.BadRequestError; +import com.skyflow.generated.detect.rest.errors.InternalServerError; +import com.skyflow.generated.detect.rest.errors.UnauthorizedError; +import com.skyflow.generated.detect.rest.resources.guardrailsv2.requests.DetectGuardrailsRequestV2; +import com.skyflow.generated.detect.rest.types.DetectGuardrailsResponseV2; +import com.skyflow.generated.detect.rest.types.ErrorResponse; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class RawGuardrailsV2Client { + protected final ClientOptions clientOptions; + + public RawGuardrailsV2Client(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + /** + * Preserve safety and compliance with usage policies.

Note: V2 API is in beta. + */ + public ApiClientHttpResponse checkGuardrailsV2(DetectGuardrailsRequestV2 request) { + return checkGuardrailsV2(request, null); + } + + /** + * Preserve safety and compliance with usage policies.

Note: V2 API is in beta. + */ + public ApiClientHttpResponse checkGuardrailsV2( + DetectGuardrailsRequestV2 request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/detect/guardrails"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) { + okhttpRequest = okhttpRequest + .newBuilder() + .tag( + RetryInterceptor.MaxRetriesOverride.class, + new RetryInterceptor.MaxRetriesOverride( + requestOptions.getMaxRetries().get())) + .build(); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, DetectGuardrailsResponseV2.class), + response); + } + try { + switch (response.code()) { + case 400: + throw new BadRequestError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + case 401: + throw new UnauthorizedError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + case 500: + throw new InternalServerError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new ApiClientApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to deserialize response: " + e.getMessage(), e); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/requests/DetectGuardrailsRequestV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/requests/DetectGuardrailsRequestV2.java new file mode 100644 index 00000000..f6d8dbad --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/guardrailsv2/requests/DetectGuardrailsRequestV2.java @@ -0,0 +1,253 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.guardrailsv2.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DetectGuardrailsRequestV2.Builder.class) +public final class DetectGuardrailsRequestV2 { + private final String text; + + private final Optional checkToxicity; + + private final Optional> denyTopics; + + private final String vaultId; + + private final Map additionalProperties; + + private DetectGuardrailsRequestV2( + String text, + Optional checkToxicity, + Optional> denyTopics, + String vaultId, + Map additionalProperties) { + this.text = text; + this.checkToxicity = checkToxicity; + this.denyTopics = denyTopics; + this.vaultId = vaultId; + this.additionalProperties = additionalProperties; + } + + /** + * @return Text to check against guardrails. + */ + @JsonProperty("text") + public String getText() { + return text; + } + + /** + * @return If true, checks for toxicity in the text. + */ + @JsonProperty("checkToxicity") + public Optional getCheckToxicity() { + return checkToxicity; + } + + /** + * @return List of topics to deny. + */ + @JsonProperty("denyTopics") + public Optional> getDenyTopics() { + return denyTopics; + } + + /** + * @return ID of the vault. + */ + @JsonProperty("vaultId") + public String getVaultId() { + return vaultId; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetectGuardrailsRequestV2 && equalTo((DetectGuardrailsRequestV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DetectGuardrailsRequestV2 other) { + return text.equals(other.text) + && checkToxicity.equals(other.checkToxicity) + && denyTopics.equals(other.denyTopics) + && vaultId.equals(other.vaultId); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.text, this.checkToxicity, this.denyTopics, this.vaultId); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static TextStage builder() { + return new Builder(); + } + + public interface TextStage { + /** + *

Text to check against guardrails.

+ */ + VaultIdStage text(@NotNull String text); + + Builder from(DetectGuardrailsRequestV2 other); + } + + public interface VaultIdStage { + /** + *

ID of the vault.

+ */ + _FinalStage vaultId(@NotNull String vaultId); + } + + public interface _FinalStage { + DetectGuardrailsRequestV2 build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

If true, checks for toxicity in the text.

+ */ + _FinalStage checkToxicity(Optional checkToxicity); + + _FinalStage checkToxicity(Boolean checkToxicity); + + /** + *

List of topics to deny.

+ */ + _FinalStage denyTopics(Optional> denyTopics); + + _FinalStage denyTopics(List denyTopics); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements TextStage, VaultIdStage, _FinalStage { + private String text; + + private String vaultId; + + private Optional> denyTopics = Optional.empty(); + + private Optional checkToxicity = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(DetectGuardrailsRequestV2 other) { + text(other.getText()); + checkToxicity(other.getCheckToxicity()); + denyTopics(other.getDenyTopics()); + vaultId(other.getVaultId()); + return this; + } + + /** + *

Text to check against guardrails.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("text") + public VaultIdStage text(@NotNull String text) { + this.text = Objects.requireNonNull(text, "text must not be null"); + return this; + } + + /** + *

ID of the vault.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("vaultId") + public _FinalStage vaultId(@NotNull String vaultId) { + this.vaultId = Objects.requireNonNull(vaultId, "vaultId must not be null"); + return this; + } + + /** + *

List of topics to deny.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage denyTopics(List denyTopics) { + this.denyTopics = Optional.ofNullable(denyTopics); + return this; + } + + /** + *

List of topics to deny.

+ */ + @java.lang.Override + @JsonSetter(value = "denyTopics", nulls = Nulls.SKIP) + public _FinalStage denyTopics(Optional> denyTopics) { + this.denyTopics = denyTopics; + return this; + } + + /** + *

If true, checks for toxicity in the text.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage checkToxicity(Boolean checkToxicity) { + this.checkToxicity = Optional.ofNullable(checkToxicity); + return this; + } + + /** + *

If true, checks for toxicity in the text.

+ */ + @java.lang.Override + @JsonSetter(value = "checkToxicity", nulls = Nulls.SKIP) + public _FinalStage checkToxicity(Optional checkToxicity) { + this.checkToxicity = checkToxicity; + return this; + } + + @java.lang.Override + public DetectGuardrailsRequestV2 build() { + return new DetectGuardrailsRequestV2(text, checkToxicity, denyTopics, vaultId, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/AsyncRawStringsV2Client.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/AsyncRawStringsV2Client.java new file mode 100644 index 00000000..cac69231 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/AsyncRawStringsV2Client.java @@ -0,0 +1,246 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.stringsv2; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.skyflow.generated.detect.rest.core.ApiClientApiException; +import com.skyflow.generated.detect.rest.core.ApiClientException; +import com.skyflow.generated.detect.rest.core.ApiClientHttpResponse; +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.MediaTypes; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.core.RetryInterceptor; +import com.skyflow.generated.detect.rest.errors.BadRequestError; +import com.skyflow.generated.detect.rest.errors.InternalServerError; +import com.skyflow.generated.detect.rest.errors.UnauthorizedError; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.DeidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.ReidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.types.DeidentifyStringResponseV2; +import com.skyflow.generated.detect.rest.types.ErrorResponse; +import com.skyflow.generated.detect.rest.types.ReidentifyStringResponseV2; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.jetbrains.annotations.NotNull; + +public class AsyncRawStringsV2Client { + protected final ClientOptions clientOptions; + + public AsyncRawStringsV2Client(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + /** + * De-identifies sensitive data from a string.

Note: V2 API is in beta. + */ + public CompletableFuture> deidentifyStringV2( + DeidentifyStringRequestV2 request) { + return deidentifyStringV2(request, null); + } + + /** + * De-identifies sensitive data from a string.

Note: V2 API is in beta. + */ + public CompletableFuture> deidentifyStringV2( + DeidentifyStringRequestV2 request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/detect/deidentify/string"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) { + okhttpRequest = okhttpRequest + .newBuilder() + .tag( + RetryInterceptor.MaxRetriesOverride.class, + new RetryInterceptor.MaxRetriesOverride( + requestOptions.getMaxRetries().get())) + .build(); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue( + responseBodyString, DeidentifyStringResponseV2.class), + response)); + return; + } + try { + switch (response.code()) { + case 400: + future.completeExceptionally(new BadRequestError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + case 401: + future.completeExceptionally(new UnauthorizedError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + case 500: + future.completeExceptionally(new InternalServerError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (JsonProcessingException e) { + future.completeExceptionally( + new ApiClientException("Failed to deserialize response: " + e.getMessage(), e)); + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } + + /** + * Re-identifies tokens in a string.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public CompletableFuture> reidentifyStringV2( + ReidentifyStringRequestV2 request) { + return reidentifyStringV2(request, null); + } + + /** + * Re-identifies tokens in a string.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public CompletableFuture> reidentifyStringV2( + ReidentifyStringRequestV2 request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/detect/reidentify/string"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) { + okhttpRequest = okhttpRequest + .newBuilder() + .tag( + RetryInterceptor.MaxRetriesOverride.class, + new RetryInterceptor.MaxRetriesOverride( + requestOptions.getMaxRetries().get())) + .build(); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue( + responseBodyString, ReidentifyStringResponseV2.class), + response)); + return; + } + try { + switch (response.code()) { + case 400: + future.completeExceptionally(new BadRequestError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + case 401: + future.completeExceptionally(new UnauthorizedError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + case 500: + future.completeExceptionally(new InternalServerError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), + response)); + return; + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), response.code(), errorBody, response)); + return; + } catch (JsonProcessingException e) { + future.completeExceptionally( + new ApiClientException("Failed to deserialize response: " + e.getMessage(), e)); + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/AsyncStringsV2Client.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/AsyncStringsV2Client.java new file mode 100644 index 00000000..47c34f06 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/AsyncStringsV2Client.java @@ -0,0 +1,60 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.stringsv2; + +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.DeidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.ReidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.types.DeidentifyStringResponseV2; +import com.skyflow.generated.detect.rest.types.ReidentifyStringResponseV2; +import java.util.concurrent.CompletableFuture; + +public class AsyncStringsV2Client { + protected final ClientOptions clientOptions; + + private final AsyncRawStringsV2Client rawClient; + + public AsyncStringsV2Client(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new AsyncRawStringsV2Client(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public AsyncRawStringsV2Client withRawResponse() { + return this.rawClient; + } + + /** + * De-identifies sensitive data from a string.

Note: V2 API is in beta. + */ + public CompletableFuture deidentifyStringV2(DeidentifyStringRequestV2 request) { + return this.rawClient.deidentifyStringV2(request).thenApply(response -> response.body()); + } + + /** + * De-identifies sensitive data from a string.

Note: V2 API is in beta. + */ + public CompletableFuture deidentifyStringV2( + DeidentifyStringRequestV2 request, RequestOptions requestOptions) { + return this.rawClient.deidentifyStringV2(request, requestOptions).thenApply(response -> response.body()); + } + + /** + * Re-identifies tokens in a string.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public CompletableFuture reidentifyStringV2(ReidentifyStringRequestV2 request) { + return this.rawClient.reidentifyStringV2(request).thenApply(response -> response.body()); + } + + /** + * Re-identifies tokens in a string.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public CompletableFuture reidentifyStringV2( + ReidentifyStringRequestV2 request, RequestOptions requestOptions) { + return this.rawClient.reidentifyStringV2(request, requestOptions).thenApply(response -> response.body()); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/RawStringsV2Client.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/RawStringsV2Client.java new file mode 100644 index 00000000..61f71478 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/RawStringsV2Client.java @@ -0,0 +1,198 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.stringsv2; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.skyflow.generated.detect.rest.core.ApiClientApiException; +import com.skyflow.generated.detect.rest.core.ApiClientException; +import com.skyflow.generated.detect.rest.core.ApiClientHttpResponse; +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.MediaTypes; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.core.RetryInterceptor; +import com.skyflow.generated.detect.rest.errors.BadRequestError; +import com.skyflow.generated.detect.rest.errors.InternalServerError; +import com.skyflow.generated.detect.rest.errors.UnauthorizedError; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.DeidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.ReidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.types.DeidentifyStringResponseV2; +import com.skyflow.generated.detect.rest.types.ErrorResponse; +import com.skyflow.generated.detect.rest.types.ReidentifyStringResponseV2; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class RawStringsV2Client { + protected final ClientOptions clientOptions; + + public RawStringsV2Client(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + /** + * De-identifies sensitive data from a string.

Note: V2 API is in beta. + */ + public ApiClientHttpResponse deidentifyStringV2(DeidentifyStringRequestV2 request) { + return deidentifyStringV2(request, null); + } + + /** + * De-identifies sensitive data from a string.

Note: V2 API is in beta. + */ + public ApiClientHttpResponse deidentifyStringV2( + DeidentifyStringRequestV2 request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/detect/deidentify/string"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) { + okhttpRequest = okhttpRequest + .newBuilder() + .tag( + RetryInterceptor.MaxRetriesOverride.class, + new RetryInterceptor.MaxRetriesOverride( + requestOptions.getMaxRetries().get())) + .build(); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, DeidentifyStringResponseV2.class), + response); + } + try { + switch (response.code()) { + case 400: + throw new BadRequestError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + case 401: + throw new UnauthorizedError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + case 500: + throw new InternalServerError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new ApiClientApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to deserialize response: " + e.getMessage(), e); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } + + /** + * Re-identifies tokens in a string.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public ApiClientHttpResponse reidentifyStringV2(ReidentifyStringRequestV2 request) { + return reidentifyStringV2(request, null); + } + + /** + * Re-identifies tokens in a string.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public ApiClientHttpResponse reidentifyStringV2( + ReidentifyStringRequestV2 request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/detect/reidentify/string"); + if (requestOptions != null) { + requestOptions.getQueryParameters().forEach((_key, _value) -> { + httpUrl.addQueryParameter(_key, _value); + }); + } + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl.build()) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + if (requestOptions != null && requestOptions.getMaxRetries().isPresent()) { + okhttpRequest = okhttpRequest + .newBuilder() + .tag( + RetryInterceptor.MaxRetriesOverride.class, + new RetryInterceptor.MaxRetriesOverride( + requestOptions.getMaxRetries().get())) + .build(); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + if (response.isSuccessful()) { + return new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ReidentifyStringResponseV2.class), + response); + } + try { + switch (response.code()) { + case 400: + throw new BadRequestError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + case 401: + throw new UnauthorizedError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + case 500: + throw new InternalServerError( + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, ErrorResponse.class), response); + } + } catch (JsonProcessingException ignored) { + // unable to map error response, throwing generic error + } + Object errorBody = ObjectMappers.parseErrorBody(responseBodyString); + throw new ApiClientApiException( + "Error with status code " + response.code(), response.code(), errorBody, response); + } catch (JsonProcessingException e) { + throw new ApiClientException("Failed to deserialize response: " + e.getMessage(), e); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/StringsV2Client.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/StringsV2Client.java new file mode 100644 index 00000000..2a50d897 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/StringsV2Client.java @@ -0,0 +1,59 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.stringsv2; + +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.DeidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.ReidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.types.DeidentifyStringResponseV2; +import com.skyflow.generated.detect.rest.types.ReidentifyStringResponseV2; + +public class StringsV2Client { + protected final ClientOptions clientOptions; + + private final RawStringsV2Client rawClient; + + public StringsV2Client(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new RawStringsV2Client(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public RawStringsV2Client withRawResponse() { + return this.rawClient; + } + + /** + * De-identifies sensitive data from a string.

Note: V2 API is in beta. + */ + public DeidentifyStringResponseV2 deidentifyStringV2(DeidentifyStringRequestV2 request) { + return this.rawClient.deidentifyStringV2(request).body(); + } + + /** + * De-identifies sensitive data from a string.

Note: V2 API is in beta. + */ + public DeidentifyStringResponseV2 deidentifyStringV2( + DeidentifyStringRequestV2 request, RequestOptions requestOptions) { + return this.rawClient.deidentifyStringV2(request, requestOptions).body(); + } + + /** + * Re-identifies tokens in a string.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public ReidentifyStringResponseV2 reidentifyStringV2(ReidentifyStringRequestV2 request) { + return this.rawClient.reidentifyStringV2(request).body(); + } + + /** + * Re-identifies tokens in a string.

Requires the Detect Invoker, Detect Manager, or Vault Owner role. Re-identifying vault_token values also requires a policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

Note: V2 API is in beta. + */ + public ReidentifyStringResponseV2 reidentifyStringV2( + ReidentifyStringRequestV2 request, RequestOptions requestOptions) { + return this.rawClient.reidentifyStringV2(request, requestOptions).body(); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/requests/DeidentifyStringRequestV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/requests/DeidentifyStringRequestV2.java new file mode 100644 index 00000000..c3964a6d --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/requests/DeidentifyStringRequestV2.java @@ -0,0 +1,206 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.stringsv2.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.types.DetectConfigV2; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DeidentifyStringRequestV2.Builder.class) +public final class DeidentifyStringRequestV2 { + private final String text; + + private final Optional configurationId; + + private final Optional configuration; + + private final Map additionalProperties; + + private DeidentifyStringRequestV2( + String text, + Optional configurationId, + Optional configuration, + Map additionalProperties) { + this.text = text; + this.configurationId = configurationId; + this.configuration = configuration; + this.additionalProperties = additionalProperties; + } + + /** + * @return Text to deidentify. + */ + @JsonProperty("text") + public String getText() { + return text; + } + + /** + * @return ID of the Detect configuration to use for de-identification. + */ + @JsonProperty("configurationId") + public Optional getConfigurationId() { + return configurationId; + } + + @JsonProperty("configuration") + public Optional getConfiguration() { + return configuration; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DeidentifyStringRequestV2 && equalTo((DeidentifyStringRequestV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DeidentifyStringRequestV2 other) { + return text.equals(other.text) + && configurationId.equals(other.configurationId) + && configuration.equals(other.configuration); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.text, this.configurationId, this.configuration); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static TextStage builder() { + return new Builder(); + } + + public interface TextStage { + /** + *

Text to deidentify.

+ */ + _FinalStage text(@NotNull String text); + + Builder from(DeidentifyStringRequestV2 other); + } + + public interface _FinalStage { + DeidentifyStringRequestV2 build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

ID of the Detect configuration to use for de-identification.

+ */ + _FinalStage configurationId(Optional configurationId); + + _FinalStage configurationId(String configurationId); + + _FinalStage configuration(Optional configuration); + + _FinalStage configuration(DetectConfigV2 configuration); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements TextStage, _FinalStage { + private String text; + + private Optional configuration = Optional.empty(); + + private Optional configurationId = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(DeidentifyStringRequestV2 other) { + text(other.getText()); + configurationId(other.getConfigurationId()); + configuration(other.getConfiguration()); + return this; + } + + /** + *

Text to deidentify.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("text") + public _FinalStage text(@NotNull String text) { + this.text = Objects.requireNonNull(text, "text must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage configuration(DetectConfigV2 configuration) { + this.configuration = Optional.ofNullable(configuration); + return this; + } + + @java.lang.Override + @JsonSetter(value = "configuration", nulls = Nulls.SKIP) + public _FinalStage configuration(Optional configuration) { + this.configuration = configuration; + return this; + } + + /** + *

ID of the Detect configuration to use for de-identification.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage configurationId(String configurationId) { + this.configurationId = Optional.ofNullable(configurationId); + return this; + } + + /** + *

ID of the Detect configuration to use for de-identification.

+ */ + @java.lang.Override + @JsonSetter(value = "configurationId", nulls = Nulls.SKIP) + public _FinalStage configurationId(Optional configurationId) { + this.configurationId = configurationId; + return this; + } + + @java.lang.Override + public DeidentifyStringRequestV2 build() { + return new DeidentifyStringRequestV2(text, configurationId, configuration, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/requests/ReidentifyStringRequestV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/requests/ReidentifyStringRequestV2.java new file mode 100644 index 00000000..b242a87b --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/resources/stringsv2/requests/ReidentifyStringRequestV2.java @@ -0,0 +1,209 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.resources.stringsv2.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.types.RedactionLevel; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ReidentifyStringRequestV2.Builder.class) +public final class ReidentifyStringRequestV2 { + private final String vaultId; + + private final String text; + + private final Optional> redactionLevel; + + private final Map additionalProperties; + + private ReidentifyStringRequestV2( + String vaultId, + String text, + Optional> redactionLevel, + Map additionalProperties) { + this.vaultId = vaultId; + this.text = text; + this.redactionLevel = redactionLevel; + this.additionalProperties = additionalProperties; + } + + /** + * @return ID of the vault used for re-identification. Requires the Detect Invoker, Detect Manager, or Vault Owner role for this vault. Re-identifying vault_token values also requires a role or policy that separately grants the Detokenization action, unless the caller has the Vault Owner role. + */ + @JsonProperty("vaultId") + public String getVaultId() { + return vaultId; + } + + /** + * @return Text to reidentify. + */ + @JsonProperty("text") + public String getText() { + return text; + } + + /** + * @return Replacement patterns or types applied to entity types or token groups during re-identification. Specifies a replacement pattern/type and the token group or entity type it applies to during re-identification. Set exactly one of tokenGroupName or entityName. Use tokenGroupName for FlowVault and entityName for SkyVault. Similarly, set exactly one of redactionType or redactionPattern to specify the replacement. + */ + @JsonProperty("redactionLevel") + public Optional> getRedactionLevel() { + return redactionLevel; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ReidentifyStringRequestV2 && equalTo((ReidentifyStringRequestV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ReidentifyStringRequestV2 other) { + return vaultId.equals(other.vaultId) && text.equals(other.text) && redactionLevel.equals(other.redactionLevel); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.vaultId, this.text, this.redactionLevel); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static VaultIdStage builder() { + return new Builder(); + } + + public interface VaultIdStage { + /** + *

ID of the vault used for re-identification. Requires the Detect Invoker, Detect Manager, or Vault Owner role for this vault. Re-identifying vault_token values also requires a role or policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

+ */ + TextStage vaultId(@NotNull String vaultId); + + Builder from(ReidentifyStringRequestV2 other); + } + + public interface TextStage { + /** + *

Text to reidentify.

+ */ + _FinalStage text(@NotNull String text); + } + + public interface _FinalStage { + ReidentifyStringRequestV2 build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Replacement patterns or types applied to entity types or token groups during re-identification. Specifies a replacement pattern/type and the token group or entity type it applies to during re-identification. Set exactly one of tokenGroupName or entityName. Use tokenGroupName for FlowVault and entityName for SkyVault. Similarly, set exactly one of redactionType or redactionPattern to specify the replacement.

+ */ + _FinalStage redactionLevel(Optional> redactionLevel); + + _FinalStage redactionLevel(List redactionLevel); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements VaultIdStage, TextStage, _FinalStage { + private String vaultId; + + private String text; + + private Optional> redactionLevel = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(ReidentifyStringRequestV2 other) { + vaultId(other.getVaultId()); + text(other.getText()); + redactionLevel(other.getRedactionLevel()); + return this; + } + + /** + *

ID of the vault used for re-identification. Requires the Detect Invoker, Detect Manager, or Vault Owner role for this vault. Re-identifying vault_token values also requires a role or policy that separately grants the Detokenization action, unless the caller has the Vault Owner role.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("vaultId") + public TextStage vaultId(@NotNull String vaultId) { + this.vaultId = Objects.requireNonNull(vaultId, "vaultId must not be null"); + return this; + } + + /** + *

Text to reidentify.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("text") + public _FinalStage text(@NotNull String text) { + this.text = Objects.requireNonNull(text, "text must not be null"); + return this; + } + + /** + *

Replacement patterns or types applied to entity types or token groups during re-identification. Specifies a replacement pattern/type and the token group or entity type it applies to during re-identification. Set exactly one of tokenGroupName or entityName. Use tokenGroupName for FlowVault and entityName for SkyVault. Similarly, set exactly one of redactionType or redactionPattern to specify the replacement.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage redactionLevel(List redactionLevel) { + this.redactionLevel = Optional.ofNullable(redactionLevel); + return this; + } + + /** + *

Replacement patterns or types applied to entity types or token groups during re-identification. Specifies a replacement pattern/type and the token group or entity type it applies to during re-identification. Set exactly one of tokenGroupName or entityName. Use tokenGroupName for FlowVault and entityName for SkyVault. Similarly, set exactly one of redactionType or redactionPattern to specify the replacement.

+ */ + @java.lang.Override + @JsonSetter(value = "redactionLevel", nulls = Nulls.SKIP) + public _FinalStage redactionLevel(Optional> redactionLevel) { + this.redactionLevel = redactionLevel; + return this; + } + + @java.lang.Override + public ReidentifyStringRequestV2 build() { + return new ReidentifyStringRequestV2(vaultId, text, redactionLevel, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/AudioV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/AudioV2.java new file mode 100644 index 00000000..eb4be5de --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/AudioV2.java @@ -0,0 +1,167 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AudioV2.Builder.class) +public final class AudioV2 { + private final Optional outputProcessedAudio; + + private final Optional outputTranscription; + + private final Optional bleep; + + private final Map additionalProperties; + + private AudioV2( + Optional outputProcessedAudio, + Optional outputTranscription, + Optional bleep, + Map additionalProperties) { + this.outputProcessedAudio = outputProcessedAudio; + this.outputTranscription = outputTranscription; + this.bleep = bleep; + this.additionalProperties = additionalProperties; + } + + /** + * @return If true, includes processed audio file in the response. Can't be used together with outputTranscription. + */ + @JsonProperty("outputProcessedAudio") + public Optional getOutputProcessedAudio() { + return outputProcessedAudio; + } + + /** + * @return Type of transcription to include in the response. TRANSCRIPTION: standard transcript of the audio. MEDICAL_TRANSCRIPTION: transcript focused on medical content and terminology. DIARIZED_TRANSCRIPTION: speaker-separated transcript that labels each speaker's dialogue. MEDICAL_DIARIZED_TRANSCRIPTION: speaker-separated transcript focused on medical content and terminology. NONE: no transcription is included. Can't be used together with outputProcessedAudio. + */ + @JsonProperty("outputTranscription") + public Optional getOutputTranscription() { + return outputTranscription; + } + + @JsonProperty("bleep") + public Optional getBleep() { + return bleep; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AudioV2 && equalTo((AudioV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(AudioV2 other) { + return outputProcessedAudio.equals(other.outputProcessedAudio) + && outputTranscription.equals(other.outputTranscription) + && bleep.equals(other.bleep); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.outputProcessedAudio, this.outputTranscription, this.bleep); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional outputProcessedAudio = Optional.empty(); + + private Optional outputTranscription = Optional.empty(); + + private Optional bleep = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AudioV2 other) { + outputProcessedAudio(other.getOutputProcessedAudio()); + outputTranscription(other.getOutputTranscription()); + bleep(other.getBleep()); + return this; + } + + /** + *

If true, includes processed audio file in the response. Can't be used together with outputTranscription.

+ */ + @JsonSetter(value = "outputProcessedAudio", nulls = Nulls.SKIP) + public Builder outputProcessedAudio(Optional outputProcessedAudio) { + this.outputProcessedAudio = outputProcessedAudio; + return this; + } + + public Builder outputProcessedAudio(Boolean outputProcessedAudio) { + this.outputProcessedAudio = Optional.ofNullable(outputProcessedAudio); + return this; + } + + /** + *

Type of transcription to include in the response. TRANSCRIPTION: standard transcript of the audio. MEDICAL_TRANSCRIPTION: transcript focused on medical content and terminology. DIARIZED_TRANSCRIPTION: speaker-separated transcript that labels each speaker's dialogue. MEDICAL_DIARIZED_TRANSCRIPTION: speaker-separated transcript focused on medical content and terminology. NONE: no transcription is included. Can't be used together with outputProcessedAudio.

+ */ + @JsonSetter(value = "outputTranscription", nulls = Nulls.SKIP) + public Builder outputTranscription(Optional outputTranscription) { + this.outputTranscription = outputTranscription; + return this; + } + + public Builder outputTranscription(AudioV2OutputTranscription outputTranscription) { + this.outputTranscription = Optional.ofNullable(outputTranscription); + return this; + } + + @JsonSetter(value = "bleep", nulls = Nulls.SKIP) + public Builder bleep(Optional bleep) { + this.bleep = bleep; + return this; + } + + public Builder bleep(AudioV2Bleep bleep) { + this.bleep = Optional.ofNullable(bleep); + return this; + } + + public AudioV2 build() { + return new AudioV2(outputProcessedAudio, outputTranscription, bleep, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/AudioV2Bleep.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/AudioV2Bleep.java new file mode 100644 index 00000000..eb234230 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/AudioV2Bleep.java @@ -0,0 +1,203 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = AudioV2Bleep.Builder.class) +public final class AudioV2Bleep { + private final Optional startPadding; + + private final Optional stopPadding; + + private final Optional frequency; + + private final Optional gain; + + private final Map additionalProperties; + + private AudioV2Bleep( + Optional startPadding, + Optional stopPadding, + Optional frequency, + Optional gain, + Map additionalProperties) { + this.startPadding = startPadding; + this.stopPadding = stopPadding; + this.frequency = frequency; + this.gain = gain; + this.additionalProperties = additionalProperties; + } + + /** + * @return Padding added to the beginning of a bleep, in seconds. Range: 0–20. Defaults to 0.5. + */ + @JsonProperty("startPadding") + public Optional getStartPadding() { + return startPadding; + } + + /** + * @return Padding added to the end of a bleep, in seconds. Range: 0–20. Defaults to 0.2. + */ + @JsonProperty("stopPadding") + public Optional getStopPadding() { + return stopPadding; + } + + /** + * @return Frequency of the sine wave used for the bleep sound, in Hz. + */ + @JsonProperty("frequency") + public Optional getFrequency() { + return frequency; + } + + /** + * @return Relative loudness of the bleep. Positive values increase loudness and negative values decrease it. + */ + @JsonProperty("gain") + public Optional getGain() { + return gain; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof AudioV2Bleep && equalTo((AudioV2Bleep) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(AudioV2Bleep other) { + return startPadding.equals(other.startPadding) + && stopPadding.equals(other.stopPadding) + && frequency.equals(other.frequency) + && gain.equals(other.gain); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.startPadding, this.stopPadding, this.frequency, this.gain); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional startPadding = Optional.empty(); + + private Optional stopPadding = Optional.empty(); + + private Optional frequency = Optional.empty(); + + private Optional gain = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(AudioV2Bleep other) { + startPadding(other.getStartPadding()); + stopPadding(other.getStopPadding()); + frequency(other.getFrequency()); + gain(other.getGain()); + return this; + } + + /** + *

Padding added to the beginning of a bleep, in seconds. Range: 0–20. Defaults to 0.5.

+ */ + @JsonSetter(value = "startPadding", nulls = Nulls.SKIP) + public Builder startPadding(Optional startPadding) { + this.startPadding = startPadding; + return this; + } + + public Builder startPadding(Float startPadding) { + this.startPadding = Optional.ofNullable(startPadding); + return this; + } + + /** + *

Padding added to the end of a bleep, in seconds. Range: 0–20. Defaults to 0.2.

+ */ + @JsonSetter(value = "stopPadding", nulls = Nulls.SKIP) + public Builder stopPadding(Optional stopPadding) { + this.stopPadding = stopPadding; + return this; + } + + public Builder stopPadding(Float stopPadding) { + this.stopPadding = Optional.ofNullable(stopPadding); + return this; + } + + /** + *

Frequency of the sine wave used for the bleep sound, in Hz.

+ */ + @JsonSetter(value = "frequency", nulls = Nulls.SKIP) + public Builder frequency(Optional frequency) { + this.frequency = frequency; + return this; + } + + public Builder frequency(Integer frequency) { + this.frequency = Optional.ofNullable(frequency); + return this; + } + + /** + *

Relative loudness of the bleep. Positive values increase loudness and negative values decrease it.

+ */ + @JsonSetter(value = "gain", nulls = Nulls.SKIP) + public Builder gain(Optional gain) { + this.gain = gain; + return this; + } + + public Builder gain(Integer gain) { + this.gain = Optional.ofNullable(gain); + return this; + } + + public AudioV2Bleep build() { + return new AudioV2Bleep(startPadding, stopPadding, frequency, gain, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/AudioV2OutputTranscription.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/AudioV2OutputTranscription.java new file mode 100644 index 00000000..9d48255f --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/AudioV2OutputTranscription.java @@ -0,0 +1,118 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class AudioV2OutputTranscription { + public static final AudioV2OutputTranscription NONE = new AudioV2OutputTranscription(Value.NONE, "NONE"); + + public static final AudioV2OutputTranscription TRANSCRIPTION = + new AudioV2OutputTranscription(Value.TRANSCRIPTION, "TRANSCRIPTION"); + + public static final AudioV2OutputTranscription MEDICAL_TRANSCRIPTION = + new AudioV2OutputTranscription(Value.MEDICAL_TRANSCRIPTION, "MEDICAL_TRANSCRIPTION"); + + public static final AudioV2OutputTranscription DIARIZED_TRANSCRIPTION = + new AudioV2OutputTranscription(Value.DIARIZED_TRANSCRIPTION, "DIARIZED_TRANSCRIPTION"); + + public static final AudioV2OutputTranscription MEDICAL_DIARIZED_TRANSCRIPTION = + new AudioV2OutputTranscription(Value.MEDICAL_DIARIZED_TRANSCRIPTION, "MEDICAL_DIARIZED_TRANSCRIPTION"); + + private final Value value; + + private final String string; + + AudioV2OutputTranscription(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof AudioV2OutputTranscription + && this.string.equals(((AudioV2OutputTranscription) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case NONE: + return visitor.visitNone(); + case TRANSCRIPTION: + return visitor.visitTranscription(); + case MEDICAL_TRANSCRIPTION: + return visitor.visitMedicalTranscription(); + case DIARIZED_TRANSCRIPTION: + return visitor.visitDiarizedTranscription(); + case MEDICAL_DIARIZED_TRANSCRIPTION: + return visitor.visitMedicalDiarizedTranscription(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static AudioV2OutputTranscription valueOf(String value) { + switch (value) { + case "NONE": + return NONE; + case "TRANSCRIPTION": + return TRANSCRIPTION; + case "MEDICAL_TRANSCRIPTION": + return MEDICAL_TRANSCRIPTION; + case "DIARIZED_TRANSCRIPTION": + return DIARIZED_TRANSCRIPTION; + case "MEDICAL_DIARIZED_TRANSCRIPTION": + return MEDICAL_DIARIZED_TRANSCRIPTION; + default: + return new AudioV2OutputTranscription(Value.UNKNOWN, value); + } + } + + public enum Value { + NONE, + + TRANSCRIPTION, + + MEDICAL_TRANSCRIPTION, + + DIARIZED_TRANSCRIPTION, + + MEDICAL_DIARIZED_TRANSCRIPTION, + + UNKNOWN + } + + public interface Visitor { + T visitNone(); + + T visitTranscription(); + + T visitMedicalTranscription(); + + T visitDiarizedTranscription(); + + T visitMedicalDiarizedTranscription(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyFileResponseV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyFileResponseV2.java new file mode 100644 index 00000000..fa5b77eb --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyFileResponseV2.java @@ -0,0 +1,111 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DeidentifyFileResponseV2.Builder.class) +public final class DeidentifyFileResponseV2 { + private final Optional runId; + + private final Map additionalProperties; + + private DeidentifyFileResponseV2(Optional runId, Map additionalProperties) { + this.runId = runId; + this.additionalProperties = additionalProperties; + } + + /** + * @return Run ID of the deidentification request. + */ + @JsonProperty("runId") + public Optional getRunId() { + return runId; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DeidentifyFileResponseV2 && equalTo((DeidentifyFileResponseV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DeidentifyFileResponseV2 other) { + return runId.equals(other.runId); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.runId); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional runId = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(DeidentifyFileResponseV2 other) { + runId(other.getRunId()); + return this; + } + + /** + *

Run ID of the deidentification request.

+ */ + @JsonSetter(value = "runId", nulls = Nulls.SKIP) + public Builder runId(Optional runId) { + this.runId = runId; + return this; + } + + public Builder runId(String runId) { + this.runId = Optional.ofNullable(runId); + return this; + } + + public DeidentifyFileResponseV2 build() { + return new DeidentifyFileResponseV2(runId, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyStringResponseV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyStringResponseV2.java new file mode 100644 index 00000000..eb0eca2d --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyStringResponseV2.java @@ -0,0 +1,168 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DeidentifyStringResponseV2.Builder.class) +public final class DeidentifyStringResponseV2 { + private final Optional processedText; + + private final Optional> entities; + + private final Optional metrics; + + private final Map additionalProperties; + + private DeidentifyStringResponseV2( + Optional processedText, + Optional> entities, + Optional metrics, + Map additionalProperties) { + this.processedText = processedText; + this.entities = entities; + this.metrics = metrics; + this.additionalProperties = additionalProperties; + } + + /** + * @return Deidentified text with sensitive entities replaced. + */ + @JsonProperty("processedText") + public Optional getProcessedText() { + return processedText; + } + + /** + * @return Detected entities. + */ + @JsonProperty("entities") + public Optional> getEntities() { + return entities; + } + + @JsonProperty("metrics") + public Optional getMetrics() { + return metrics; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DeidentifyStringResponseV2 && equalTo((DeidentifyStringResponseV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DeidentifyStringResponseV2 other) { + return processedText.equals(other.processedText) + && entities.equals(other.entities) + && metrics.equals(other.metrics); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.processedText, this.entities, this.metrics); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional processedText = Optional.empty(); + + private Optional> entities = Optional.empty(); + + private Optional metrics = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(DeidentifyStringResponseV2 other) { + processedText(other.getProcessedText()); + entities(other.getEntities()); + metrics(other.getMetrics()); + return this; + } + + /** + *

Deidentified text with sensitive entities replaced.

+ */ + @JsonSetter(value = "processedText", nulls = Nulls.SKIP) + public Builder processedText(Optional processedText) { + this.processedText = processedText; + return this; + } + + public Builder processedText(String processedText) { + this.processedText = Optional.ofNullable(processedText); + return this; + } + + /** + *

Detected entities.

+ */ + @JsonSetter(value = "entities", nulls = Nulls.SKIP) + public Builder entities(Optional> entities) { + this.entities = entities; + return this; + } + + public Builder entities(List entities) { + this.entities = Optional.ofNullable(entities); + return this; + } + + @JsonSetter(value = "metrics", nulls = Nulls.SKIP) + public Builder metrics(Optional metrics) { + this.metrics = metrics; + return this; + } + + public Builder metrics(Metrics metrics) { + this.metrics = Optional.ofNullable(metrics); + return this; + } + + public DeidentifyStringResponseV2 build() { + return new DeidentifyStringResponseV2(processedText, entities, metrics, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyStringResponseV2DetectedEntity.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyStringResponseV2DetectedEntity.java new file mode 100644 index 00000000..e3bf15bd --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyStringResponseV2DetectedEntity.java @@ -0,0 +1,229 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DeidentifyStringResponseV2DetectedEntity.Builder.class) +public final class DeidentifyStringResponseV2DetectedEntity { + private final Optional token; + + private final Optional value; + + private final Optional location; + + private final Optional entityType; + + private final Optional> entityScores; + + private final Map additionalProperties; + + private DeidentifyStringResponseV2DetectedEntity( + Optional token, + Optional value, + Optional location, + Optional entityType, + Optional> entityScores, + Map additionalProperties) { + this.token = token; + this.value = value; + this.location = location; + this.entityType = entityType; + this.entityScores = entityScores; + this.additionalProperties = additionalProperties; + } + + /** + * @return Processed text of the entity. + */ + @JsonProperty("token") + public Optional getToken() { + return token; + } + + /** + * @return Original text of the entity. + */ + @JsonProperty("value") + public Optional getValue() { + return value; + } + + @JsonProperty("location") + public Optional getLocation() { + return location; + } + + /** + * @return Highest-rated label. + */ + @JsonProperty("entityType") + public Optional getEntityType() { + return entityType; + } + + /** + * @return Labels and their scores. + */ + @JsonProperty("entityScores") + public Optional> getEntityScores() { + return entityScores; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DeidentifyStringResponseV2DetectedEntity + && equalTo((DeidentifyStringResponseV2DetectedEntity) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DeidentifyStringResponseV2DetectedEntity other) { + return token.equals(other.token) + && value.equals(other.value) + && location.equals(other.location) + && entityType.equals(other.entityType) + && entityScores.equals(other.entityScores); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.token, this.value, this.location, this.entityType, this.entityScores); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional token = Optional.empty(); + + private Optional value = Optional.empty(); + + private Optional location = Optional.empty(); + + private Optional entityType = Optional.empty(); + + private Optional> entityScores = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(DeidentifyStringResponseV2DetectedEntity other) { + token(other.getToken()); + value(other.getValue()); + location(other.getLocation()); + entityType(other.getEntityType()); + entityScores(other.getEntityScores()); + return this; + } + + /** + *

Processed text of the entity.

+ */ + @JsonSetter(value = "token", nulls = Nulls.SKIP) + public Builder token(Optional token) { + this.token = token; + return this; + } + + public Builder token(String token) { + this.token = Optional.ofNullable(token); + return this; + } + + /** + *

Original text of the entity.

+ */ + @JsonSetter(value = "value", nulls = Nulls.SKIP) + public Builder value(Optional value) { + this.value = value; + return this; + } + + public Builder value(String value) { + this.value = Optional.ofNullable(value); + return this; + } + + @JsonSetter(value = "location", nulls = Nulls.SKIP) + public Builder location(Optional location) { + this.location = location; + return this; + } + + public Builder location(DeidentifyStringResponseV2EntityLocation location) { + this.location = Optional.ofNullable(location); + return this; + } + + /** + *

Highest-rated label.

+ */ + @JsonSetter(value = "entityType", nulls = Nulls.SKIP) + public Builder entityType(Optional entityType) { + this.entityType = entityType; + return this; + } + + public Builder entityType(String entityType) { + this.entityType = Optional.ofNullable(entityType); + return this; + } + + /** + *

Labels and their scores.

+ */ + @JsonSetter(value = "entityScores", nulls = Nulls.SKIP) + public Builder entityScores(Optional> entityScores) { + this.entityScores = entityScores; + return this; + } + + public Builder entityScores(Map entityScores) { + this.entityScores = Optional.ofNullable(entityScores); + return this; + } + + public DeidentifyStringResponseV2DetectedEntity build() { + return new DeidentifyStringResponseV2DetectedEntity( + token, value, location, entityType, entityScores, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyStringResponseV2EntityLocation.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyStringResponseV2EntityLocation.java new file mode 100644 index 00000000..6ac6a633 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DeidentifyStringResponseV2EntityLocation.java @@ -0,0 +1,205 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DeidentifyStringResponseV2EntityLocation.Builder.class) +public final class DeidentifyStringResponseV2EntityLocation { + private final Optional startIndex; + + private final Optional endIndex; + + private final Optional startIndexProcessed; + + private final Optional endIndexProcessed; + + private final Map additionalProperties; + + private DeidentifyStringResponseV2EntityLocation( + Optional startIndex, + Optional endIndex, + Optional startIndexProcessed, + Optional endIndexProcessed, + Map additionalProperties) { + this.startIndex = startIndex; + this.endIndex = endIndex; + this.startIndexProcessed = startIndexProcessed; + this.endIndexProcessed = endIndexProcessed; + this.additionalProperties = additionalProperties; + } + + /** + * @return Index of the first character of the string in the original text. + */ + @JsonProperty("startIndex") + public Optional getStartIndex() { + return startIndex; + } + + /** + * @return Index of the last character of the string in the original text. + */ + @JsonProperty("endIndex") + public Optional getEndIndex() { + return endIndex; + } + + /** + * @return Index of the first character of the string in the processed text. + */ + @JsonProperty("startIndexProcessed") + public Optional getStartIndexProcessed() { + return startIndexProcessed; + } + + /** + * @return Index of the last character of the string in the processed text. + */ + @JsonProperty("endIndexProcessed") + public Optional getEndIndexProcessed() { + return endIndexProcessed; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DeidentifyStringResponseV2EntityLocation + && equalTo((DeidentifyStringResponseV2EntityLocation) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DeidentifyStringResponseV2EntityLocation other) { + return startIndex.equals(other.startIndex) + && endIndex.equals(other.endIndex) + && startIndexProcessed.equals(other.startIndexProcessed) + && endIndexProcessed.equals(other.endIndexProcessed); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.startIndex, this.endIndex, this.startIndexProcessed, this.endIndexProcessed); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional startIndex = Optional.empty(); + + private Optional endIndex = Optional.empty(); + + private Optional startIndexProcessed = Optional.empty(); + + private Optional endIndexProcessed = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(DeidentifyStringResponseV2EntityLocation other) { + startIndex(other.getStartIndex()); + endIndex(other.getEndIndex()); + startIndexProcessed(other.getStartIndexProcessed()); + endIndexProcessed(other.getEndIndexProcessed()); + return this; + } + + /** + *

Index of the first character of the string in the original text.

+ */ + @JsonSetter(value = "startIndex", nulls = Nulls.SKIP) + public Builder startIndex(Optional startIndex) { + this.startIndex = startIndex; + return this; + } + + public Builder startIndex(Integer startIndex) { + this.startIndex = Optional.ofNullable(startIndex); + return this; + } + + /** + *

Index of the last character of the string in the original text.

+ */ + @JsonSetter(value = "endIndex", nulls = Nulls.SKIP) + public Builder endIndex(Optional endIndex) { + this.endIndex = endIndex; + return this; + } + + public Builder endIndex(Integer endIndex) { + this.endIndex = Optional.ofNullable(endIndex); + return this; + } + + /** + *

Index of the first character of the string in the processed text.

+ */ + @JsonSetter(value = "startIndexProcessed", nulls = Nulls.SKIP) + public Builder startIndexProcessed(Optional startIndexProcessed) { + this.startIndexProcessed = startIndexProcessed; + return this; + } + + public Builder startIndexProcessed(Integer startIndexProcessed) { + this.startIndexProcessed = Optional.ofNullable(startIndexProcessed); + return this; + } + + /** + *

Index of the last character of the string in the processed text.

+ */ + @JsonSetter(value = "endIndexProcessed", nulls = Nulls.SKIP) + public Builder endIndexProcessed(Optional endIndexProcessed) { + this.endIndexProcessed = endIndexProcessed; + return this; + } + + public Builder endIndexProcessed(Integer endIndexProcessed) { + this.endIndexProcessed = Optional.ofNullable(endIndexProcessed); + return this; + } + + public DeidentifyStringResponseV2EntityLocation build() { + return new DeidentifyStringResponseV2EntityLocation( + startIndex, endIndex, startIndexProcessed, endIndexProcessed, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/Detect.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/Detect.java new file mode 100644 index 00000000..4935b1d0 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/Detect.java @@ -0,0 +1,266 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = Detect.Builder.class) +public final class Detect { + private final Optional> entities; + + private final Optional> objectEntities; + + private final Optional> restrict; + + private final Optional> skip; + + private final Optional> customTypes; + + private final Optional returnEntities; + + private final Map additionalProperties; + + private Detect( + Optional> entities, + Optional> objectEntities, + Optional> restrict, + Optional> skip, + Optional> customTypes, + Optional returnEntities, + Map additionalProperties) { + this.entities = entities; + this.objectEntities = objectEntities; + this.restrict = restrict; + this.skip = skip; + this.customTypes = customTypes; + this.returnEntities = returnEntities; + this.additionalProperties = additionalProperties; + } + + /** + * @return Entities and their deidentification settings. + */ + @JsonProperty("entities") + public Optional> getEntities() { + return entities; + } + + /** + * @return Object entities and their deidentification settings. + */ + @JsonProperty("objectEntities") + public Optional> getObjectEntities() { + return objectEntities; + } + + /** + * @return Settings for restricting detected entities. A detected entity is replaced with '[RESTRICTED]' only when a restrict pattern matches the entity's full value. If a pattern matches only a substring of the detected entity, the pattern is ignored and the entity is processed according to the configured tokenization and transformation settings. Patterns don't match across entity boundaries. If an entity matches both a skip and a restrict pattern, skip takes precedence and the entity is displayed in plaintext. Note: Vault tokens are currently not supported. + */ + @JsonProperty("restrict") + public Optional> getRestrict() { + return restrict; + } + + /** + * @return Settings for bypassing deidentification on detected entities. A detected entity is displayed in plaintext when a skip pattern matches either the entity's full value or any substring of it. Patterns don't match across entity boundaries. If an entity matches both a skip and a restrict pattern, skip takes precedence and the entity is displayed in plaintext. + */ + @JsonProperty("skip") + public Optional> getSkip() { + return skip; + } + + /** + * @return Custom entity type definitions. + */ + @JsonProperty("customTypes") + public Optional> getCustomTypes() { + return customTypes; + } + + /** + * @return Controls which detected entities are returned in the response. EXCLUDE_SENSITIVE_DATA: returns each detected entity's metadata (token, entity type, location, and confidence scores) but omits the original sensitive value. ALL: returns each detected entity's metadata and the original sensitive value. NONE: doesn't return detected entities. + */ + @JsonProperty("returnEntities") + public Optional getReturnEntities() { + return returnEntities; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Detect && equalTo((Detect) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(Detect other) { + return entities.equals(other.entities) + && objectEntities.equals(other.objectEntities) + && restrict.equals(other.restrict) + && skip.equals(other.skip) + && customTypes.equals(other.customTypes) + && returnEntities.equals(other.returnEntities); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash( + this.entities, this.objectEntities, this.restrict, this.skip, this.customTypes, this.returnEntities); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional> entities = Optional.empty(); + + private Optional> objectEntities = Optional.empty(); + + private Optional> restrict = Optional.empty(); + + private Optional> skip = Optional.empty(); + + private Optional> customTypes = Optional.empty(); + + private Optional returnEntities = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(Detect other) { + entities(other.getEntities()); + objectEntities(other.getObjectEntities()); + restrict(other.getRestrict()); + skip(other.getSkip()); + customTypes(other.getCustomTypes()); + returnEntities(other.getReturnEntities()); + return this; + } + + /** + *

Entities and their deidentification settings.

+ */ + @JsonSetter(value = "entities", nulls = Nulls.SKIP) + public Builder entities(Optional> entities) { + this.entities = entities; + return this; + } + + public Builder entities(List entities) { + this.entities = Optional.ofNullable(entities); + return this; + } + + /** + *

Object entities and their deidentification settings.

+ */ + @JsonSetter(value = "objectEntities", nulls = Nulls.SKIP) + public Builder objectEntities(Optional> objectEntities) { + this.objectEntities = objectEntities; + return this; + } + + public Builder objectEntities(List objectEntities) { + this.objectEntities = Optional.ofNullable(objectEntities); + return this; + } + + /** + *

Settings for restricting detected entities. A detected entity is replaced with '[RESTRICTED]' only when a restrict pattern matches the entity's full value. If a pattern matches only a substring of the detected entity, the pattern is ignored and the entity is processed according to the configured tokenization and transformation settings. Patterns don't match across entity boundaries. If an entity matches both a skip and a restrict pattern, skip takes precedence and the entity is displayed in plaintext. Note: Vault tokens are currently not supported.

+ */ + @JsonSetter(value = "restrict", nulls = Nulls.SKIP) + public Builder restrict(Optional> restrict) { + this.restrict = restrict; + return this; + } + + public Builder restrict(List restrict) { + this.restrict = Optional.ofNullable(restrict); + return this; + } + + /** + *

Settings for bypassing deidentification on detected entities. A detected entity is displayed in plaintext when a skip pattern matches either the entity's full value or any substring of it. Patterns don't match across entity boundaries. If an entity matches both a skip and a restrict pattern, skip takes precedence and the entity is displayed in plaintext.

+ */ + @JsonSetter(value = "skip", nulls = Nulls.SKIP) + public Builder skip(Optional> skip) { + this.skip = skip; + return this; + } + + public Builder skip(List skip) { + this.skip = Optional.ofNullable(skip); + return this; + } + + /** + *

Custom entity type definitions.

+ */ + @JsonSetter(value = "customTypes", nulls = Nulls.SKIP) + public Builder customTypes(Optional> customTypes) { + this.customTypes = customTypes; + return this; + } + + public Builder customTypes(List customTypes) { + this.customTypes = Optional.ofNullable(customTypes); + return this; + } + + /** + *

Controls which detected entities are returned in the response. EXCLUDE_SENSITIVE_DATA: returns each detected entity's metadata (token, entity type, location, and confidence scores) but omits the original sensitive value. ALL: returns each detected entity's metadata and the original sensitive value. NONE: doesn't return detected entities.

+ */ + @JsonSetter(value = "returnEntities", nulls = Nulls.SKIP) + public Builder returnEntities(Optional returnEntities) { + this.returnEntities = returnEntities; + return this; + } + + public Builder returnEntities(DetectReturnEntities returnEntities) { + this.returnEntities = Optional.ofNullable(returnEntities); + return this; + } + + public Detect build() { + return new Detect( + entities, objectEntities, restrict, skip, customTypes, returnEntities, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectConfigV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectConfigV2.java new file mode 100644 index 00000000..77f00c81 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectConfigV2.java @@ -0,0 +1,470 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DetectConfigV2.Builder.class) +public final class DetectConfigV2 { + private final Optional id; + + private final Optional name; + + private final Optional namespace; + + private final Optional description; + + private final String vaultId; + + private final Optional vaultType; + + private final Optional detect; + + private final Optional> fileMapping; + + private final Optional media; + + private final Map additionalProperties; + + private DetectConfigV2( + Optional id, + Optional name, + Optional namespace, + Optional description, + String vaultId, + Optional vaultType, + Optional detect, + Optional> fileMapping, + Optional media, + Map additionalProperties) { + this.id = id; + this.name = name; + this.namespace = namespace; + this.description = description; + this.vaultId = vaultId; + this.vaultType = vaultType; + this.detect = detect; + this.fileMapping = fileMapping; + this.media = media; + this.additionalProperties = additionalProperties; + } + + /** + * @return ID of the configuration. + */ + @JsonProperty("ID") + public Optional getId() { + return id; + } + + /** + * @return Name of the configuration. + */ + @JsonProperty("name") + public Optional getName() { + return name; + } + + /** + * @return Namespace of the configuration. + */ + @JsonProperty("namespace") + public Optional getNamespace() { + return namespace; + } + + /** + * @return Description of the configuration. + */ + @JsonProperty("description") + public Optional getDescription() { + return description; + } + + /** + * @return ID of the vault. + */ + @JsonProperty("vaultId") + public String getVaultId() { + return vaultId; + } + + /** + * @return Type of the vault. + */ + @JsonProperty("vaultType") + public Optional getVaultType() { + return vaultType; + } + + @JsonProperty("detect") + public Optional getDetect() { + return detect; + } + + /** + * @return Mappings for source and deidentified file locations. + */ + @JsonProperty("fileMapping") + public Optional> getFileMapping() { + return fileMapping; + } + + @JsonProperty("media") + public Optional getMedia() { + return media; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetectConfigV2 && equalTo((DetectConfigV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DetectConfigV2 other) { + return id.equals(other.id) + && name.equals(other.name) + && namespace.equals(other.namespace) + && description.equals(other.description) + && vaultId.equals(other.vaultId) + && vaultType.equals(other.vaultType) + && detect.equals(other.detect) + && fileMapping.equals(other.fileMapping) + && media.equals(other.media); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash( + this.id, + this.name, + this.namespace, + this.description, + this.vaultId, + this.vaultType, + this.detect, + this.fileMapping, + this.media); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static VaultIdStage builder() { + return new Builder(); + } + + public interface VaultIdStage { + /** + *

ID of the vault.

+ */ + _FinalStage vaultId(@NotNull String vaultId); + + Builder from(DetectConfigV2 other); + } + + public interface _FinalStage { + DetectConfigV2 build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

ID of the configuration.

+ */ + _FinalStage id(Optional id); + + _FinalStage id(String id); + + /** + *

Name of the configuration.

+ */ + _FinalStage name(Optional name); + + _FinalStage name(String name); + + /** + *

Namespace of the configuration.

+ */ + _FinalStage namespace(Optional namespace); + + _FinalStage namespace(String namespace); + + /** + *

Description of the configuration.

+ */ + _FinalStage description(Optional description); + + _FinalStage description(String description); + + /** + *

Type of the vault.

+ */ + _FinalStage vaultType(Optional vaultType); + + _FinalStage vaultType(DetectConfigV2VaultType vaultType); + + _FinalStage detect(Optional detect); + + _FinalStage detect(Detect detect); + + /** + *

Mappings for source and deidentified file locations.

+ */ + _FinalStage fileMapping(Optional> fileMapping); + + _FinalStage fileMapping(List fileMapping); + + _FinalStage media(Optional media); + + _FinalStage media(Media media); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements VaultIdStage, _FinalStage { + private String vaultId; + + private Optional media = Optional.empty(); + + private Optional> fileMapping = Optional.empty(); + + private Optional detect = Optional.empty(); + + private Optional vaultType = Optional.empty(); + + private Optional description = Optional.empty(); + + private Optional namespace = Optional.empty(); + + private Optional name = Optional.empty(); + + private Optional id = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(DetectConfigV2 other) { + id(other.getId()); + name(other.getName()); + namespace(other.getNamespace()); + description(other.getDescription()); + vaultId(other.getVaultId()); + vaultType(other.getVaultType()); + detect(other.getDetect()); + fileMapping(other.getFileMapping()); + media(other.getMedia()); + return this; + } + + /** + *

ID of the vault.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("vaultId") + public _FinalStage vaultId(@NotNull String vaultId) { + this.vaultId = Objects.requireNonNull(vaultId, "vaultId must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage media(Media media) { + this.media = Optional.ofNullable(media); + return this; + } + + @java.lang.Override + @JsonSetter(value = "media", nulls = Nulls.SKIP) + public _FinalStage media(Optional media) { + this.media = media; + return this; + } + + /** + *

Mappings for source and deidentified file locations.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage fileMapping(List fileMapping) { + this.fileMapping = Optional.ofNullable(fileMapping); + return this; + } + + /** + *

Mappings for source and deidentified file locations.

+ */ + @java.lang.Override + @JsonSetter(value = "fileMapping", nulls = Nulls.SKIP) + public _FinalStage fileMapping(Optional> fileMapping) { + this.fileMapping = fileMapping; + return this; + } + + @java.lang.Override + public _FinalStage detect(Detect detect) { + this.detect = Optional.ofNullable(detect); + return this; + } + + @java.lang.Override + @JsonSetter(value = "detect", nulls = Nulls.SKIP) + public _FinalStage detect(Optional detect) { + this.detect = detect; + return this; + } + + /** + *

Type of the vault.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage vaultType(DetectConfigV2VaultType vaultType) { + this.vaultType = Optional.ofNullable(vaultType); + return this; + } + + /** + *

Type of the vault.

+ */ + @java.lang.Override + @JsonSetter(value = "vaultType", nulls = Nulls.SKIP) + public _FinalStage vaultType(Optional vaultType) { + this.vaultType = vaultType; + return this; + } + + /** + *

Description of the configuration.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage description(String description) { + this.description = Optional.ofNullable(description); + return this; + } + + /** + *

Description of the configuration.

+ */ + @java.lang.Override + @JsonSetter(value = "description", nulls = Nulls.SKIP) + public _FinalStage description(Optional description) { + this.description = description; + return this; + } + + /** + *

Namespace of the configuration.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage namespace(String namespace) { + this.namespace = Optional.ofNullable(namespace); + return this; + } + + /** + *

Namespace of the configuration.

+ */ + @java.lang.Override + @JsonSetter(value = "namespace", nulls = Nulls.SKIP) + public _FinalStage namespace(Optional namespace) { + this.namespace = namespace; + return this; + } + + /** + *

Name of the configuration.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage name(String name) { + this.name = Optional.ofNullable(name); + return this; + } + + /** + *

Name of the configuration.

+ */ + @java.lang.Override + @JsonSetter(value = "name", nulls = Nulls.SKIP) + public _FinalStage name(Optional name) { + this.name = name; + return this; + } + + /** + *

ID of the configuration.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage id(String id) { + this.id = Optional.ofNullable(id); + return this; + } + + /** + *

ID of the configuration.

+ */ + @java.lang.Override + @JsonSetter(value = "ID", nulls = Nulls.SKIP) + public _FinalStage id(Optional id) { + this.id = id; + return this; + } + + @java.lang.Override + public DetectConfigV2 build() { + return new DetectConfigV2( + id, + name, + namespace, + description, + vaultId, + vaultType, + detect, + fileMapping, + media, + additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectConfigV2VaultType.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectConfigV2VaultType.java new file mode 100644 index 00000000..4249b6a3 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectConfigV2VaultType.java @@ -0,0 +1,105 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DetectConfigV2VaultType { + public static final DetectConfigV2VaultType PRIVACYDB = new DetectConfigV2VaultType(Value.PRIVACYDB, "PRIVACYDB"); + + public static final DetectConfigV2VaultType FLOWDB = new DetectConfigV2VaultType(Value.FLOWDB, "FLOWDB"); + + public static final DetectConfigV2VaultType NONE = new DetectConfigV2VaultType(Value.NONE, "NONE"); + + public static final DetectConfigV2VaultType FLOWDB_SCHEMALESS = + new DetectConfigV2VaultType(Value.FLOWDB_SCHEMALESS, "FLOWDB_SCHEMALESS"); + + private final Value value; + + private final String string; + + DetectConfigV2VaultType(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DetectConfigV2VaultType + && this.string.equals(((DetectConfigV2VaultType) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case PRIVACYDB: + return visitor.visitPrivacydb(); + case FLOWDB: + return visitor.visitFlowdb(); + case NONE: + return visitor.visitNone(); + case FLOWDB_SCHEMALESS: + return visitor.visitFlowdbSchemaless(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DetectConfigV2VaultType valueOf(String value) { + switch (value) { + case "PRIVACYDB": + return PRIVACYDB; + case "FLOWDB": + return FLOWDB; + case "NONE": + return NONE; + case "FLOWDB_SCHEMALESS": + return FLOWDB_SCHEMALESS; + default: + return new DetectConfigV2VaultType(Value.UNKNOWN, value); + } + } + + public enum Value { + NONE, + + PRIVACYDB, + + FLOWDB, + + FLOWDB_SCHEMALESS, + + UNKNOWN + } + + public interface Visitor { + T visitNone(); + + T visitPrivacydb(); + + T visitFlowdb(); + + T visitFlowdbSchemaless(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectCustomTypes.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectCustomTypes.java new file mode 100644 index 00000000..a4db8e7c --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectCustomTypes.java @@ -0,0 +1,141 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DetectCustomTypes.Builder.class) +public final class DetectCustomTypes { + private final Optional label; + + private final Optional> match; + + private final Map additionalProperties; + + private DetectCustomTypes( + Optional label, Optional> match, Map additionalProperties) { + this.label = label; + this.match = match; + this.additionalProperties = additionalProperties; + } + + /** + * @return Label of the custom type. Note: Vault tokens are currently not supported. + */ + @JsonProperty("label") + public Optional getLabel() { + return label; + } + + /** + * @return Regular expressions to replace with '[<customType.label>]' enclosed in square brackets. Expressions must match the entirety of a detected entity, not just a substring, for the entity to be redacted. Expressions don't match across entity boundaries. If a string or entity matches both skip and customType.label, the entity is displayed in plaintext. If a string is detected as an entity and a customType.match pattern matches the entire detected entity, the entity is replaced with '[<customType.label>]'. If a string is detected as an entity but a customType.match pattern only matches a substring of it, the customType.match pattern is ignored, and the entity is processed according to the specified tokenization and transformation settings. + */ + @JsonProperty("match") + public Optional> getMatch() { + return match; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetectCustomTypes && equalTo((DetectCustomTypes) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DetectCustomTypes other) { + return label.equals(other.label) && match.equals(other.match); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.label, this.match); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional label = Optional.empty(); + + private Optional> match = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(DetectCustomTypes other) { + label(other.getLabel()); + match(other.getMatch()); + return this; + } + + /** + *

Label of the custom type. Note: Vault tokens are currently not supported.

+ */ + @JsonSetter(value = "label", nulls = Nulls.SKIP) + public Builder label(Optional label) { + this.label = label; + return this; + } + + public Builder label(String label) { + this.label = Optional.ofNullable(label); + return this; + } + + /** + *

Regular expressions to replace with '[<customType.label>]' enclosed in square brackets. Expressions must match the entirety of a detected entity, not just a substring, for the entity to be redacted. Expressions don't match across entity boundaries. If a string or entity matches both skip and customType.label, the entity is displayed in plaintext. If a string is detected as an entity and a customType.match pattern matches the entire detected entity, the entity is replaced with '[<customType.label>]'. If a string is detected as an entity but a customType.match pattern only matches a substring of it, the customType.match pattern is ignored, and the entity is processed according to the specified tokenization and transformation settings.

+ */ + @JsonSetter(value = "match", nulls = Nulls.SKIP) + public Builder match(Optional> match) { + this.match = match; + return this; + } + + public Builder match(List match) { + this.match = Optional.ofNullable(match); + return this; + } + + public DetectCustomTypes build() { + return new DetectCustomTypes(label, match, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectEntity.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectEntity.java new file mode 100644 index 00000000..472e7044 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectEntity.java @@ -0,0 +1,198 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DetectEntity.Builder.class) +public final class DetectEntity { + private final Optional entityType; + + private final Optional deidentificationType; + + private final Optional destination; + + private final Optional transformation; + + private final Map additionalProperties; + + private DetectEntity( + Optional entityType, + Optional deidentificationType, + Optional destination, + Optional transformation, + Map additionalProperties) { + this.entityType = entityType; + this.deidentificationType = deidentificationType; + this.destination = destination; + this.transformation = transformation; + this.additionalProperties = additionalProperties; + } + + /** + * @return Type of entity to detect. + */ + @JsonProperty("entityType") + public Optional getEntityType() { + return entityType; + } + + /** + * @return Type of deidentification to apply. VAULT_TOKEN is only supported for the following file formats: txt, json, csv, xml, jsonl, docx, xlsx, pptx. + */ + @JsonProperty("deidentificationType") + public Optional getDeidentificationType() { + return deidentificationType; + } + + /** + * @return Vault destination where detected values of this entity type are stored. For vaults with a schema, specify the table and column in table.column format (for example, persons.name). For schemaless vaults, specify the token group name (for example, personTokenGroup). + */ + @JsonProperty("destination") + public Optional getDestination() { + return destination; + } + + @JsonProperty("transformation") + public Optional getTransformation() { + return transformation; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetectEntity && equalTo((DetectEntity) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DetectEntity other) { + return entityType.equals(other.entityType) + && deidentificationType.equals(other.deidentificationType) + && destination.equals(other.destination) + && transformation.equals(other.transformation); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.entityType, this.deidentificationType, this.destination, this.transformation); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional entityType = Optional.empty(); + + private Optional deidentificationType = Optional.empty(); + + private Optional destination = Optional.empty(); + + private Optional transformation = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(DetectEntity other) { + entityType(other.getEntityType()); + deidentificationType(other.getDeidentificationType()); + destination(other.getDestination()); + transformation(other.getTransformation()); + return this; + } + + /** + *

Type of entity to detect.

+ */ + @JsonSetter(value = "entityType", nulls = Nulls.SKIP) + public Builder entityType(Optional entityType) { + this.entityType = entityType; + return this; + } + + public Builder entityType(DetectEntityEntityType entityType) { + this.entityType = Optional.ofNullable(entityType); + return this; + } + + /** + *

Type of deidentification to apply. VAULT_TOKEN is only supported for the following file formats: txt, json, csv, xml, jsonl, docx, xlsx, pptx.

+ */ + @JsonSetter(value = "deidentificationType", nulls = Nulls.SKIP) + public Builder deidentificationType(Optional deidentificationType) { + this.deidentificationType = deidentificationType; + return this; + } + + public Builder deidentificationType(DetectEntityDeidentificationType deidentificationType) { + this.deidentificationType = Optional.ofNullable(deidentificationType); + return this; + } + + /** + *

Vault destination where detected values of this entity type are stored. For vaults with a schema, specify the table and column in table.column format (for example, persons.name). For schemaless vaults, specify the token group name (for example, personTokenGroup).

+ */ + @JsonSetter(value = "destination", nulls = Nulls.SKIP) + public Builder destination(Optional destination) { + this.destination = destination; + return this; + } + + public Builder destination(String destination) { + this.destination = Optional.ofNullable(destination); + return this; + } + + @JsonSetter(value = "transformation", nulls = Nulls.SKIP) + public Builder transformation(Optional transformation) { + this.transformation = transformation; + return this; + } + + public Builder transformation(EntityTransformation transformation) { + this.transformation = Optional.ofNullable(transformation); + return this; + } + + public DetectEntity build() { + return new DetectEntity( + entityType, deidentificationType, destination, transformation, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectEntityDeidentificationType.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectEntityDeidentificationType.java new file mode 100644 index 00000000..26262dce --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectEntityDeidentificationType.java @@ -0,0 +1,119 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DetectEntityDeidentificationType { + public static final DetectEntityDeidentificationType MASK = + new DetectEntityDeidentificationType(Value.MASK, "MASK"); + + public static final DetectEntityDeidentificationType ENTITY_ONLY = + new DetectEntityDeidentificationType(Value.ENTITY_ONLY, "ENTITY_ONLY"); + + public static final DetectEntityDeidentificationType VAULT_TOKEN = + new DetectEntityDeidentificationType(Value.VAULT_TOKEN, "VAULT_TOKEN"); + + public static final DetectEntityDeidentificationType UNKNOWN = + new DetectEntityDeidentificationType(Value.UNKNOWN, "UNKNOWN"); + + public static final DetectEntityDeidentificationType ENTITY_UNIQUE_COUNTER = + new DetectEntityDeidentificationType(Value.ENTITY_UNIQUE_COUNTER, "ENTITY_UNIQUE_COUNTER"); + + private final Value value; + + private final String string; + + DetectEntityDeidentificationType(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DetectEntityDeidentificationType + && this.string.equals(((DetectEntityDeidentificationType) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case MASK: + return visitor.visitMask(); + case ENTITY_ONLY: + return visitor.visitEntityOnly(); + case VAULT_TOKEN: + return visitor.visitVaultToken(); + case UNKNOWN: + return visitor.visitUnknown(); + case ENTITY_UNIQUE_COUNTER: + return visitor.visitEntityUniqueCounter(); + case _UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DetectEntityDeidentificationType valueOf(String value) { + switch (value) { + case "MASK": + return MASK; + case "ENTITY_ONLY": + return ENTITY_ONLY; + case "VAULT_TOKEN": + return VAULT_TOKEN; + case "UNKNOWN": + return UNKNOWN; + case "ENTITY_UNIQUE_COUNTER": + return ENTITY_UNIQUE_COUNTER; + default: + return new DetectEntityDeidentificationType(Value._UNKNOWN, value); + } + } + + public enum Value { + UNKNOWN, + + ENTITY_UNIQUE_COUNTER, + + ENTITY_ONLY, + + VAULT_TOKEN, + + MASK, + + _UNKNOWN + } + + public interface Visitor { + T visitUnknown(); + + T visitEntityUniqueCounter(); + + T visitEntityOnly(); + + T visitVaultToken(); + + T visitMask(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectEntityEntityType.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectEntityEntityType.java new file mode 100644 index 00000000..7e286cfc --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectEntityEntityType.java @@ -0,0 +1,806 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DetectEntityEntityType { + public static final DetectEntityEntityType PHONE_NUMBER = + new DetectEntityEntityType(Value.PHONE_NUMBER, "PHONE_NUMBER"); + + public static final DetectEntityEntityType USERNAME = new DetectEntityEntityType(Value.USERNAME, "USERNAME"); + + public static final DetectEntityEntityType VEHICLE_ID = new DetectEntityEntityType(Value.VEHICLE_ID, "VEHICLE_ID"); + + public static final DetectEntityEntityType RELIGION = new DetectEntityEntityType(Value.RELIGION, "RELIGION"); + + public static final DetectEntityEntityType ORIGIN = new DetectEntityEntityType(Value.ORIGIN, "ORIGIN"); + + public static final DetectEntityEntityType PASSPORT_NUMBER = + new DetectEntityEntityType(Value.PASSPORT_NUMBER, "PASSPORT_NUMBER"); + + public static final DetectEntityEntityType YEAR = new DetectEntityEntityType(Value.YEAR, "YEAR"); + + public static final DetectEntityEntityType CONDITION = new DetectEntityEntityType(Value.CONDITION, "CONDITION"); + + public static final DetectEntityEntityType LOCATION = new DetectEntityEntityType(Value.LOCATION, "LOCATION"); + + public static final DetectEntityEntityType LOCATION_CITY = + new DetectEntityEntityType(Value.LOCATION_CITY, "LOCATION_CITY"); + + public static final DetectEntityEntityType LOCATION_COUNTRY = + new DetectEntityEntityType(Value.LOCATION_COUNTRY, "LOCATION_COUNTRY"); + + public static final DetectEntityEntityType PROJECT = new DetectEntityEntityType(Value.PROJECT, "PROJECT"); + + public static final DetectEntityEntityType LOCATION_ZIP = + new DetectEntityEntityType(Value.LOCATION_ZIP, "LOCATION_ZIP"); + + public static final DetectEntityEntityType NAME_MEDICAL_PROFESSIONAL = + new DetectEntityEntityType(Value.NAME_MEDICAL_PROFESSIONAL, "NAME_MEDICAL_PROFESSIONAL"); + + public static final DetectEntityEntityType BANK_ACCOUNT = + new DetectEntityEntityType(Value.BANK_ACCOUNT, "BANK_ACCOUNT"); + + public static final DetectEntityEntityType OCCUPATION = new DetectEntityEntityType(Value.OCCUPATION, "OCCUPATION"); + + public static final DetectEntityEntityType TIME = new DetectEntityEntityType(Value.TIME, "TIME"); + + public static final DetectEntityEntityType LOCATION_STATE = + new DetectEntityEntityType(Value.LOCATION_STATE, "LOCATION_STATE"); + + public static final DetectEntityEntityType EVENT = new DetectEntityEntityType(Value.EVENT, "EVENT"); + + public static final DetectEntityEntityType POLITICAL_AFFILIATION = + new DetectEntityEntityType(Value.POLITICAL_AFFILIATION, "POLITICAL_AFFILIATION"); + + public static final DetectEntityEntityType LANGUAGE = new DetectEntityEntityType(Value.LANGUAGE, "LANGUAGE"); + + public static final DetectEntityEntityType ZODIAC_SIGN = + new DetectEntityEntityType(Value.ZODIAC_SIGN, "ZODIAC_SIGN"); + + public static final DetectEntityEntityType PRODUCT = new DetectEntityEntityType(Value.PRODUCT, "PRODUCT"); + + public static final DetectEntityEntityType SEXUALITY = new DetectEntityEntityType(Value.SEXUALITY, "SEXUALITY"); + + public static final DetectEntityEntityType DURATION = new DetectEntityEntityType(Value.DURATION, "DURATION"); + + public static final DetectEntityEntityType PHYSICAL_ATTRIBUTE = + new DetectEntityEntityType(Value.PHYSICAL_ATTRIBUTE, "PHYSICAL_ATTRIBUTE"); + + public static final DetectEntityEntityType LOCATION_ADDRESS_STREET = + new DetectEntityEntityType(Value.LOCATION_ADDRESS_STREET, "LOCATION_ADDRESS_STREET"); + + public static final DetectEntityEntityType MEDICAL_PROCESS = + new DetectEntityEntityType(Value.MEDICAL_PROCESS, "MEDICAL_PROCESS"); + + public static final DetectEntityEntityType LOCATION_ADDRESS = + new DetectEntityEntityType(Value.LOCATION_ADDRESS, "LOCATION_ADDRESS"); + + public static final DetectEntityEntityType FILENAME = new DetectEntityEntityType(Value.FILENAME, "FILENAME"); + + public static final DetectEntityEntityType DOB = new DetectEntityEntityType(Value.DOB, "DOB"); + + public static final DetectEntityEntityType ORGANIZATION_MEDICAL_FACILITY = + new DetectEntityEntityType(Value.ORGANIZATION_MEDICAL_FACILITY, "ORGANIZATION_MEDICAL_FACILITY"); + + public static final DetectEntityEntityType ROUTING_NUMBER = + new DetectEntityEntityType(Value.ROUTING_NUMBER, "ROUTING_NUMBER"); + + public static final DetectEntityEntityType HEALTHCARE_NUMBER = + new DetectEntityEntityType(Value.HEALTHCARE_NUMBER, "HEALTHCARE_NUMBER"); + + public static final DetectEntityEntityType GENDER = new DetectEntityEntityType(Value.GENDER, "GENDER"); + + public static final DetectEntityEntityType NAME = new DetectEntityEntityType(Value.NAME, "NAME"); + + public static final DetectEntityEntityType DRUG = new DetectEntityEntityType(Value.DRUG, "DRUG"); + + public static final DetectEntityEntityType NONE = new DetectEntityEntityType(Value.NONE, "NONE"); + + public static final DetectEntityEntityType DATE_INTERVAL = + new DetectEntityEntityType(Value.DATE_INTERVAL, "DATE_INTERVAL"); + + public static final DetectEntityEntityType MARITAL_STATUS = + new DetectEntityEntityType(Value.MARITAL_STATUS, "MARITAL_STATUS"); + + public static final DetectEntityEntityType CORPORATE_ACTION = + new DetectEntityEntityType(Value.CORPORATE_ACTION, "CORPORATE_ACTION"); + + public static final DetectEntityEntityType AGE = new DetectEntityEntityType(Value.AGE, "AGE"); + + public static final DetectEntityEntityType CVV = new DetectEntityEntityType(Value.CVV, "CVV"); + + public static final DetectEntityEntityType NAME_GIVEN = new DetectEntityEntityType(Value.NAME_GIVEN, "NAME_GIVEN"); + + public static final DetectEntityEntityType DATE = new DetectEntityEntityType(Value.DATE, "DATE"); + + public static final DetectEntityEntityType DAY = new DetectEntityEntityType(Value.DAY, "DAY"); + + public static final DetectEntityEntityType EFFECT = new DetectEntityEntityType(Value.EFFECT, "EFFECT"); + + public static final DetectEntityEntityType MEDICAL_CODE = + new DetectEntityEntityType(Value.MEDICAL_CODE, "MEDICAL_CODE"); + + public static final DetectEntityEntityType NUMERICAL_PII = + new DetectEntityEntityType(Value.NUMERICAL_PII, "NUMERICAL_PII"); + + public static final DetectEntityEntityType TREND = new DetectEntityEntityType(Value.TREND, "TREND"); + + public static final DetectEntityEntityType INJURY = new DetectEntityEntityType(Value.INJURY, "INJURY"); + + public static final DetectEntityEntityType DRIVER_LICENSE = + new DetectEntityEntityType(Value.DRIVER_LICENSE, "DRIVER_LICENSE"); + + public static final DetectEntityEntityType ALL = new DetectEntityEntityType(Value.ALL, "ALL"); + + public static final DetectEntityEntityType ORGANIZATION_ID = + new DetectEntityEntityType(Value.ORGANIZATION_ID, "ORGANIZATION_ID"); + + public static final DetectEntityEntityType PASSWORD = new DetectEntityEntityType(Value.PASSWORD, "PASSWORD"); + + public static final DetectEntityEntityType DOSE = new DetectEntityEntityType(Value.DOSE, "DOSE"); + + public static final DetectEntityEntityType NAME_FAMILY = + new DetectEntityEntityType(Value.NAME_FAMILY, "NAME_FAMILY"); + + public static final DetectEntityEntityType MONEY = new DetectEntityEntityType(Value.MONEY, "MONEY"); + + public static final DetectEntityEntityType URL = new DetectEntityEntityType(Value.URL, "URL"); + + public static final DetectEntityEntityType LOCATION_COORDINATE = + new DetectEntityEntityType(Value.LOCATION_COORDINATE, "LOCATION_COORDINATE"); + + public static final DetectEntityEntityType FINANCIAL_METRIC = + new DetectEntityEntityType(Value.FINANCIAL_METRIC, "FINANCIAL_METRIC"); + + public static final DetectEntityEntityType ORGANIZATION = + new DetectEntityEntityType(Value.ORGANIZATION, "ORGANIZATION"); + + public static final DetectEntityEntityType IP_ADDRESS = new DetectEntityEntityType(Value.IP_ADDRESS, "IP_ADDRESS"); + + public static final DetectEntityEntityType BLOOD_TYPE = new DetectEntityEntityType(Value.BLOOD_TYPE, "BLOOD_TYPE"); + + public static final DetectEntityEntityType MONTH = new DetectEntityEntityType(Value.MONTH, "MONTH"); + + public static final DetectEntityEntityType CREDIT_CARD = + new DetectEntityEntityType(Value.CREDIT_CARD, "CREDIT_CARD"); + + public static final DetectEntityEntityType ACCOUNT_NUMBER = + new DetectEntityEntityType(Value.ACCOUNT_NUMBER, "ACCOUNT_NUMBER"); + + public static final DetectEntityEntityType CREDIT_CARD_EXPIRATION = + new DetectEntityEntityType(Value.CREDIT_CARD_EXPIRATION, "CREDIT_CARD_EXPIRATION"); + + public static final DetectEntityEntityType SSN = new DetectEntityEntityType(Value.SSN, "SSN"); + + public static final DetectEntityEntityType EMAIL_ADDRESS = + new DetectEntityEntityType(Value.EMAIL_ADDRESS, "EMAIL_ADDRESS"); + + public static final DetectEntityEntityType STATISTICS = new DetectEntityEntityType(Value.STATISTICS, "STATISTICS"); + + private final Value value; + + private final String string; + + DetectEntityEntityType(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DetectEntityEntityType + && this.string.equals(((DetectEntityEntityType) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case PHONE_NUMBER: + return visitor.visitPhoneNumber(); + case USERNAME: + return visitor.visitUsername(); + case VEHICLE_ID: + return visitor.visitVehicleId(); + case RELIGION: + return visitor.visitReligion(); + case ORIGIN: + return visitor.visitOrigin(); + case PASSPORT_NUMBER: + return visitor.visitPassportNumber(); + case YEAR: + return visitor.visitYear(); + case CONDITION: + return visitor.visitCondition(); + case LOCATION: + return visitor.visitLocation(); + case LOCATION_CITY: + return visitor.visitLocationCity(); + case LOCATION_COUNTRY: + return visitor.visitLocationCountry(); + case PROJECT: + return visitor.visitProject(); + case LOCATION_ZIP: + return visitor.visitLocationZip(); + case NAME_MEDICAL_PROFESSIONAL: + return visitor.visitNameMedicalProfessional(); + case BANK_ACCOUNT: + return visitor.visitBankAccount(); + case OCCUPATION: + return visitor.visitOccupation(); + case TIME: + return visitor.visitTime(); + case LOCATION_STATE: + return visitor.visitLocationState(); + case EVENT: + return visitor.visitEvent(); + case POLITICAL_AFFILIATION: + return visitor.visitPoliticalAffiliation(); + case LANGUAGE: + return visitor.visitLanguage(); + case ZODIAC_SIGN: + return visitor.visitZodiacSign(); + case PRODUCT: + return visitor.visitProduct(); + case SEXUALITY: + return visitor.visitSexuality(); + case DURATION: + return visitor.visitDuration(); + case PHYSICAL_ATTRIBUTE: + return visitor.visitPhysicalAttribute(); + case LOCATION_ADDRESS_STREET: + return visitor.visitLocationAddressStreet(); + case MEDICAL_PROCESS: + return visitor.visitMedicalProcess(); + case LOCATION_ADDRESS: + return visitor.visitLocationAddress(); + case FILENAME: + return visitor.visitFilename(); + case DOB: + return visitor.visitDob(); + case ORGANIZATION_MEDICAL_FACILITY: + return visitor.visitOrganizationMedicalFacility(); + case ROUTING_NUMBER: + return visitor.visitRoutingNumber(); + case HEALTHCARE_NUMBER: + return visitor.visitHealthcareNumber(); + case GENDER: + return visitor.visitGender(); + case NAME: + return visitor.visitName(); + case DRUG: + return visitor.visitDrug(); + case NONE: + return visitor.visitNone(); + case DATE_INTERVAL: + return visitor.visitDateInterval(); + case MARITAL_STATUS: + return visitor.visitMaritalStatus(); + case CORPORATE_ACTION: + return visitor.visitCorporateAction(); + case AGE: + return visitor.visitAge(); + case CVV: + return visitor.visitCvv(); + case NAME_GIVEN: + return visitor.visitNameGiven(); + case DATE: + return visitor.visitDate(); + case DAY: + return visitor.visitDay(); + case EFFECT: + return visitor.visitEffect(); + case MEDICAL_CODE: + return visitor.visitMedicalCode(); + case NUMERICAL_PII: + return visitor.visitNumericalPii(); + case TREND: + return visitor.visitTrend(); + case INJURY: + return visitor.visitInjury(); + case DRIVER_LICENSE: + return visitor.visitDriverLicense(); + case ALL: + return visitor.visitAll(); + case ORGANIZATION_ID: + return visitor.visitOrganizationId(); + case PASSWORD: + return visitor.visitPassword(); + case DOSE: + return visitor.visitDose(); + case NAME_FAMILY: + return visitor.visitNameFamily(); + case MONEY: + return visitor.visitMoney(); + case URL: + return visitor.visitUrl(); + case LOCATION_COORDINATE: + return visitor.visitLocationCoordinate(); + case FINANCIAL_METRIC: + return visitor.visitFinancialMetric(); + case ORGANIZATION: + return visitor.visitOrganization(); + case IP_ADDRESS: + return visitor.visitIpAddress(); + case BLOOD_TYPE: + return visitor.visitBloodType(); + case MONTH: + return visitor.visitMonth(); + case CREDIT_CARD: + return visitor.visitCreditCard(); + case ACCOUNT_NUMBER: + return visitor.visitAccountNumber(); + case CREDIT_CARD_EXPIRATION: + return visitor.visitCreditCardExpiration(); + case SSN: + return visitor.visitSsn(); + case EMAIL_ADDRESS: + return visitor.visitEmailAddress(); + case STATISTICS: + return visitor.visitStatistics(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DetectEntityEntityType valueOf(String value) { + switch (value) { + case "PHONE_NUMBER": + return PHONE_NUMBER; + case "USERNAME": + return USERNAME; + case "VEHICLE_ID": + return VEHICLE_ID; + case "RELIGION": + return RELIGION; + case "ORIGIN": + return ORIGIN; + case "PASSPORT_NUMBER": + return PASSPORT_NUMBER; + case "YEAR": + return YEAR; + case "CONDITION": + return CONDITION; + case "LOCATION": + return LOCATION; + case "LOCATION_CITY": + return LOCATION_CITY; + case "LOCATION_COUNTRY": + return LOCATION_COUNTRY; + case "PROJECT": + return PROJECT; + case "LOCATION_ZIP": + return LOCATION_ZIP; + case "NAME_MEDICAL_PROFESSIONAL": + return NAME_MEDICAL_PROFESSIONAL; + case "BANK_ACCOUNT": + return BANK_ACCOUNT; + case "OCCUPATION": + return OCCUPATION; + case "TIME": + return TIME; + case "LOCATION_STATE": + return LOCATION_STATE; + case "EVENT": + return EVENT; + case "POLITICAL_AFFILIATION": + return POLITICAL_AFFILIATION; + case "LANGUAGE": + return LANGUAGE; + case "ZODIAC_SIGN": + return ZODIAC_SIGN; + case "PRODUCT": + return PRODUCT; + case "SEXUALITY": + return SEXUALITY; + case "DURATION": + return DURATION; + case "PHYSICAL_ATTRIBUTE": + return PHYSICAL_ATTRIBUTE; + case "LOCATION_ADDRESS_STREET": + return LOCATION_ADDRESS_STREET; + case "MEDICAL_PROCESS": + return MEDICAL_PROCESS; + case "LOCATION_ADDRESS": + return LOCATION_ADDRESS; + case "FILENAME": + return FILENAME; + case "DOB": + return DOB; + case "ORGANIZATION_MEDICAL_FACILITY": + return ORGANIZATION_MEDICAL_FACILITY; + case "ROUTING_NUMBER": + return ROUTING_NUMBER; + case "HEALTHCARE_NUMBER": + return HEALTHCARE_NUMBER; + case "GENDER": + return GENDER; + case "NAME": + return NAME; + case "DRUG": + return DRUG; + case "NONE": + return NONE; + case "DATE_INTERVAL": + return DATE_INTERVAL; + case "MARITAL_STATUS": + return MARITAL_STATUS; + case "CORPORATE_ACTION": + return CORPORATE_ACTION; + case "AGE": + return AGE; + case "CVV": + return CVV; + case "NAME_GIVEN": + return NAME_GIVEN; + case "DATE": + return DATE; + case "DAY": + return DAY; + case "EFFECT": + return EFFECT; + case "MEDICAL_CODE": + return MEDICAL_CODE; + case "NUMERICAL_PII": + return NUMERICAL_PII; + case "TREND": + return TREND; + case "INJURY": + return INJURY; + case "DRIVER_LICENSE": + return DRIVER_LICENSE; + case "ALL": + return ALL; + case "ORGANIZATION_ID": + return ORGANIZATION_ID; + case "PASSWORD": + return PASSWORD; + case "DOSE": + return DOSE; + case "NAME_FAMILY": + return NAME_FAMILY; + case "MONEY": + return MONEY; + case "URL": + return URL; + case "LOCATION_COORDINATE": + return LOCATION_COORDINATE; + case "FINANCIAL_METRIC": + return FINANCIAL_METRIC; + case "ORGANIZATION": + return ORGANIZATION; + case "IP_ADDRESS": + return IP_ADDRESS; + case "BLOOD_TYPE": + return BLOOD_TYPE; + case "MONTH": + return MONTH; + case "CREDIT_CARD": + return CREDIT_CARD; + case "ACCOUNT_NUMBER": + return ACCOUNT_NUMBER; + case "CREDIT_CARD_EXPIRATION": + return CREDIT_CARD_EXPIRATION; + case "SSN": + return SSN; + case "EMAIL_ADDRESS": + return EMAIL_ADDRESS; + case "STATISTICS": + return STATISTICS; + default: + return new DetectEntityEntityType(Value.UNKNOWN, value); + } + } + + public enum Value { + NONE, + + BANK_ACCOUNT, + + CREDIT_CARD, + + CREDIT_CARD_EXPIRATION, + + CVV, + + DATE, + + DATE_INTERVAL, + + DOB, + + DRIVER_LICENSE, + + EMAIL_ADDRESS, + + HEALTHCARE_NUMBER, + + IP_ADDRESS, + + LOCATION, + + NAME, + + NUMERICAL_PII, + + PHONE_NUMBER, + + SSN, + + URL, + + VEHICLE_ID, + + MEDICAL_CODE, + + NAME_FAMILY, + + NAME_GIVEN, + + ACCOUNT_NUMBER, + + EVENT, + + FILENAME, + + GENDER, + + LANGUAGE, + + LOCATION_ADDRESS, + + LOCATION_CITY, + + LOCATION_COORDINATE, + + LOCATION_COUNTRY, + + LOCATION_STATE, + + LOCATION_ZIP, + + MARITAL_STATUS, + + MONEY, + + NAME_MEDICAL_PROFESSIONAL, + + OCCUPATION, + + ORGANIZATION, + + ORGANIZATION_MEDICAL_FACILITY, + + ORIGIN, + + PASSPORT_NUMBER, + + PASSWORD, + + PHYSICAL_ATTRIBUTE, + + POLITICAL_AFFILIATION, + + RELIGION, + + TIME, + + USERNAME, + + ZODIAC_SIGN, + + BLOOD_TYPE, + + CONDITION, + + DOSE, + + DRUG, + + INJURY, + + MEDICAL_PROCESS, + + STATISTICS, + + ROUTING_NUMBER, + + CORPORATE_ACTION, + + FINANCIAL_METRIC, + + PRODUCT, + + TREND, + + DURATION, + + LOCATION_ADDRESS_STREET, + + AGE, + + SEXUALITY, + + EFFECT, + + PROJECT, + + ORGANIZATION_ID, + + DAY, + + MONTH, + + YEAR, + + ALL, + + UNKNOWN + } + + public interface Visitor { + T visitNone(); + + T visitBankAccount(); + + T visitCreditCard(); + + T visitCreditCardExpiration(); + + T visitCvv(); + + T visitDate(); + + T visitDateInterval(); + + T visitDob(); + + T visitDriverLicense(); + + T visitEmailAddress(); + + T visitHealthcareNumber(); + + T visitIpAddress(); + + T visitLocation(); + + T visitName(); + + T visitNumericalPii(); + + T visitPhoneNumber(); + + T visitSsn(); + + T visitUrl(); + + T visitVehicleId(); + + T visitMedicalCode(); + + T visitNameFamily(); + + T visitNameGiven(); + + T visitAccountNumber(); + + T visitEvent(); + + T visitFilename(); + + T visitGender(); + + T visitLanguage(); + + T visitLocationAddress(); + + T visitLocationCity(); + + T visitLocationCoordinate(); + + T visitLocationCountry(); + + T visitLocationState(); + + T visitLocationZip(); + + T visitMaritalStatus(); + + T visitMoney(); + + T visitNameMedicalProfessional(); + + T visitOccupation(); + + T visitOrganization(); + + T visitOrganizationMedicalFacility(); + + T visitOrigin(); + + T visitPassportNumber(); + + T visitPassword(); + + T visitPhysicalAttribute(); + + T visitPoliticalAffiliation(); + + T visitReligion(); + + T visitTime(); + + T visitUsername(); + + T visitZodiacSign(); + + T visitBloodType(); + + T visitCondition(); + + T visitDose(); + + T visitDrug(); + + T visitInjury(); + + T visitMedicalProcess(); + + T visitStatistics(); + + T visitRoutingNumber(); + + T visitCorporateAction(); + + T visitFinancialMetric(); + + T visitProduct(); + + T visitTrend(); + + T visitDuration(); + + T visitLocationAddressStreet(); + + T visitAge(); + + T visitSexuality(); + + T visitEffect(); + + T visitProject(); + + T visitOrganizationId(); + + T visitDay(); + + T visitMonth(); + + T visitYear(); + + T visitAll(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectGuardrailsResponseV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectGuardrailsResponseV2.java new file mode 100644 index 00000000..ca14dc32 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectGuardrailsResponseV2.java @@ -0,0 +1,252 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DetectGuardrailsResponseV2.Builder.class) +public final class DetectGuardrailsResponseV2 { + private final String text; + + private final Optional toxic; + + private final Optional deniedTopic; + + private final DetectGuardrailsResponseV2Validation validation; + + private final Map additionalProperties; + + private DetectGuardrailsResponseV2( + String text, + Optional toxic, + Optional deniedTopic, + DetectGuardrailsResponseV2Validation validation, + Map additionalProperties) { + this.text = text; + this.toxic = toxic; + this.deniedTopic = deniedTopic; + this.validation = validation; + this.additionalProperties = additionalProperties; + } + + /** + * @return Text that was checked against guardrails. + */ + @JsonProperty("text") + public String getText() { + return text; + } + + /** + * @return Whether the text is toxic. + */ + @JsonProperty("toxic") + public Optional getToxic() { + return toxic; + } + + /** + * @return Whether the text included a denied topic. + */ + @JsonProperty("deniedTopic") + public Optional getDeniedTopic() { + return deniedTopic; + } + + /** + * @return Whether the text passed validation. + */ + @JsonProperty("validation") + public DetectGuardrailsResponseV2Validation getValidation() { + return validation; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetectGuardrailsResponseV2 && equalTo((DetectGuardrailsResponseV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DetectGuardrailsResponseV2 other) { + return text.equals(other.text) + && toxic.equals(other.toxic) + && deniedTopic.equals(other.deniedTopic) + && validation.equals(other.validation); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.text, this.toxic, this.deniedTopic, this.validation); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static TextStage builder() { + return new Builder(); + } + + public interface TextStage { + /** + *

Text that was checked against guardrails.

+ */ + ValidationStage text(@NotNull String text); + + Builder from(DetectGuardrailsResponseV2 other); + } + + public interface ValidationStage { + /** + *

Whether the text passed validation.

+ */ + _FinalStage validation(@NotNull DetectGuardrailsResponseV2Validation validation); + } + + public interface _FinalStage { + DetectGuardrailsResponseV2 build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + /** + *

Whether the text is toxic.

+ */ + _FinalStage toxic(Optional toxic); + + _FinalStage toxic(Boolean toxic); + + /** + *

Whether the text included a denied topic.

+ */ + _FinalStage deniedTopic(Optional deniedTopic); + + _FinalStage deniedTopic(Boolean deniedTopic); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements TextStage, ValidationStage, _FinalStage { + private String text; + + private DetectGuardrailsResponseV2Validation validation; + + private Optional deniedTopic = Optional.empty(); + + private Optional toxic = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(DetectGuardrailsResponseV2 other) { + text(other.getText()); + toxic(other.getToxic()); + deniedTopic(other.getDeniedTopic()); + validation(other.getValidation()); + return this; + } + + /** + *

Text that was checked against guardrails.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("text") + public ValidationStage text(@NotNull String text) { + this.text = Objects.requireNonNull(text, "text must not be null"); + return this; + } + + /** + *

Whether the text passed validation.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("validation") + public _FinalStage validation(@NotNull DetectGuardrailsResponseV2Validation validation) { + this.validation = Objects.requireNonNull(validation, "validation must not be null"); + return this; + } + + /** + *

Whether the text included a denied topic.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage deniedTopic(Boolean deniedTopic) { + this.deniedTopic = Optional.ofNullable(deniedTopic); + return this; + } + + /** + *

Whether the text included a denied topic.

+ */ + @java.lang.Override + @JsonSetter(value = "deniedTopic", nulls = Nulls.SKIP) + public _FinalStage deniedTopic(Optional deniedTopic) { + this.deniedTopic = deniedTopic; + return this; + } + + /** + *

Whether the text is toxic.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + public _FinalStage toxic(Boolean toxic) { + this.toxic = Optional.ofNullable(toxic); + return this; + } + + /** + *

Whether the text is toxic.

+ */ + @java.lang.Override + @JsonSetter(value = "toxic", nulls = Nulls.SKIP) + public _FinalStage toxic(Optional toxic) { + this.toxic = toxic; + return this; + } + + @java.lang.Override + public DetectGuardrailsResponseV2 build() { + return new DetectGuardrailsResponseV2(text, toxic, deniedTopic, validation, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectGuardrailsResponseV2Validation.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectGuardrailsResponseV2Validation.java new file mode 100644 index 00000000..0337a29f --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectGuardrailsResponseV2Validation.java @@ -0,0 +1,86 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DetectGuardrailsResponseV2Validation { + public static final DetectGuardrailsResponseV2Validation PASSED = + new DetectGuardrailsResponseV2Validation(Value.PASSED, "PASSED"); + + public static final DetectGuardrailsResponseV2Validation FAILED = + new DetectGuardrailsResponseV2Validation(Value.FAILED, "FAILED"); + + private final Value value; + + private final String string; + + DetectGuardrailsResponseV2Validation(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DetectGuardrailsResponseV2Validation + && this.string.equals(((DetectGuardrailsResponseV2Validation) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case PASSED: + return visitor.visitPassed(); + case FAILED: + return visitor.visitFailed(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DetectGuardrailsResponseV2Validation valueOf(String value) { + switch (value) { + case "PASSED": + return PASSED; + case "FAILED": + return FAILED; + default: + return new DetectGuardrailsResponseV2Validation(Value.UNKNOWN, value); + } + } + + public enum Value { + FAILED, + + PASSED, + + UNKNOWN + } + + public interface Visitor { + T visitFailed(); + + T visitPassed(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectObjectEntity.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectObjectEntity.java new file mode 100644 index 00000000..52333c00 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectObjectEntity.java @@ -0,0 +1,142 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DetectObjectEntity.Builder.class) +public final class DetectObjectEntity { + private final Optional entityType; + + private final Optional deidentificationType; + + private final Map additionalProperties; + + private DetectObjectEntity( + Optional entityType, + Optional deidentificationType, + Map additionalProperties) { + this.entityType = entityType; + this.deidentificationType = deidentificationType; + this.additionalProperties = additionalProperties; + } + + /** + * @return Type of entity to detect. + */ + @JsonProperty("entityType") + public Optional getEntityType() { + return entityType; + } + + /** + * @return Type of deidentification to apply. + */ + @JsonProperty("deidentificationType") + public Optional getDeidentificationType() { + return deidentificationType; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetectObjectEntity && equalTo((DetectObjectEntity) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DetectObjectEntity other) { + return entityType.equals(other.entityType) && deidentificationType.equals(other.deidentificationType); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.entityType, this.deidentificationType); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional entityType = Optional.empty(); + + private Optional deidentificationType = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(DetectObjectEntity other) { + entityType(other.getEntityType()); + deidentificationType(other.getDeidentificationType()); + return this; + } + + /** + *

Type of entity to detect.

+ */ + @JsonSetter(value = "entityType", nulls = Nulls.SKIP) + public Builder entityType(Optional entityType) { + this.entityType = entityType; + return this; + } + + public Builder entityType(DetectObjectEntityEntityType entityType) { + this.entityType = Optional.ofNullable(entityType); + return this; + } + + /** + *

Type of deidentification to apply.

+ */ + @JsonSetter(value = "deidentificationType", nulls = Nulls.SKIP) + public Builder deidentificationType(Optional deidentificationType) { + this.deidentificationType = deidentificationType; + return this; + } + + public Builder deidentificationType(DetectObjectEntityDeidentificationType deidentificationType) { + this.deidentificationType = Optional.ofNullable(deidentificationType); + return this; + } + + public DetectObjectEntity build() { + return new DetectObjectEntity(entityType, deidentificationType, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectObjectEntityDeidentificationType.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectObjectEntityDeidentificationType.java new file mode 100644 index 00000000..aec7dc98 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectObjectEntityDeidentificationType.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DetectObjectEntityDeidentificationType { + public static final DetectObjectEntityDeidentificationType UNREDACT = + new DetectObjectEntityDeidentificationType(Value.UNREDACT, "UNREDACT"); + + public static final DetectObjectEntityDeidentificationType UNKNOWN = + new DetectObjectEntityDeidentificationType(Value.UNKNOWN, "UNKNOWN"); + + public static final DetectObjectEntityDeidentificationType REDACT = + new DetectObjectEntityDeidentificationType(Value.REDACT, "REDACT"); + + private final Value value; + + private final String string; + + DetectObjectEntityDeidentificationType(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DetectObjectEntityDeidentificationType + && this.string.equals(((DetectObjectEntityDeidentificationType) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case UNREDACT: + return visitor.visitUnredact(); + case UNKNOWN: + return visitor.visitUnknown(); + case REDACT: + return visitor.visitRedact(); + case _UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DetectObjectEntityDeidentificationType valueOf(String value) { + switch (value) { + case "UNREDACT": + return UNREDACT; + case "UNKNOWN": + return UNKNOWN; + case "REDACT": + return REDACT; + default: + return new DetectObjectEntityDeidentificationType(Value._UNKNOWN, value); + } + } + + public enum Value { + UNKNOWN, + + REDACT, + + UNREDACT, + + _UNKNOWN + } + + public interface Visitor { + T visitUnknown(); + + T visitRedact(); + + T visitUnredact(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectObjectEntityEntityType.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectObjectEntityEntityType.java new file mode 100644 index 00000000..93f8b64f --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectObjectEntityEntityType.java @@ -0,0 +1,126 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DetectObjectEntityEntityType { + public static final DetectObjectEntityEntityType FACE = new DetectObjectEntityEntityType(Value.FACE, "FACE"); + + public static final DetectObjectEntityEntityType SIGNATURE = + new DetectObjectEntityEntityType(Value.SIGNATURE, "SIGNATURE"); + + public static final DetectObjectEntityEntityType LICENSE_PLATE = + new DetectObjectEntityEntityType(Value.LICENSE_PLATE, "LICENSE_PLATE"); + + public static final DetectObjectEntityEntityType ALL = new DetectObjectEntityEntityType(Value.ALL, "ALL"); + + public static final DetectObjectEntityEntityType NONE = new DetectObjectEntityEntityType(Value.NONE, "NONE"); + + public static final DetectObjectEntityEntityType LOGO = new DetectObjectEntityEntityType(Value.LOGO, "LOGO"); + + private final Value value; + + private final String string; + + DetectObjectEntityEntityType(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DetectObjectEntityEntityType + && this.string.equals(((DetectObjectEntityEntityType) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case FACE: + return visitor.visitFace(); + case SIGNATURE: + return visitor.visitSignature(); + case LICENSE_PLATE: + return visitor.visitLicensePlate(); + case ALL: + return visitor.visitAll(); + case NONE: + return visitor.visitNone(); + case LOGO: + return visitor.visitLogo(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DetectObjectEntityEntityType valueOf(String value) { + switch (value) { + case "FACE": + return FACE; + case "SIGNATURE": + return SIGNATURE; + case "LICENSE_PLATE": + return LICENSE_PLATE; + case "ALL": + return ALL; + case "NONE": + return NONE; + case "LOGO": + return LOGO; + default: + return new DetectObjectEntityEntityType(Value.UNKNOWN, value); + } + } + + public enum Value { + NONE, + + FACE, + + LICENSE_PLATE, + + LOGO, + + SIGNATURE, + + ALL, + + UNKNOWN + } + + public interface Visitor { + T visitNone(); + + T visitFace(); + + T visitLicensePlate(); + + T visitLogo(); + + T visitSignature(); + + T visitAll(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectReturnEntities.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectReturnEntities.java new file mode 100644 index 00000000..f5435ec2 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectReturnEntities.java @@ -0,0 +1,94 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DetectReturnEntities { + public static final DetectReturnEntities ALL = new DetectReturnEntities(Value.ALL, "ALL"); + + public static final DetectReturnEntities NONE = new DetectReturnEntities(Value.NONE, "NONE"); + + public static final DetectReturnEntities EXCLUDE_SENSITIVE_DATA = + new DetectReturnEntities(Value.EXCLUDE_SENSITIVE_DATA, "EXCLUDE_SENSITIVE_DATA"); + + private final Value value; + + private final String string; + + DetectReturnEntities(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DetectReturnEntities && this.string.equals(((DetectReturnEntities) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case ALL: + return visitor.visitAll(); + case NONE: + return visitor.visitNone(); + case EXCLUDE_SENSITIVE_DATA: + return visitor.visitExcludeSensitiveData(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DetectReturnEntities valueOf(String value) { + switch (value) { + case "ALL": + return ALL; + case "NONE": + return NONE; + case "EXCLUDE_SENSITIVE_DATA": + return EXCLUDE_SENSITIVE_DATA; + default: + return new DetectReturnEntities(Value.UNKNOWN, value); + } + } + + public enum Value { + EXCLUDE_SENSITIVE_DATA, + + ALL, + + NONE, + + UNKNOWN + } + + public interface Visitor { + T visitExcludeSensitiveData(); + + T visitAll(); + + T visitNone(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectRunsResponseV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectRunsResponseV2.java new file mode 100644 index 00000000..372e164d --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectRunsResponseV2.java @@ -0,0 +1,228 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DetectRunsResponseV2.Builder.class) +public final class DetectRunsResponseV2 { + private final Optional status; + + private final Optional outputType; + + private final Optional> output; + + private final Optional message; + + private final Optional metrics; + + private final Map additionalProperties; + + private DetectRunsResponseV2( + Optional status, + Optional outputType, + Optional> output, + Optional message, + Optional metrics, + Map additionalProperties) { + this.status = status; + this.outputType = outputType; + this.output = output; + this.message = message; + this.metrics = metrics; + this.additionalProperties = additionalProperties; + } + + /** + * @return Status of the detect run. + */ + @JsonProperty("status") + public Optional getStatus() { + return status; + } + + /** + * @return Data source of the output file. BASE64: Base64-encoded file string. SKYFLOW_ID: Reference file by vault ID. PRESIGNED_URL: S3 presigned URL of input file. + */ + @JsonProperty("outputType") + public Optional getOutputType() { + return outputType; + } + + /** + * @return Details of output files. + */ + @JsonProperty("output") + public Optional> getOutput() { + return output; + } + + /** + * @return Status details about the Detect run. + */ + @JsonProperty("message") + public Optional getMessage() { + return message; + } + + @JsonProperty("metrics") + public Optional getMetrics() { + return metrics; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DetectRunsResponseV2 && equalTo((DetectRunsResponseV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DetectRunsResponseV2 other) { + return status.equals(other.status) + && outputType.equals(other.outputType) + && output.equals(other.output) + && message.equals(other.message) + && metrics.equals(other.metrics); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.status, this.outputType, this.output, this.message, this.metrics); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional status = Optional.empty(); + + private Optional outputType = Optional.empty(); + + private Optional> output = Optional.empty(); + + private Optional message = Optional.empty(); + + private Optional metrics = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(DetectRunsResponseV2 other) { + status(other.getStatus()); + outputType(other.getOutputType()); + output(other.getOutput()); + message(other.getMessage()); + metrics(other.getMetrics()); + return this; + } + + /** + *

Status of the detect run.

+ */ + @JsonSetter(value = "status", nulls = Nulls.SKIP) + public Builder status(Optional status) { + this.status = status; + return this; + } + + public Builder status(DetectRunsResponseV2Status status) { + this.status = Optional.ofNullable(status); + return this; + } + + /** + *

Data source of the output file. BASE64: Base64-encoded file string. SKYFLOW_ID: Reference file by vault ID. PRESIGNED_URL: S3 presigned URL of input file.

+ */ + @JsonSetter(value = "outputType", nulls = Nulls.SKIP) + public Builder outputType(Optional outputType) { + this.outputType = outputType; + return this; + } + + public Builder outputType(DetectRunsResponseV2OutputType outputType) { + this.outputType = Optional.ofNullable(outputType); + return this; + } + + /** + *

Details of output files.

+ */ + @JsonSetter(value = "output", nulls = Nulls.SKIP) + public Builder output(Optional> output) { + this.output = output; + return this; + } + + public Builder output(List output) { + this.output = Optional.ofNullable(output); + return this; + } + + /** + *

Status details about the Detect run.

+ */ + @JsonSetter(value = "message", nulls = Nulls.SKIP) + public Builder message(Optional message) { + this.message = message; + return this; + } + + public Builder message(String message) { + this.message = Optional.ofNullable(message); + return this; + } + + @JsonSetter(value = "metrics", nulls = Nulls.SKIP) + public Builder metrics(Optional metrics) { + this.metrics = metrics; + return this; + } + + public Builder metrics(Metrics metrics) { + this.metrics = Optional.ofNullable(metrics); + return this; + } + + public DetectRunsResponseV2 build() { + return new DetectRunsResponseV2(status, outputType, output, message, metrics, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectRunsResponseV2OutputType.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectRunsResponseV2OutputType.java new file mode 100644 index 00000000..cb995d80 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectRunsResponseV2OutputType.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DetectRunsResponseV2OutputType { + public static final DetectRunsResponseV2OutputType BASE64 = + new DetectRunsResponseV2OutputType(Value.BASE64, "BASE64"); + + public static final DetectRunsResponseV2OutputType SKYFLOW_ID = + new DetectRunsResponseV2OutputType(Value.SKYFLOW_ID, "SKYFLOW_ID"); + + public static final DetectRunsResponseV2OutputType PRESIGNED_URL = + new DetectRunsResponseV2OutputType(Value.PRESIGNED_URL, "PRESIGNED_URL"); + + private final Value value; + + private final String string; + + DetectRunsResponseV2OutputType(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DetectRunsResponseV2OutputType + && this.string.equals(((DetectRunsResponseV2OutputType) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case BASE64: + return visitor.visitBase64(); + case SKYFLOW_ID: + return visitor.visitSkyflowId(); + case PRESIGNED_URL: + return visitor.visitPresignedUrl(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DetectRunsResponseV2OutputType valueOf(String value) { + switch (value) { + case "BASE64": + return BASE64; + case "SKYFLOW_ID": + return SKYFLOW_ID; + case "PRESIGNED_URL": + return PRESIGNED_URL; + default: + return new DetectRunsResponseV2OutputType(Value.UNKNOWN, value); + } + } + + public enum Value { + BASE64, + + SKYFLOW_ID, + + PRESIGNED_URL, + + UNKNOWN + } + + public interface Visitor { + T visitBase64(); + + T visitSkyflowId(); + + T visitPresignedUrl(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectRunsResponseV2Status.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectRunsResponseV2Status.java new file mode 100644 index 00000000..c4549699 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DetectRunsResponseV2Status.java @@ -0,0 +1,115 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DetectRunsResponseV2Status { + public static final DetectRunsResponseV2Status IN_PROGRESS = + new DetectRunsResponseV2Status(Value.IN_PROGRESS, "IN_PROGRESS"); + + public static final DetectRunsResponseV2Status QUEUED = new DetectRunsResponseV2Status(Value.QUEUED, "QUEUED"); + + public static final DetectRunsResponseV2Status SUCCESS = new DetectRunsResponseV2Status(Value.SUCCESS, "SUCCESS"); + + public static final DetectRunsResponseV2Status UNKNOWN = new DetectRunsResponseV2Status(Value.UNKNOWN, "UNKNOWN"); + + public static final DetectRunsResponseV2Status FAILED = new DetectRunsResponseV2Status(Value.FAILED, "FAILED"); + + private final Value value; + + private final String string; + + DetectRunsResponseV2Status(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DetectRunsResponseV2Status + && this.string.equals(((DetectRunsResponseV2Status) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case IN_PROGRESS: + return visitor.visitInProgress(); + case QUEUED: + return visitor.visitQueued(); + case SUCCESS: + return visitor.visitSuccess(); + case UNKNOWN: + return visitor.visitUnknown(); + case FAILED: + return visitor.visitFailed(); + case _UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DetectRunsResponseV2Status valueOf(String value) { + switch (value) { + case "IN_PROGRESS": + return IN_PROGRESS; + case "QUEUED": + return QUEUED; + case "SUCCESS": + return SUCCESS; + case "UNKNOWN": + return UNKNOWN; + case "FAILED": + return FAILED; + default: + return new DetectRunsResponseV2Status(Value._UNKNOWN, value); + } + } + + public enum Value { + UNKNOWN, + + FAILED, + + SUCCESS, + + IN_PROGRESS, + + QUEUED, + + _UNKNOWN + } + + public interface Visitor { + T visitUnknown(); + + T visitFailed(); + + T visitSuccess(); + + T visitInProgress(); + + T visitQueued(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DocumentV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DocumentV2.java new file mode 100644 index 00000000..4e080422 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DocumentV2.java @@ -0,0 +1,105 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DocumentV2.Builder.class) +public final class DocumentV2 { + private final Optional pdf; + + private final Map additionalProperties; + + private DocumentV2(Optional pdf, Map additionalProperties) { + this.pdf = pdf; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("pdf") + public Optional getPdf() { + return pdf; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DocumentV2 && equalTo((DocumentV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DocumentV2 other) { + return pdf.equals(other.pdf); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.pdf); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional pdf = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(DocumentV2 other) { + pdf(other.getPdf()); + return this; + } + + @JsonSetter(value = "pdf", nulls = Nulls.SKIP) + public Builder pdf(Optional pdf) { + this.pdf = pdf; + return this; + } + + public Builder pdf(DocumentV2Pdf pdf) { + this.pdf = Optional.ofNullable(pdf); + return this; + } + + public DocumentV2 build() { + return new DocumentV2(pdf, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DocumentV2Pdf.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DocumentV2Pdf.java new file mode 100644 index 00000000..4aae1940 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DocumentV2Pdf.java @@ -0,0 +1,173 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = DocumentV2Pdf.Builder.class) +public final class DocumentV2Pdf { + private final Optional processingMode; + + private final Optional density; + + private final Optional maxResolution; + + private final Map additionalProperties; + + private DocumentV2Pdf( + Optional processingMode, + Optional density, + Optional maxResolution, + Map additionalProperties) { + this.processingMode = processingMode; + this.density = density; + this.maxResolution = maxResolution; + this.additionalProperties = additionalProperties; + } + + /** + * @return Processing mode for the PDF file. TEXT_LAYER: processes the PDF's native text layer and replaces detected entities with entity tokens (for example, [NAME_1]). Fast and accurate for PDFs with selectable text. OCR: processes the PDF as an image using optical character recognition and covers detected content with a gray box. Use for scanned or image-based PDFs. Because OCR rasterizes each page, the output PDF file size can be significantly larger than the input. + */ + @JsonProperty("processingMode") + public Optional getProcessingMode() { + return processingMode; + } + + /** + * @return Pixel density at which to process the PDF file. Applies only when processingMode is OCR. + */ + @JsonProperty("density") + public Optional getDensity() { + return density; + } + + /** + * @return Max resolution at which to process the PDF file. Applies only when processingMode is OCR. + */ + @JsonProperty("maxResolution") + public Optional getMaxResolution() { + return maxResolution; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof DocumentV2Pdf && equalTo((DocumentV2Pdf) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(DocumentV2Pdf other) { + return processingMode.equals(other.processingMode) + && density.equals(other.density) + && maxResolution.equals(other.maxResolution); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.processingMode, this.density, this.maxResolution); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional processingMode = Optional.empty(); + + private Optional density = Optional.empty(); + + private Optional maxResolution = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(DocumentV2Pdf other) { + processingMode(other.getProcessingMode()); + density(other.getDensity()); + maxResolution(other.getMaxResolution()); + return this; + } + + /** + *

Processing mode for the PDF file. TEXT_LAYER: processes the PDF's native text layer and replaces detected entities with entity tokens (for example, [NAME_1]). Fast and accurate for PDFs with selectable text. OCR: processes the PDF as an image using optical character recognition and covers detected content with a gray box. Use for scanned or image-based PDFs. Because OCR rasterizes each page, the output PDF file size can be significantly larger than the input.

+ */ + @JsonSetter(value = "processingMode", nulls = Nulls.SKIP) + public Builder processingMode(Optional processingMode) { + this.processingMode = processingMode; + return this; + } + + public Builder processingMode(DocumentV2PdfProcessingMode processingMode) { + this.processingMode = Optional.ofNullable(processingMode); + return this; + } + + /** + *

Pixel density at which to process the PDF file. Applies only when processingMode is OCR.

+ */ + @JsonSetter(value = "density", nulls = Nulls.SKIP) + public Builder density(Optional density) { + this.density = density; + return this; + } + + public Builder density(Integer density) { + this.density = Optional.ofNullable(density); + return this; + } + + /** + *

Max resolution at which to process the PDF file. Applies only when processingMode is OCR.

+ */ + @JsonSetter(value = "maxResolution", nulls = Nulls.SKIP) + public Builder maxResolution(Optional maxResolution) { + this.maxResolution = maxResolution; + return this; + } + + public Builder maxResolution(Integer maxResolution) { + this.maxResolution = Optional.ofNullable(maxResolution); + return this; + } + + public DocumentV2Pdf build() { + return new DocumentV2Pdf(processingMode, density, maxResolution, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/DocumentV2PdfProcessingMode.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/DocumentV2PdfProcessingMode.java new file mode 100644 index 00000000..2ada581f --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/DocumentV2PdfProcessingMode.java @@ -0,0 +1,95 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class DocumentV2PdfProcessingMode { + public static final DocumentV2PdfProcessingMode OCR = new DocumentV2PdfProcessingMode(Value.OCR, "OCR"); + + public static final DocumentV2PdfProcessingMode NONE = new DocumentV2PdfProcessingMode(Value.NONE, "NONE"); + + public static final DocumentV2PdfProcessingMode TEXT_LAYER = + new DocumentV2PdfProcessingMode(Value.TEXT_LAYER, "TEXT_LAYER"); + + private final Value value; + + private final String string; + + DocumentV2PdfProcessingMode(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof DocumentV2PdfProcessingMode + && this.string.equals(((DocumentV2PdfProcessingMode) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case OCR: + return visitor.visitOcr(); + case NONE: + return visitor.visitNone(); + case TEXT_LAYER: + return visitor.visitTextLayer(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static DocumentV2PdfProcessingMode valueOf(String value) { + switch (value) { + case "OCR": + return OCR; + case "NONE": + return NONE; + case "TEXT_LAYER": + return TEXT_LAYER; + default: + return new DocumentV2PdfProcessingMode(Value.UNKNOWN, value); + } + } + + public enum Value { + NONE, + + OCR, + + TEXT_LAYER, + + UNKNOWN + } + + public interface Visitor { + T visitNone(); + + T visitOcr(); + + T visitTextLayer(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/EntityTransformation.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/EntityTransformation.java new file mode 100644 index 00000000..870b3e13 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/EntityTransformation.java @@ -0,0 +1,106 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = EntityTransformation.Builder.class) +public final class EntityTransformation { + private final Optional shiftDates; + + private final Map additionalProperties; + + private EntityTransformation( + Optional shiftDates, Map additionalProperties) { + this.shiftDates = shiftDates; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("shiftDates") + public Optional getShiftDates() { + return shiftDates; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof EntityTransformation && equalTo((EntityTransformation) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(EntityTransformation other) { + return shiftDates.equals(other.shiftDates); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.shiftDates); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional shiftDates = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(EntityTransformation other) { + shiftDates(other.getShiftDates()); + return this; + } + + @JsonSetter(value = "shiftDates", nulls = Nulls.SKIP) + public Builder shiftDates(Optional shiftDates) { + this.shiftDates = shiftDates; + return this; + } + + public Builder shiftDates(TransformationShiftDates shiftDates) { + this.shiftDates = Optional.ofNullable(shiftDates); + return this; + } + + public EntityTransformation build() { + return new EntityTransformation(shiftDates, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/ErrorResponse.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/ErrorResponse.java new file mode 100644 index 00000000..4e29036e --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/ErrorResponse.java @@ -0,0 +1,118 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ErrorResponse.Builder.class) +public final class ErrorResponse { + private final ErrorResponseError error; + + private final Map additionalProperties; + + private ErrorResponse(ErrorResponseError error, Map additionalProperties) { + this.error = error; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("error") + public ErrorResponseError getError() { + return error; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ErrorResponse && equalTo((ErrorResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ErrorResponse other) { + return error.equals(other.error); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.error); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static ErrorStage builder() { + return new Builder(); + } + + public interface ErrorStage { + _FinalStage error(@NotNull ErrorResponseError error); + + Builder from(ErrorResponse other); + } + + public interface _FinalStage { + ErrorResponse build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements ErrorStage, _FinalStage { + private ErrorResponseError error; + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(ErrorResponse other) { + error(other.getError()); + return this; + } + + @java.lang.Override + @JsonSetter("error") + public _FinalStage error(@NotNull ErrorResponseError error) { + this.error = Objects.requireNonNull(error, "error must not be null"); + return this; + } + + @java.lang.Override + public ErrorResponse build() { + return new ErrorResponse(error, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/ErrorResponseError.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/ErrorResponseError.java new file mode 100644 index 00000000..5c43d6a6 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/ErrorResponseError.java @@ -0,0 +1,236 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ErrorResponseError.Builder.class) +public final class ErrorResponseError { + private final int grpcCode; + + private final int httpCode; + + private final String httpStatus; + + private final String message; + + private final Optional>> details; + + private final Map additionalProperties; + + private ErrorResponseError( + int grpcCode, + int httpCode, + String httpStatus, + String message, + Optional>> details, + Map additionalProperties) { + this.grpcCode = grpcCode; + this.httpCode = httpCode; + this.httpStatus = httpStatus; + this.message = message; + this.details = details; + this.additionalProperties = additionalProperties; + } + + /** + * @return gRPC status codes. See https://grpc.io/docs/guides/status-codes. + */ + @JsonProperty("grpc_code") + public int getGrpcCode() { + return grpcCode; + } + + @JsonProperty("http_code") + public int getHttpCode() { + return httpCode; + } + + @JsonProperty("http_status") + public String getHttpStatus() { + return httpStatus; + } + + @JsonProperty("message") + public String getMessage() { + return message; + } + + @JsonProperty("details") + public Optional>> getDetails() { + return details; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ErrorResponseError && equalTo((ErrorResponseError) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ErrorResponseError other) { + return grpcCode == other.grpcCode + && httpCode == other.httpCode + && httpStatus.equals(other.httpStatus) + && message.equals(other.message) + && details.equals(other.details); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.grpcCode, this.httpCode, this.httpStatus, this.message, this.details); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static GrpcCodeStage builder() { + return new Builder(); + } + + public interface GrpcCodeStage { + /** + *

gRPC status codes. See https://grpc.io/docs/guides/status-codes.

+ */ + HttpCodeStage grpcCode(int grpcCode); + + Builder from(ErrorResponseError other); + } + + public interface HttpCodeStage { + HttpStatusStage httpCode(int httpCode); + } + + public interface HttpStatusStage { + MessageStage httpStatus(@NotNull String httpStatus); + } + + public interface MessageStage { + _FinalStage message(@NotNull String message); + } + + public interface _FinalStage { + ErrorResponseError build(); + + _FinalStage additionalProperty(String key, Object value); + + _FinalStage additionalProperties(Map additionalProperties); + + _FinalStage details(Optional>> details); + + _FinalStage details(List> details); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder + implements GrpcCodeStage, HttpCodeStage, HttpStatusStage, MessageStage, _FinalStage { + private int grpcCode; + + private int httpCode; + + private String httpStatus; + + private String message; + + private Optional>> details = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(ErrorResponseError other) { + grpcCode(other.getGrpcCode()); + httpCode(other.getHttpCode()); + httpStatus(other.getHttpStatus()); + message(other.getMessage()); + details(other.getDetails()); + return this; + } + + /** + *

gRPC status codes. See https://grpc.io/docs/guides/status-codes.

+ * @return Reference to {@code this} so that method calls can be chained together. + */ + @java.lang.Override + @JsonSetter("grpc_code") + public HttpCodeStage grpcCode(int grpcCode) { + this.grpcCode = grpcCode; + return this; + } + + @java.lang.Override + @JsonSetter("http_code") + public HttpStatusStage httpCode(int httpCode) { + this.httpCode = httpCode; + return this; + } + + @java.lang.Override + @JsonSetter("http_status") + public MessageStage httpStatus(@NotNull String httpStatus) { + this.httpStatus = Objects.requireNonNull(httpStatus, "httpStatus must not be null"); + return this; + } + + @java.lang.Override + @JsonSetter("message") + public _FinalStage message(@NotNull String message) { + this.message = Objects.requireNonNull(message, "message must not be null"); + return this; + } + + @java.lang.Override + public _FinalStage details(List> details) { + this.details = Optional.ofNullable(details); + return this; + } + + @java.lang.Override + @JsonSetter(value = "details", nulls = Nulls.SKIP) + public _FinalStage details(Optional>> details) { + this.details = details; + return this; + } + + @java.lang.Override + public ErrorResponseError build() { + return new ErrorResponseError(grpcCode, httpCode, httpStatus, message, details, additionalProperties); + } + + @java.lang.Override + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + @java.lang.Override + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/FileMapping.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/FileMapping.java new file mode 100644 index 00000000..75b023be --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/FileMapping.java @@ -0,0 +1,244 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = FileMapping.Builder.class) +public final class FileMapping { + private final Optional source; + + private final Optional destination; + + private final Optional outputFileNameSuffix; + + private final Optional entitiesDestination; + + private final Optional objectEntitiesDestination; + + private final Map additionalProperties; + + private FileMapping( + Optional source, + Optional destination, + Optional outputFileNameSuffix, + Optional entitiesDestination, + Optional objectEntitiesDestination, + Map additionalProperties) { + this.source = source; + this.destination = destination; + this.outputFileNameSuffix = outputFileNameSuffix; + this.entitiesDestination = entitiesDestination; + this.objectEntitiesDestination = objectEntitiesDestination; + this.additionalProperties = additionalProperties; + } + + /** + * @return Location of the source file. + */ + @JsonProperty("source") + public Optional getSource() { + return source; + } + + /** + * @return Location of the target file. + */ + @JsonProperty("destination") + public Optional getDestination() { + return destination; + } + + /** + * @return Suffix of the output file. For example, with outputFileNameSuffix = "_deidentified", "invoice.pdf" becomes "invoice_deidentified.pdf". + */ + @JsonProperty("outputFileNameSuffix") + public Optional getOutputFileNameSuffix() { + return outputFileNameSuffix; + } + + /** + * @return Location of the entities output, as "table.column". For example, "documents.entities_json". Must be in the same table as source. If unset, the entities output is not stored in the vault. + */ + @JsonProperty("entitiesDestination") + public Optional getEntitiesDestination() { + return entitiesDestination; + } + + /** + * @return Location of the object entities output, as "table.column". For example, "documents.object_entities_json". Must be in the same table as source. If unset, the object entities output is not stored in the vault. + */ + @JsonProperty("objectEntitiesDestination") + public Optional getObjectEntitiesDestination() { + return objectEntitiesDestination; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof FileMapping && equalTo((FileMapping) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(FileMapping other) { + return source.equals(other.source) + && destination.equals(other.destination) + && outputFileNameSuffix.equals(other.outputFileNameSuffix) + && entitiesDestination.equals(other.entitiesDestination) + && objectEntitiesDestination.equals(other.objectEntitiesDestination); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash( + this.source, + this.destination, + this.outputFileNameSuffix, + this.entitiesDestination, + this.objectEntitiesDestination); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional source = Optional.empty(); + + private Optional destination = Optional.empty(); + + private Optional outputFileNameSuffix = Optional.empty(); + + private Optional entitiesDestination = Optional.empty(); + + private Optional objectEntitiesDestination = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(FileMapping other) { + source(other.getSource()); + destination(other.getDestination()); + outputFileNameSuffix(other.getOutputFileNameSuffix()); + entitiesDestination(other.getEntitiesDestination()); + objectEntitiesDestination(other.getObjectEntitiesDestination()); + return this; + } + + /** + *

Location of the source file.

+ */ + @JsonSetter(value = "source", nulls = Nulls.SKIP) + public Builder source(Optional source) { + this.source = source; + return this; + } + + public Builder source(String source) { + this.source = Optional.ofNullable(source); + return this; + } + + /** + *

Location of the target file.

+ */ + @JsonSetter(value = "destination", nulls = Nulls.SKIP) + public Builder destination(Optional destination) { + this.destination = destination; + return this; + } + + public Builder destination(String destination) { + this.destination = Optional.ofNullable(destination); + return this; + } + + /** + *

Suffix of the output file. For example, with outputFileNameSuffix = "_deidentified", "invoice.pdf" becomes "invoice_deidentified.pdf".

+ */ + @JsonSetter(value = "outputFileNameSuffix", nulls = Nulls.SKIP) + public Builder outputFileNameSuffix(Optional outputFileNameSuffix) { + this.outputFileNameSuffix = outputFileNameSuffix; + return this; + } + + public Builder outputFileNameSuffix(String outputFileNameSuffix) { + this.outputFileNameSuffix = Optional.ofNullable(outputFileNameSuffix); + return this; + } + + /** + *

Location of the entities output, as "table.column". For example, "documents.entities_json". Must be in the same table as source. If unset, the entities output is not stored in the vault.

+ */ + @JsonSetter(value = "entitiesDestination", nulls = Nulls.SKIP) + public Builder entitiesDestination(Optional entitiesDestination) { + this.entitiesDestination = entitiesDestination; + return this; + } + + public Builder entitiesDestination(String entitiesDestination) { + this.entitiesDestination = Optional.ofNullable(entitiesDestination); + return this; + } + + /** + *

Location of the object entities output, as "table.column". For example, "documents.object_entities_json". Must be in the same table as source. If unset, the object entities output is not stored in the vault.

+ */ + @JsonSetter(value = "objectEntitiesDestination", nulls = Nulls.SKIP) + public Builder objectEntitiesDestination(Optional objectEntitiesDestination) { + this.objectEntitiesDestination = objectEntitiesDestination; + return this; + } + + public Builder objectEntitiesDestination(String objectEntitiesDestination) { + this.objectEntitiesDestination = Optional.ofNullable(objectEntitiesDestination); + return this; + } + + public FileMapping build() { + return new FileMapping( + source, + destination, + outputFileNameSuffix, + entitiesDestination, + objectEntitiesDestination, + additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/FileOutput.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/FileOutput.java new file mode 100644 index 00000000..69f6cdf8 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/FileOutput.java @@ -0,0 +1,173 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = FileOutput.Builder.class) +public final class FileOutput { + private final Optional processedFile; + + private final Optional processedFileType; + + private final Optional processedFileExtension; + + private final Map additionalProperties; + + private FileOutput( + Optional processedFile, + Optional processedFileType, + Optional processedFileExtension, + Map additionalProperties) { + this.processedFile = processedFile; + this.processedFileType = processedFileType; + this.processedFileExtension = processedFileExtension; + this.additionalProperties = additionalProperties; + } + + /** + * @return Value of the processed file. + */ + @JsonProperty("processedFile") + public Optional getProcessedFile() { + return processedFile; + } + + /** + * @return Type of the processed file. + */ + @JsonProperty("processedFileType") + public Optional getProcessedFileType() { + return processedFileType; + } + + /** + * @return Processed file extension. + */ + @JsonProperty("processedFileExtension") + public Optional getProcessedFileExtension() { + return processedFileExtension; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof FileOutput && equalTo((FileOutput) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(FileOutput other) { + return processedFile.equals(other.processedFile) + && processedFileType.equals(other.processedFileType) + && processedFileExtension.equals(other.processedFileExtension); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.processedFile, this.processedFileType, this.processedFileExtension); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional processedFile = Optional.empty(); + + private Optional processedFileType = Optional.empty(); + + private Optional processedFileExtension = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(FileOutput other) { + processedFile(other.getProcessedFile()); + processedFileType(other.getProcessedFileType()); + processedFileExtension(other.getProcessedFileExtension()); + return this; + } + + /** + *

Value of the processed file.

+ */ + @JsonSetter(value = "processedFile", nulls = Nulls.SKIP) + public Builder processedFile(Optional processedFile) { + this.processedFile = processedFile; + return this; + } + + public Builder processedFile(String processedFile) { + this.processedFile = Optional.ofNullable(processedFile); + return this; + } + + /** + *

Type of the processed file.

+ */ + @JsonSetter(value = "processedFileType", nulls = Nulls.SKIP) + public Builder processedFileType(Optional processedFileType) { + this.processedFileType = processedFileType; + return this; + } + + public Builder processedFileType(FileOutputProcessedFileType processedFileType) { + this.processedFileType = Optional.ofNullable(processedFileType); + return this; + } + + /** + *

Processed file extension.

+ */ + @JsonSetter(value = "processedFileExtension", nulls = Nulls.SKIP) + public Builder processedFileExtension(Optional processedFileExtension) { + this.processedFileExtension = processedFileExtension; + return this; + } + + public Builder processedFileExtension(FileOutputProcessedFileExtension processedFileExtension) { + this.processedFileExtension = Optional.ofNullable(processedFileExtension); + return this; + } + + public FileOutput build() { + return new FileOutput(processedFile, processedFileType, processedFileExtension, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/FileOutputProcessedFileExtension.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/FileOutputProcessedFileExtension.java new file mode 100644 index 00000000..5eb8fd6f --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/FileOutputProcessedFileExtension.java @@ -0,0 +1,301 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class FileOutputProcessedFileExtension { + public static final FileOutputProcessedFileExtension BMP = new FileOutputProcessedFileExtension(Value.BMP, "bmp"); + + public static final FileOutputProcessedFileExtension PPTX = + new FileOutputProcessedFileExtension(Value.PPTX, "pptx"); + + public static final FileOutputProcessedFileExtension WAV = new FileOutputProcessedFileExtension(Value.WAV, "wav"); + + public static final FileOutputProcessedFileExtension DOC = new FileOutputProcessedFileExtension(Value.DOC, "doc"); + + public static final FileOutputProcessedFileExtension XML = new FileOutputProcessedFileExtension(Value.XML, "xml"); + + public static final FileOutputProcessedFileExtension ZIP = new FileOutputProcessedFileExtension(Value.ZIP, "zip"); + + public static final FileOutputProcessedFileExtension MP3 = new FileOutputProcessedFileExtension(Value.MP3, "mp3"); + + public static final FileOutputProcessedFileExtension PPT = new FileOutputProcessedFileExtension(Value.PPT, "ppt"); + + public static final FileOutputProcessedFileExtension JPEG = + new FileOutputProcessedFileExtension(Value.JPEG, "jpeg"); + + public static final FileOutputProcessedFileExtension TIFF = + new FileOutputProcessedFileExtension(Value.TIFF, "tiff"); + + public static final FileOutputProcessedFileExtension PDF = new FileOutputProcessedFileExtension(Value.PDF, "pdf"); + + public static final FileOutputProcessedFileExtension DOCX = + new FileOutputProcessedFileExtension(Value.DOCX, "docx"); + + public static final FileOutputProcessedFileExtension PNG = new FileOutputProcessedFileExtension(Value.PNG, "png"); + + public static final FileOutputProcessedFileExtension JSONL = + new FileOutputProcessedFileExtension(Value.JSONL, "jsonl"); + + public static final FileOutputProcessedFileExtension CSV = new FileOutputProcessedFileExtension(Value.CSV, "csv"); + + public static final FileOutputProcessedFileExtension JPG = new FileOutputProcessedFileExtension(Value.JPG, "jpg"); + + public static final FileOutputProcessedFileExtension DCM = new FileOutputProcessedFileExtension(Value.DCM, "dcm"); + + public static final FileOutputProcessedFileExtension JSON = + new FileOutputProcessedFileExtension(Value.JSON, "json"); + + public static final FileOutputProcessedFileExtension GIF = new FileOutputProcessedFileExtension(Value.GIF, "gif"); + + public static final FileOutputProcessedFileExtension TXT = new FileOutputProcessedFileExtension(Value.TXT, "txt"); + + public static final FileOutputProcessedFileExtension XLS = new FileOutputProcessedFileExtension(Value.XLS, "xls"); + + public static final FileOutputProcessedFileExtension XLSX = + new FileOutputProcessedFileExtension(Value.XLSX, "xlsx"); + + public static final FileOutputProcessedFileExtension TIF = new FileOutputProcessedFileExtension(Value.TIF, "tif"); + + private final Value value; + + private final String string; + + FileOutputProcessedFileExtension(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof FileOutputProcessedFileExtension + && this.string.equals(((FileOutputProcessedFileExtension) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case BMP: + return visitor.visitBmp(); + case PPTX: + return visitor.visitPptx(); + case WAV: + return visitor.visitWav(); + case DOC: + return visitor.visitDoc(); + case XML: + return visitor.visitXml(); + case ZIP: + return visitor.visitZip(); + case MP3: + return visitor.visitMp3(); + case PPT: + return visitor.visitPpt(); + case JPEG: + return visitor.visitJpeg(); + case TIFF: + return visitor.visitTiff(); + case PDF: + return visitor.visitPdf(); + case DOCX: + return visitor.visitDocx(); + case PNG: + return visitor.visitPng(); + case JSONL: + return visitor.visitJsonl(); + case CSV: + return visitor.visitCsv(); + case JPG: + return visitor.visitJpg(); + case DCM: + return visitor.visitDcm(); + case JSON: + return visitor.visitJson(); + case GIF: + return visitor.visitGif(); + case TXT: + return visitor.visitTxt(); + case XLS: + return visitor.visitXls(); + case XLSX: + return visitor.visitXlsx(); + case TIF: + return visitor.visitTif(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static FileOutputProcessedFileExtension valueOf(String value) { + switch (value) { + case "bmp": + return BMP; + case "pptx": + return PPTX; + case "wav": + return WAV; + case "doc": + return DOC; + case "xml": + return XML; + case "zip": + return ZIP; + case "mp3": + return MP3; + case "ppt": + return PPT; + case "jpeg": + return JPEG; + case "tiff": + return TIFF; + case "pdf": + return PDF; + case "docx": + return DOCX; + case "png": + return PNG; + case "jsonl": + return JSONL; + case "csv": + return CSV; + case "jpg": + return JPG; + case "dcm": + return DCM; + case "json": + return JSON; + case "gif": + return GIF; + case "txt": + return TXT; + case "xls": + return XLS; + case "xlsx": + return XLSX; + case "tif": + return TIF; + default: + return new FileOutputProcessedFileExtension(Value.UNKNOWN, value); + } + } + + public enum Value { + MP3, + + WAV, + + PDF, + + TXT, + + CSV, + + JSON, + + JPG, + + JPEG, + + TIF, + + TIFF, + + PNG, + + BMP, + + XLS, + + XLSX, + + DOC, + + DOCX, + + PPT, + + PPTX, + + XML, + + DCM, + + JSONL, + + ZIP, + + GIF, + + UNKNOWN + } + + public interface Visitor { + T visitMp3(); + + T visitWav(); + + T visitPdf(); + + T visitTxt(); + + T visitCsv(); + + T visitJson(); + + T visitJpg(); + + T visitJpeg(); + + T visitTif(); + + T visitTiff(); + + T visitPng(); + + T visitBmp(); + + T visitXls(); + + T visitXlsx(); + + T visitDoc(); + + T visitDocx(); + + T visitPpt(); + + T visitPptx(); + + T visitXml(); + + T visitDcm(); + + T visitJsonl(); + + T visitZip(); + + T visitGif(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/FileOutputProcessedFileType.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/FileOutputProcessedFileType.java new file mode 100644 index 00000000..faeb8849 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/FileOutputProcessedFileType.java @@ -0,0 +1,64 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class FileOutputProcessedFileType { + private final Value value; + + private final String string; + + FileOutputProcessedFileType(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof FileOutputProcessedFileType + && this.string.equals(((FileOutputProcessedFileType) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static FileOutputProcessedFileType valueOf(String value) { + switch (value) { + default: + return new FileOutputProcessedFileType(Value.UNKNOWN, value); + } + } + + public enum Value { + UNKNOWN + } + + public interface Visitor { + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/ImageV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/ImageV2.java new file mode 100644 index 00000000..ebcc89de --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/ImageV2.java @@ -0,0 +1,173 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ImageV2.Builder.class) +public final class ImageV2 { + private final Optional outputProcessedImage; + + private final Optional outputOcrText; + + private final Optional maskingMethod; + + private final Map additionalProperties; + + private ImageV2( + Optional outputProcessedImage, + Optional outputOcrText, + Optional maskingMethod, + Map additionalProperties) { + this.outputProcessedImage = outputProcessedImage; + this.outputOcrText = outputOcrText; + this.maskingMethod = maskingMethod; + this.additionalProperties = additionalProperties; + } + + /** + * @return If true, includes processed image in the output. + */ + @JsonProperty("outputProcessedImage") + public Optional getOutputProcessedImage() { + return outputProcessedImage; + } + + /** + * @return If true, includes text detected by OCR in the response. + */ + @JsonProperty("outputOcrText") + public Optional getOutputOcrText() { + return outputOcrText; + } + + /** + * @return Method used to mask detected entities in the image. BLUR: blurs each detected entity so the sensitive content is unreadable while the rest of the image is preserved. BLACKBOX: covers each detected entity with a solid black box. + */ + @JsonProperty("maskingMethod") + public Optional getMaskingMethod() { + return maskingMethod; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ImageV2 && equalTo((ImageV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ImageV2 other) { + return outputProcessedImage.equals(other.outputProcessedImage) + && outputOcrText.equals(other.outputOcrText) + && maskingMethod.equals(other.maskingMethod); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.outputProcessedImage, this.outputOcrText, this.maskingMethod); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional outputProcessedImage = Optional.empty(); + + private Optional outputOcrText = Optional.empty(); + + private Optional maskingMethod = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(ImageV2 other) { + outputProcessedImage(other.getOutputProcessedImage()); + outputOcrText(other.getOutputOcrText()); + maskingMethod(other.getMaskingMethod()); + return this; + } + + /** + *

If true, includes processed image in the output.

+ */ + @JsonSetter(value = "outputProcessedImage", nulls = Nulls.SKIP) + public Builder outputProcessedImage(Optional outputProcessedImage) { + this.outputProcessedImage = outputProcessedImage; + return this; + } + + public Builder outputProcessedImage(Boolean outputProcessedImage) { + this.outputProcessedImage = Optional.ofNullable(outputProcessedImage); + return this; + } + + /** + *

If true, includes text detected by OCR in the response.

+ */ + @JsonSetter(value = "outputOcrText", nulls = Nulls.SKIP) + public Builder outputOcrText(Optional outputOcrText) { + this.outputOcrText = outputOcrText; + return this; + } + + public Builder outputOcrText(Boolean outputOcrText) { + this.outputOcrText = Optional.ofNullable(outputOcrText); + return this; + } + + /** + *

Method used to mask detected entities in the image. BLUR: blurs each detected entity so the sensitive content is unreadable while the rest of the image is preserved. BLACKBOX: covers each detected entity with a solid black box.

+ */ + @JsonSetter(value = "maskingMethod", nulls = Nulls.SKIP) + public Builder maskingMethod(Optional maskingMethod) { + this.maskingMethod = maskingMethod; + return this; + } + + public Builder maskingMethod(ImageV2MaskingMethod maskingMethod) { + this.maskingMethod = Optional.ofNullable(maskingMethod); + return this; + } + + public ImageV2 build() { + return new ImageV2(outputProcessedImage, outputOcrText, maskingMethod, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/ImageV2MaskingMethod.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/ImageV2MaskingMethod.java new file mode 100644 index 00000000..41f59512 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/ImageV2MaskingMethod.java @@ -0,0 +1,93 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class ImageV2MaskingMethod { + public static final ImageV2MaskingMethod BLUR = new ImageV2MaskingMethod(Value.BLUR, "BLUR"); + + public static final ImageV2MaskingMethod NONE = new ImageV2MaskingMethod(Value.NONE, "NONE"); + + public static final ImageV2MaskingMethod BLACKBOX = new ImageV2MaskingMethod(Value.BLACKBOX, "BLACKBOX"); + + private final Value value; + + private final String string; + + ImageV2MaskingMethod(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof ImageV2MaskingMethod && this.string.equals(((ImageV2MaskingMethod) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case BLUR: + return visitor.visitBlur(); + case NONE: + return visitor.visitNone(); + case BLACKBOX: + return visitor.visitBlackbox(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static ImageV2MaskingMethod valueOf(String value) { + switch (value) { + case "BLUR": + return BLUR; + case "NONE": + return NONE; + case "BLACKBOX": + return BLACKBOX; + default: + return new ImageV2MaskingMethod(Value.UNKNOWN, value); + } + } + + public enum Value { + NONE, + + BLUR, + + BLACKBOX, + + UNKNOWN + } + + public interface Visitor { + T visitNone(); + + T visitBlur(); + + T visitBlackbox(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/Media.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/Media.java new file mode 100644 index 00000000..7bba13bb --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/Media.java @@ -0,0 +1,153 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = Media.Builder.class) +public final class Media { + private final Optional audio; + + private final Optional document; + + private final Optional image; + + private final Map additionalProperties; + + private Media( + Optional audio, + Optional document, + Optional image, + Map additionalProperties) { + this.audio = audio; + this.document = document; + this.image = image; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("audio") + public Optional getAudio() { + return audio; + } + + @JsonProperty("document") + public Optional getDocument() { + return document; + } + + @JsonProperty("image") + public Optional getImage() { + return image; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Media && equalTo((Media) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(Media other) { + return audio.equals(other.audio) && document.equals(other.document) && image.equals(other.image); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.audio, this.document, this.image); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional audio = Optional.empty(); + + private Optional document = Optional.empty(); + + private Optional image = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(Media other) { + audio(other.getAudio()); + document(other.getDocument()); + image(other.getImage()); + return this; + } + + @JsonSetter(value = "audio", nulls = Nulls.SKIP) + public Builder audio(Optional audio) { + this.audio = audio; + return this; + } + + public Builder audio(AudioV2 audio) { + this.audio = Optional.ofNullable(audio); + return this; + } + + @JsonSetter(value = "document", nulls = Nulls.SKIP) + public Builder document(Optional document) { + this.document = document; + return this; + } + + public Builder document(DocumentV2 document) { + this.document = Optional.ofNullable(document); + return this; + } + + @JsonSetter(value = "image", nulls = Nulls.SKIP) + public Builder image(Optional image) { + this.image = image; + return this; + } + + public Builder image(ImageV2 image) { + this.image = Optional.ofNullable(image); + return this; + } + + public Media build() { + return new Media(audio, document, image, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/Metrics.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/Metrics.java new file mode 100644 index 00000000..ac525333 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/Metrics.java @@ -0,0 +1,263 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = Metrics.Builder.class) +public final class Metrics { + private final Optional size; + + private final Optional wordCount; + + private final Optional characterCount; + + private final Optional slides; + + private final Optional pages; + + private final Optional duration; + + private final Map additionalProperties; + + private Metrics( + Optional size, + Optional wordCount, + Optional characterCount, + Optional slides, + Optional pages, + Optional duration, + Map additionalProperties) { + this.size = size; + this.wordCount = wordCount; + this.characterCount = characterCount; + this.slides = slides; + this.pages = pages; + this.duration = duration; + this.additionalProperties = additionalProperties; + } + + /** + * @return Size of the input text(KB). + */ + @JsonProperty("size") + public Optional getSize() { + return size; + } + + /** + * @return Total number of words processed. + */ + @JsonProperty("wordCount") + public Optional getWordCount() { + return wordCount; + } + + /** + * @return Total number of characters processed. + */ + @JsonProperty("characterCount") + public Optional getCharacterCount() { + return characterCount; + } + + /** + * @return Total number of slides processed. + */ + @JsonProperty("slides") + public Optional getSlides() { + return slides; + } + + /** + * @return Total number of pages processed. + */ + @JsonProperty("pages") + public Optional getPages() { + return pages; + } + + /** + * @return Total duration processed(seconds). + */ + @JsonProperty("duration") + public Optional getDuration() { + return duration; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof Metrics && equalTo((Metrics) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(Metrics other) { + return size.equals(other.size) + && wordCount.equals(other.wordCount) + && characterCount.equals(other.characterCount) + && slides.equals(other.slides) + && pages.equals(other.pages) + && duration.equals(other.duration); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.size, this.wordCount, this.characterCount, this.slides, this.pages, this.duration); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional size = Optional.empty(); + + private Optional wordCount = Optional.empty(); + + private Optional characterCount = Optional.empty(); + + private Optional slides = Optional.empty(); + + private Optional pages = Optional.empty(); + + private Optional duration = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(Metrics other) { + size(other.getSize()); + wordCount(other.getWordCount()); + characterCount(other.getCharacterCount()); + slides(other.getSlides()); + pages(other.getPages()); + duration(other.getDuration()); + return this; + } + + /** + *

Size of the input text(KB).

+ */ + @JsonSetter(value = "size", nulls = Nulls.SKIP) + public Builder size(Optional size) { + this.size = size; + return this; + } + + public Builder size(Double size) { + this.size = Optional.ofNullable(size); + return this; + } + + /** + *

Total number of words processed.

+ */ + @JsonSetter(value = "wordCount", nulls = Nulls.SKIP) + public Builder wordCount(Optional wordCount) { + this.wordCount = wordCount; + return this; + } + + public Builder wordCount(Integer wordCount) { + this.wordCount = Optional.ofNullable(wordCount); + return this; + } + + /** + *

Total number of characters processed.

+ */ + @JsonSetter(value = "characterCount", nulls = Nulls.SKIP) + public Builder characterCount(Optional characterCount) { + this.characterCount = characterCount; + return this; + } + + public Builder characterCount(Integer characterCount) { + this.characterCount = Optional.ofNullable(characterCount); + return this; + } + + /** + *

Total number of slides processed.

+ */ + @JsonSetter(value = "slides", nulls = Nulls.SKIP) + public Builder slides(Optional slides) { + this.slides = slides; + return this; + } + + public Builder slides(Integer slides) { + this.slides = Optional.ofNullable(slides); + return this; + } + + /** + *

Total number of pages processed.

+ */ + @JsonSetter(value = "pages", nulls = Nulls.SKIP) + public Builder pages(Optional pages) { + this.pages = pages; + return this; + } + + public Builder pages(Integer pages) { + this.pages = Optional.ofNullable(pages); + return this; + } + + /** + *

Total duration processed(seconds).

+ */ + @JsonSetter(value = "duration", nulls = Nulls.SKIP) + public Builder duration(Optional duration) { + this.duration = duration; + return this; + } + + public Builder duration(Double duration) { + this.duration = Optional.ofNullable(duration); + return this; + } + + public Metrics build() { + return new Metrics(size, wordCount, characterCount, slides, pages, duration, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/RedactionLevel.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/RedactionLevel.java new file mode 100644 index 00000000..961c2f2f --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/RedactionLevel.java @@ -0,0 +1,204 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = RedactionLevel.Builder.class) +public final class RedactionLevel { + private final Optional tokenGroupName; + + private final Optional entityName; + + private final Optional redactionType; + + private final Optional redactionPattern; + + private final Map additionalProperties; + + private RedactionLevel( + Optional tokenGroupName, + Optional entityName, + Optional redactionType, + Optional redactionPattern, + Map additionalProperties) { + this.tokenGroupName = tokenGroupName; + this.entityName = entityName; + this.redactionType = redactionType; + this.redactionPattern = redactionPattern; + this.additionalProperties = additionalProperties; + } + + /** + * @return Name of the token group whose detokenized values should be masked (e.g., a token in personTokenGroup that detokenizes to John Doe is masked as JoXXXe). Use this for FlowVault. + */ + @JsonProperty("tokenGroupName") + public Optional getTokenGroupName() { + return tokenGroupName; + } + + /** + * @return Entity type whose detokenized values should be shown in plain text, redacted, or masked based on redactionType (e.g., a NAME entity that detokenizes to John Doe is masked as JoXXXe). Use this for SkyVault. + */ + @JsonProperty("entityName") + public Optional getEntityName() { + return entityName; + } + + /** + * @return Redaction method to apply to the detokenized value (e.g., for the value John Doe, MASKED transforms it into JoXXXe, REDACTED transforms it into *REDACTED*, and PLAIN_TEXT keeps it as John Doe). Use this for SkyVault. + */ + @JsonProperty("redactionType") + public Optional getRedactionType() { + return redactionType; + } + + /** + * @return Redaction method to apply to the detokenized value. Use the name of a redaction pattern configured in the vault (e.g., personMaskPattern transforms John Doe into JoXXXe). Use this for FlowVault. + */ + @JsonProperty("redactionPattern") + public Optional getRedactionPattern() { + return redactionPattern; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RedactionLevel && equalTo((RedactionLevel) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(RedactionLevel other) { + return tokenGroupName.equals(other.tokenGroupName) + && entityName.equals(other.entityName) + && redactionType.equals(other.redactionType) + && redactionPattern.equals(other.redactionPattern); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.tokenGroupName, this.entityName, this.redactionType, this.redactionPattern); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional tokenGroupName = Optional.empty(); + + private Optional entityName = Optional.empty(); + + private Optional redactionType = Optional.empty(); + + private Optional redactionPattern = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(RedactionLevel other) { + tokenGroupName(other.getTokenGroupName()); + entityName(other.getEntityName()); + redactionType(other.getRedactionType()); + redactionPattern(other.getRedactionPattern()); + return this; + } + + /** + *

Name of the token group whose detokenized values should be masked (e.g., a token in personTokenGroup that detokenizes to John Doe is masked as JoXXXe). Use this for FlowVault.

+ */ + @JsonSetter(value = "tokenGroupName", nulls = Nulls.SKIP) + public Builder tokenGroupName(Optional tokenGroupName) { + this.tokenGroupName = tokenGroupName; + return this; + } + + public Builder tokenGroupName(String tokenGroupName) { + this.tokenGroupName = Optional.ofNullable(tokenGroupName); + return this; + } + + /** + *

Entity type whose detokenized values should be shown in plain text, redacted, or masked based on redactionType (e.g., a NAME entity that detokenizes to John Doe is masked as JoXXXe). Use this for SkyVault.

+ */ + @JsonSetter(value = "entityName", nulls = Nulls.SKIP) + public Builder entityName(Optional entityName) { + this.entityName = entityName; + return this; + } + + public Builder entityName(RedactionLevelEntityName entityName) { + this.entityName = Optional.ofNullable(entityName); + return this; + } + + /** + *

Redaction method to apply to the detokenized value (e.g., for the value John Doe, MASKED transforms it into JoXXXe, REDACTED transforms it into *REDACTED*, and PLAIN_TEXT keeps it as John Doe). Use this for SkyVault.

+ */ + @JsonSetter(value = "redactionType", nulls = Nulls.SKIP) + public Builder redactionType(Optional redactionType) { + this.redactionType = redactionType; + return this; + } + + public Builder redactionType(RedactionLevelRedactionType redactionType) { + this.redactionType = Optional.ofNullable(redactionType); + return this; + } + + /** + *

Redaction method to apply to the detokenized value. Use the name of a redaction pattern configured in the vault (e.g., personMaskPattern transforms John Doe into JoXXXe). Use this for FlowVault.

+ */ + @JsonSetter(value = "redactionPattern", nulls = Nulls.SKIP) + public Builder redactionPattern(Optional redactionPattern) { + this.redactionPattern = redactionPattern; + return this; + } + + public Builder redactionPattern(String redactionPattern) { + this.redactionPattern = Optional.ofNullable(redactionPattern); + return this; + } + + public RedactionLevel build() { + return new RedactionLevel( + tokenGroupName, entityName, redactionType, redactionPattern, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/RedactionLevelEntityName.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/RedactionLevelEntityName.java new file mode 100644 index 00000000..a4e9acb6 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/RedactionLevelEntityName.java @@ -0,0 +1,812 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class RedactionLevelEntityName { + public static final RedactionLevelEntityName PHONE_NUMBER = + new RedactionLevelEntityName(Value.PHONE_NUMBER, "PHONE_NUMBER"); + + public static final RedactionLevelEntityName USERNAME = new RedactionLevelEntityName(Value.USERNAME, "USERNAME"); + + public static final RedactionLevelEntityName VEHICLE_ID = + new RedactionLevelEntityName(Value.VEHICLE_ID, "VEHICLE_ID"); + + public static final RedactionLevelEntityName RELIGION = new RedactionLevelEntityName(Value.RELIGION, "RELIGION"); + + public static final RedactionLevelEntityName ORIGIN = new RedactionLevelEntityName(Value.ORIGIN, "ORIGIN"); + + public static final RedactionLevelEntityName PASSPORT_NUMBER = + new RedactionLevelEntityName(Value.PASSPORT_NUMBER, "PASSPORT_NUMBER"); + + public static final RedactionLevelEntityName YEAR = new RedactionLevelEntityName(Value.YEAR, "YEAR"); + + public static final RedactionLevelEntityName CONDITION = new RedactionLevelEntityName(Value.CONDITION, "CONDITION"); + + public static final RedactionLevelEntityName LOCATION = new RedactionLevelEntityName(Value.LOCATION, "LOCATION"); + + public static final RedactionLevelEntityName LOCATION_CITY = + new RedactionLevelEntityName(Value.LOCATION_CITY, "LOCATION_CITY"); + + public static final RedactionLevelEntityName LOCATION_COUNTRY = + new RedactionLevelEntityName(Value.LOCATION_COUNTRY, "LOCATION_COUNTRY"); + + public static final RedactionLevelEntityName PROJECT = new RedactionLevelEntityName(Value.PROJECT, "PROJECT"); + + public static final RedactionLevelEntityName LOCATION_ZIP = + new RedactionLevelEntityName(Value.LOCATION_ZIP, "LOCATION_ZIP"); + + public static final RedactionLevelEntityName NAME_MEDICAL_PROFESSIONAL = + new RedactionLevelEntityName(Value.NAME_MEDICAL_PROFESSIONAL, "NAME_MEDICAL_PROFESSIONAL"); + + public static final RedactionLevelEntityName BANK_ACCOUNT = + new RedactionLevelEntityName(Value.BANK_ACCOUNT, "BANK_ACCOUNT"); + + public static final RedactionLevelEntityName OCCUPATION = + new RedactionLevelEntityName(Value.OCCUPATION, "OCCUPATION"); + + public static final RedactionLevelEntityName TIME = new RedactionLevelEntityName(Value.TIME, "TIME"); + + public static final RedactionLevelEntityName LOCATION_STATE = + new RedactionLevelEntityName(Value.LOCATION_STATE, "LOCATION_STATE"); + + public static final RedactionLevelEntityName EVENT = new RedactionLevelEntityName(Value.EVENT, "EVENT"); + + public static final RedactionLevelEntityName POLITICAL_AFFILIATION = + new RedactionLevelEntityName(Value.POLITICAL_AFFILIATION, "POLITICAL_AFFILIATION"); + + public static final RedactionLevelEntityName LANGUAGE = new RedactionLevelEntityName(Value.LANGUAGE, "LANGUAGE"); + + public static final RedactionLevelEntityName ZODIAC_SIGN = + new RedactionLevelEntityName(Value.ZODIAC_SIGN, "ZODIAC_SIGN"); + + public static final RedactionLevelEntityName PRODUCT = new RedactionLevelEntityName(Value.PRODUCT, "PRODUCT"); + + public static final RedactionLevelEntityName SEXUALITY = new RedactionLevelEntityName(Value.SEXUALITY, "SEXUALITY"); + + public static final RedactionLevelEntityName DURATION = new RedactionLevelEntityName(Value.DURATION, "DURATION"); + + public static final RedactionLevelEntityName PHYSICAL_ATTRIBUTE = + new RedactionLevelEntityName(Value.PHYSICAL_ATTRIBUTE, "PHYSICAL_ATTRIBUTE"); + + public static final RedactionLevelEntityName LOCATION_ADDRESS_STREET = + new RedactionLevelEntityName(Value.LOCATION_ADDRESS_STREET, "LOCATION_ADDRESS_STREET"); + + public static final RedactionLevelEntityName MEDICAL_PROCESS = + new RedactionLevelEntityName(Value.MEDICAL_PROCESS, "MEDICAL_PROCESS"); + + public static final RedactionLevelEntityName LOCATION_ADDRESS = + new RedactionLevelEntityName(Value.LOCATION_ADDRESS, "LOCATION_ADDRESS"); + + public static final RedactionLevelEntityName FILENAME = new RedactionLevelEntityName(Value.FILENAME, "FILENAME"); + + public static final RedactionLevelEntityName DOB = new RedactionLevelEntityName(Value.DOB, "DOB"); + + public static final RedactionLevelEntityName ORGANIZATION_MEDICAL_FACILITY = + new RedactionLevelEntityName(Value.ORGANIZATION_MEDICAL_FACILITY, "ORGANIZATION_MEDICAL_FACILITY"); + + public static final RedactionLevelEntityName ROUTING_NUMBER = + new RedactionLevelEntityName(Value.ROUTING_NUMBER, "ROUTING_NUMBER"); + + public static final RedactionLevelEntityName HEALTHCARE_NUMBER = + new RedactionLevelEntityName(Value.HEALTHCARE_NUMBER, "HEALTHCARE_NUMBER"); + + public static final RedactionLevelEntityName GENDER = new RedactionLevelEntityName(Value.GENDER, "GENDER"); + + public static final RedactionLevelEntityName NAME = new RedactionLevelEntityName(Value.NAME, "NAME"); + + public static final RedactionLevelEntityName DRUG = new RedactionLevelEntityName(Value.DRUG, "DRUG"); + + public static final RedactionLevelEntityName NONE = new RedactionLevelEntityName(Value.NONE, "NONE"); + + public static final RedactionLevelEntityName DATE_INTERVAL = + new RedactionLevelEntityName(Value.DATE_INTERVAL, "DATE_INTERVAL"); + + public static final RedactionLevelEntityName MARITAL_STATUS = + new RedactionLevelEntityName(Value.MARITAL_STATUS, "MARITAL_STATUS"); + + public static final RedactionLevelEntityName CORPORATE_ACTION = + new RedactionLevelEntityName(Value.CORPORATE_ACTION, "CORPORATE_ACTION"); + + public static final RedactionLevelEntityName AGE = new RedactionLevelEntityName(Value.AGE, "AGE"); + + public static final RedactionLevelEntityName CVV = new RedactionLevelEntityName(Value.CVV, "CVV"); + + public static final RedactionLevelEntityName NAME_GIVEN = + new RedactionLevelEntityName(Value.NAME_GIVEN, "NAME_GIVEN"); + + public static final RedactionLevelEntityName DATE = new RedactionLevelEntityName(Value.DATE, "DATE"); + + public static final RedactionLevelEntityName DAY = new RedactionLevelEntityName(Value.DAY, "DAY"); + + public static final RedactionLevelEntityName EFFECT = new RedactionLevelEntityName(Value.EFFECT, "EFFECT"); + + public static final RedactionLevelEntityName MEDICAL_CODE = + new RedactionLevelEntityName(Value.MEDICAL_CODE, "MEDICAL_CODE"); + + public static final RedactionLevelEntityName NUMERICAL_PII = + new RedactionLevelEntityName(Value.NUMERICAL_PII, "NUMERICAL_PII"); + + public static final RedactionLevelEntityName TREND = new RedactionLevelEntityName(Value.TREND, "TREND"); + + public static final RedactionLevelEntityName INJURY = new RedactionLevelEntityName(Value.INJURY, "INJURY"); + + public static final RedactionLevelEntityName DRIVER_LICENSE = + new RedactionLevelEntityName(Value.DRIVER_LICENSE, "DRIVER_LICENSE"); + + public static final RedactionLevelEntityName ALL = new RedactionLevelEntityName(Value.ALL, "ALL"); + + public static final RedactionLevelEntityName ORGANIZATION_ID = + new RedactionLevelEntityName(Value.ORGANIZATION_ID, "ORGANIZATION_ID"); + + public static final RedactionLevelEntityName PASSWORD = new RedactionLevelEntityName(Value.PASSWORD, "PASSWORD"); + + public static final RedactionLevelEntityName DOSE = new RedactionLevelEntityName(Value.DOSE, "DOSE"); + + public static final RedactionLevelEntityName NAME_FAMILY = + new RedactionLevelEntityName(Value.NAME_FAMILY, "NAME_FAMILY"); + + public static final RedactionLevelEntityName MONEY = new RedactionLevelEntityName(Value.MONEY, "MONEY"); + + public static final RedactionLevelEntityName URL = new RedactionLevelEntityName(Value.URL, "URL"); + + public static final RedactionLevelEntityName LOCATION_COORDINATE = + new RedactionLevelEntityName(Value.LOCATION_COORDINATE, "LOCATION_COORDINATE"); + + public static final RedactionLevelEntityName FINANCIAL_METRIC = + new RedactionLevelEntityName(Value.FINANCIAL_METRIC, "FINANCIAL_METRIC"); + + public static final RedactionLevelEntityName ORGANIZATION = + new RedactionLevelEntityName(Value.ORGANIZATION, "ORGANIZATION"); + + public static final RedactionLevelEntityName IP_ADDRESS = + new RedactionLevelEntityName(Value.IP_ADDRESS, "IP_ADDRESS"); + + public static final RedactionLevelEntityName BLOOD_TYPE = + new RedactionLevelEntityName(Value.BLOOD_TYPE, "BLOOD_TYPE"); + + public static final RedactionLevelEntityName MONTH = new RedactionLevelEntityName(Value.MONTH, "MONTH"); + + public static final RedactionLevelEntityName CREDIT_CARD = + new RedactionLevelEntityName(Value.CREDIT_CARD, "CREDIT_CARD"); + + public static final RedactionLevelEntityName ACCOUNT_NUMBER = + new RedactionLevelEntityName(Value.ACCOUNT_NUMBER, "ACCOUNT_NUMBER"); + + public static final RedactionLevelEntityName CREDIT_CARD_EXPIRATION = + new RedactionLevelEntityName(Value.CREDIT_CARD_EXPIRATION, "CREDIT_CARD_EXPIRATION"); + + public static final RedactionLevelEntityName SSN = new RedactionLevelEntityName(Value.SSN, "SSN"); + + public static final RedactionLevelEntityName EMAIL_ADDRESS = + new RedactionLevelEntityName(Value.EMAIL_ADDRESS, "EMAIL_ADDRESS"); + + public static final RedactionLevelEntityName STATISTICS = + new RedactionLevelEntityName(Value.STATISTICS, "STATISTICS"); + + private final Value value; + + private final String string; + + RedactionLevelEntityName(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof RedactionLevelEntityName + && this.string.equals(((RedactionLevelEntityName) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case PHONE_NUMBER: + return visitor.visitPhoneNumber(); + case USERNAME: + return visitor.visitUsername(); + case VEHICLE_ID: + return visitor.visitVehicleId(); + case RELIGION: + return visitor.visitReligion(); + case ORIGIN: + return visitor.visitOrigin(); + case PASSPORT_NUMBER: + return visitor.visitPassportNumber(); + case YEAR: + return visitor.visitYear(); + case CONDITION: + return visitor.visitCondition(); + case LOCATION: + return visitor.visitLocation(); + case LOCATION_CITY: + return visitor.visitLocationCity(); + case LOCATION_COUNTRY: + return visitor.visitLocationCountry(); + case PROJECT: + return visitor.visitProject(); + case LOCATION_ZIP: + return visitor.visitLocationZip(); + case NAME_MEDICAL_PROFESSIONAL: + return visitor.visitNameMedicalProfessional(); + case BANK_ACCOUNT: + return visitor.visitBankAccount(); + case OCCUPATION: + return visitor.visitOccupation(); + case TIME: + return visitor.visitTime(); + case LOCATION_STATE: + return visitor.visitLocationState(); + case EVENT: + return visitor.visitEvent(); + case POLITICAL_AFFILIATION: + return visitor.visitPoliticalAffiliation(); + case LANGUAGE: + return visitor.visitLanguage(); + case ZODIAC_SIGN: + return visitor.visitZodiacSign(); + case PRODUCT: + return visitor.visitProduct(); + case SEXUALITY: + return visitor.visitSexuality(); + case DURATION: + return visitor.visitDuration(); + case PHYSICAL_ATTRIBUTE: + return visitor.visitPhysicalAttribute(); + case LOCATION_ADDRESS_STREET: + return visitor.visitLocationAddressStreet(); + case MEDICAL_PROCESS: + return visitor.visitMedicalProcess(); + case LOCATION_ADDRESS: + return visitor.visitLocationAddress(); + case FILENAME: + return visitor.visitFilename(); + case DOB: + return visitor.visitDob(); + case ORGANIZATION_MEDICAL_FACILITY: + return visitor.visitOrganizationMedicalFacility(); + case ROUTING_NUMBER: + return visitor.visitRoutingNumber(); + case HEALTHCARE_NUMBER: + return visitor.visitHealthcareNumber(); + case GENDER: + return visitor.visitGender(); + case NAME: + return visitor.visitName(); + case DRUG: + return visitor.visitDrug(); + case NONE: + return visitor.visitNone(); + case DATE_INTERVAL: + return visitor.visitDateInterval(); + case MARITAL_STATUS: + return visitor.visitMaritalStatus(); + case CORPORATE_ACTION: + return visitor.visitCorporateAction(); + case AGE: + return visitor.visitAge(); + case CVV: + return visitor.visitCvv(); + case NAME_GIVEN: + return visitor.visitNameGiven(); + case DATE: + return visitor.visitDate(); + case DAY: + return visitor.visitDay(); + case EFFECT: + return visitor.visitEffect(); + case MEDICAL_CODE: + return visitor.visitMedicalCode(); + case NUMERICAL_PII: + return visitor.visitNumericalPii(); + case TREND: + return visitor.visitTrend(); + case INJURY: + return visitor.visitInjury(); + case DRIVER_LICENSE: + return visitor.visitDriverLicense(); + case ALL: + return visitor.visitAll(); + case ORGANIZATION_ID: + return visitor.visitOrganizationId(); + case PASSWORD: + return visitor.visitPassword(); + case DOSE: + return visitor.visitDose(); + case NAME_FAMILY: + return visitor.visitNameFamily(); + case MONEY: + return visitor.visitMoney(); + case URL: + return visitor.visitUrl(); + case LOCATION_COORDINATE: + return visitor.visitLocationCoordinate(); + case FINANCIAL_METRIC: + return visitor.visitFinancialMetric(); + case ORGANIZATION: + return visitor.visitOrganization(); + case IP_ADDRESS: + return visitor.visitIpAddress(); + case BLOOD_TYPE: + return visitor.visitBloodType(); + case MONTH: + return visitor.visitMonth(); + case CREDIT_CARD: + return visitor.visitCreditCard(); + case ACCOUNT_NUMBER: + return visitor.visitAccountNumber(); + case CREDIT_CARD_EXPIRATION: + return visitor.visitCreditCardExpiration(); + case SSN: + return visitor.visitSsn(); + case EMAIL_ADDRESS: + return visitor.visitEmailAddress(); + case STATISTICS: + return visitor.visitStatistics(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static RedactionLevelEntityName valueOf(String value) { + switch (value) { + case "PHONE_NUMBER": + return PHONE_NUMBER; + case "USERNAME": + return USERNAME; + case "VEHICLE_ID": + return VEHICLE_ID; + case "RELIGION": + return RELIGION; + case "ORIGIN": + return ORIGIN; + case "PASSPORT_NUMBER": + return PASSPORT_NUMBER; + case "YEAR": + return YEAR; + case "CONDITION": + return CONDITION; + case "LOCATION": + return LOCATION; + case "LOCATION_CITY": + return LOCATION_CITY; + case "LOCATION_COUNTRY": + return LOCATION_COUNTRY; + case "PROJECT": + return PROJECT; + case "LOCATION_ZIP": + return LOCATION_ZIP; + case "NAME_MEDICAL_PROFESSIONAL": + return NAME_MEDICAL_PROFESSIONAL; + case "BANK_ACCOUNT": + return BANK_ACCOUNT; + case "OCCUPATION": + return OCCUPATION; + case "TIME": + return TIME; + case "LOCATION_STATE": + return LOCATION_STATE; + case "EVENT": + return EVENT; + case "POLITICAL_AFFILIATION": + return POLITICAL_AFFILIATION; + case "LANGUAGE": + return LANGUAGE; + case "ZODIAC_SIGN": + return ZODIAC_SIGN; + case "PRODUCT": + return PRODUCT; + case "SEXUALITY": + return SEXUALITY; + case "DURATION": + return DURATION; + case "PHYSICAL_ATTRIBUTE": + return PHYSICAL_ATTRIBUTE; + case "LOCATION_ADDRESS_STREET": + return LOCATION_ADDRESS_STREET; + case "MEDICAL_PROCESS": + return MEDICAL_PROCESS; + case "LOCATION_ADDRESS": + return LOCATION_ADDRESS; + case "FILENAME": + return FILENAME; + case "DOB": + return DOB; + case "ORGANIZATION_MEDICAL_FACILITY": + return ORGANIZATION_MEDICAL_FACILITY; + case "ROUTING_NUMBER": + return ROUTING_NUMBER; + case "HEALTHCARE_NUMBER": + return HEALTHCARE_NUMBER; + case "GENDER": + return GENDER; + case "NAME": + return NAME; + case "DRUG": + return DRUG; + case "NONE": + return NONE; + case "DATE_INTERVAL": + return DATE_INTERVAL; + case "MARITAL_STATUS": + return MARITAL_STATUS; + case "CORPORATE_ACTION": + return CORPORATE_ACTION; + case "AGE": + return AGE; + case "CVV": + return CVV; + case "NAME_GIVEN": + return NAME_GIVEN; + case "DATE": + return DATE; + case "DAY": + return DAY; + case "EFFECT": + return EFFECT; + case "MEDICAL_CODE": + return MEDICAL_CODE; + case "NUMERICAL_PII": + return NUMERICAL_PII; + case "TREND": + return TREND; + case "INJURY": + return INJURY; + case "DRIVER_LICENSE": + return DRIVER_LICENSE; + case "ALL": + return ALL; + case "ORGANIZATION_ID": + return ORGANIZATION_ID; + case "PASSWORD": + return PASSWORD; + case "DOSE": + return DOSE; + case "NAME_FAMILY": + return NAME_FAMILY; + case "MONEY": + return MONEY; + case "URL": + return URL; + case "LOCATION_COORDINATE": + return LOCATION_COORDINATE; + case "FINANCIAL_METRIC": + return FINANCIAL_METRIC; + case "ORGANIZATION": + return ORGANIZATION; + case "IP_ADDRESS": + return IP_ADDRESS; + case "BLOOD_TYPE": + return BLOOD_TYPE; + case "MONTH": + return MONTH; + case "CREDIT_CARD": + return CREDIT_CARD; + case "ACCOUNT_NUMBER": + return ACCOUNT_NUMBER; + case "CREDIT_CARD_EXPIRATION": + return CREDIT_CARD_EXPIRATION; + case "SSN": + return SSN; + case "EMAIL_ADDRESS": + return EMAIL_ADDRESS; + case "STATISTICS": + return STATISTICS; + default: + return new RedactionLevelEntityName(Value.UNKNOWN, value); + } + } + + public enum Value { + NONE, + + BANK_ACCOUNT, + + CREDIT_CARD, + + CREDIT_CARD_EXPIRATION, + + CVV, + + DATE, + + DATE_INTERVAL, + + DOB, + + DRIVER_LICENSE, + + EMAIL_ADDRESS, + + HEALTHCARE_NUMBER, + + IP_ADDRESS, + + LOCATION, + + NAME, + + NUMERICAL_PII, + + PHONE_NUMBER, + + SSN, + + URL, + + VEHICLE_ID, + + MEDICAL_CODE, + + NAME_FAMILY, + + NAME_GIVEN, + + ACCOUNT_NUMBER, + + EVENT, + + FILENAME, + + GENDER, + + LANGUAGE, + + LOCATION_ADDRESS, + + LOCATION_CITY, + + LOCATION_COORDINATE, + + LOCATION_COUNTRY, + + LOCATION_STATE, + + LOCATION_ZIP, + + MARITAL_STATUS, + + MONEY, + + NAME_MEDICAL_PROFESSIONAL, + + OCCUPATION, + + ORGANIZATION, + + ORGANIZATION_MEDICAL_FACILITY, + + ORIGIN, + + PASSPORT_NUMBER, + + PASSWORD, + + PHYSICAL_ATTRIBUTE, + + POLITICAL_AFFILIATION, + + RELIGION, + + TIME, + + USERNAME, + + ZODIAC_SIGN, + + BLOOD_TYPE, + + CONDITION, + + DOSE, + + DRUG, + + INJURY, + + MEDICAL_PROCESS, + + STATISTICS, + + ROUTING_NUMBER, + + CORPORATE_ACTION, + + FINANCIAL_METRIC, + + PRODUCT, + + TREND, + + DURATION, + + LOCATION_ADDRESS_STREET, + + AGE, + + SEXUALITY, + + EFFECT, + + PROJECT, + + ORGANIZATION_ID, + + DAY, + + MONTH, + + YEAR, + + ALL, + + UNKNOWN + } + + public interface Visitor { + T visitNone(); + + T visitBankAccount(); + + T visitCreditCard(); + + T visitCreditCardExpiration(); + + T visitCvv(); + + T visitDate(); + + T visitDateInterval(); + + T visitDob(); + + T visitDriverLicense(); + + T visitEmailAddress(); + + T visitHealthcareNumber(); + + T visitIpAddress(); + + T visitLocation(); + + T visitName(); + + T visitNumericalPii(); + + T visitPhoneNumber(); + + T visitSsn(); + + T visitUrl(); + + T visitVehicleId(); + + T visitMedicalCode(); + + T visitNameFamily(); + + T visitNameGiven(); + + T visitAccountNumber(); + + T visitEvent(); + + T visitFilename(); + + T visitGender(); + + T visitLanguage(); + + T visitLocationAddress(); + + T visitLocationCity(); + + T visitLocationCoordinate(); + + T visitLocationCountry(); + + T visitLocationState(); + + T visitLocationZip(); + + T visitMaritalStatus(); + + T visitMoney(); + + T visitNameMedicalProfessional(); + + T visitOccupation(); + + T visitOrganization(); + + T visitOrganizationMedicalFacility(); + + T visitOrigin(); + + T visitPassportNumber(); + + T visitPassword(); + + T visitPhysicalAttribute(); + + T visitPoliticalAffiliation(); + + T visitReligion(); + + T visitTime(); + + T visitUsername(); + + T visitZodiacSign(); + + T visitBloodType(); + + T visitCondition(); + + T visitDose(); + + T visitDrug(); + + T visitInjury(); + + T visitMedicalProcess(); + + T visitStatistics(); + + T visitRoutingNumber(); + + T visitCorporateAction(); + + T visitFinancialMetric(); + + T visitProduct(); + + T visitTrend(); + + T visitDuration(); + + T visitLocationAddressStreet(); + + T visitAge(); + + T visitSexuality(); + + T visitEffect(); + + T visitProject(); + + T visitOrganizationId(); + + T visitDay(); + + T visitMonth(); + + T visitYear(); + + T visitAll(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/RedactionLevelRedactionType.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/RedactionLevelRedactionType.java new file mode 100644 index 00000000..5e9b0807 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/RedactionLevelRedactionType.java @@ -0,0 +1,106 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class RedactionLevelRedactionType { + public static final RedactionLevelRedactionType DEFAULT = new RedactionLevelRedactionType(Value.DEFAULT, "DEFAULT"); + + public static final RedactionLevelRedactionType PLAIN_TEXT = + new RedactionLevelRedactionType(Value.PLAIN_TEXT, "PLAIN_TEXT"); + + public static final RedactionLevelRedactionType MASKED = new RedactionLevelRedactionType(Value.MASKED, "MASKED"); + + public static final RedactionLevelRedactionType REDACTED = + new RedactionLevelRedactionType(Value.REDACTED, "REDACTED"); + + private final Value value; + + private final String string; + + RedactionLevelRedactionType(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof RedactionLevelRedactionType + && this.string.equals(((RedactionLevelRedactionType) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case DEFAULT: + return visitor.visitDefault(); + case PLAIN_TEXT: + return visitor.visitPlainText(); + case MASKED: + return visitor.visitMasked(); + case REDACTED: + return visitor.visitRedacted(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static RedactionLevelRedactionType valueOf(String value) { + switch (value) { + case "DEFAULT": + return DEFAULT; + case "PLAIN_TEXT": + return PLAIN_TEXT; + case "MASKED": + return MASKED; + case "REDACTED": + return REDACTED; + default: + return new RedactionLevelRedactionType(Value.UNKNOWN, value); + } + } + + public enum Value { + DEFAULT, + + REDACTED, + + MASKED, + + PLAIN_TEXT, + + UNKNOWN + } + + public interface Visitor { + T visitDefault(); + + T visitRedacted(); + + T visitMasked(); + + T visitPlainText(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyFileResponseV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyFileResponseV2.java new file mode 100644 index 00000000..b7b8e3e5 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyFileResponseV2.java @@ -0,0 +1,198 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ReidentifyFileResponseV2.Builder.class) +public final class ReidentifyFileResponseV2 { + private final Optional status; + + private final Optional outputType; + + private final Optional> output; + + private final Optional metrics; + + private final Map additionalProperties; + + private ReidentifyFileResponseV2( + Optional status, + Optional outputType, + Optional> output, + Optional metrics, + Map additionalProperties) { + this.status = status; + this.outputType = outputType; + this.output = output; + this.metrics = metrics; + this.additionalProperties = additionalProperties; + } + + /** + * @return Status of the reidentification request. + */ + @JsonProperty("status") + public Optional getStatus() { + return status; + } + + /** + * @return Data source of the output file. BASE64: Base64-encoded file string. SKYFLOW_ID: Reference file by vault ID. PRESIGNED_URL: S3 presigned URL of input file. + */ + @JsonProperty("outputType") + public Optional getOutputType() { + return outputType; + } + + /** + * @return Details of output files + */ + @JsonProperty("output") + public Optional> getOutput() { + return output; + } + + @JsonProperty("metrics") + public Optional getMetrics() { + return metrics; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ReidentifyFileResponseV2 && equalTo((ReidentifyFileResponseV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ReidentifyFileResponseV2 other) { + return status.equals(other.status) + && outputType.equals(other.outputType) + && output.equals(other.output) + && metrics.equals(other.metrics); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.status, this.outputType, this.output, this.metrics); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional status = Optional.empty(); + + private Optional outputType = Optional.empty(); + + private Optional> output = Optional.empty(); + + private Optional metrics = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(ReidentifyFileResponseV2 other) { + status(other.getStatus()); + outputType(other.getOutputType()); + output(other.getOutput()); + metrics(other.getMetrics()); + return this; + } + + /** + *

Status of the reidentification request.

+ */ + @JsonSetter(value = "status", nulls = Nulls.SKIP) + public Builder status(Optional status) { + this.status = status; + return this; + } + + public Builder status(ReidentifyFileResponseV2Status status) { + this.status = Optional.ofNullable(status); + return this; + } + + /** + *

Data source of the output file. BASE64: Base64-encoded file string. SKYFLOW_ID: Reference file by vault ID. PRESIGNED_URL: S3 presigned URL of input file.

+ */ + @JsonSetter(value = "outputType", nulls = Nulls.SKIP) + public Builder outputType(Optional outputType) { + this.outputType = outputType; + return this; + } + + public Builder outputType(ReidentifyFileResponseV2OutputType outputType) { + this.outputType = Optional.ofNullable(outputType); + return this; + } + + /** + *

Details of output files

+ */ + @JsonSetter(value = "output", nulls = Nulls.SKIP) + public Builder output(Optional> output) { + this.output = output; + return this; + } + + public Builder output(List output) { + this.output = Optional.ofNullable(output); + return this; + } + + @JsonSetter(value = "metrics", nulls = Nulls.SKIP) + public Builder metrics(Optional metrics) { + this.metrics = metrics; + return this; + } + + public Builder metrics(Metrics metrics) { + this.metrics = Optional.ofNullable(metrics); + return this; + } + + public ReidentifyFileResponseV2 build() { + return new ReidentifyFileResponseV2(status, outputType, output, metrics, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyFileResponseV2OutputType.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyFileResponseV2OutputType.java new file mode 100644 index 00000000..ad4ea20f --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyFileResponseV2OutputType.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class ReidentifyFileResponseV2OutputType { + public static final ReidentifyFileResponseV2OutputType BASE64 = + new ReidentifyFileResponseV2OutputType(Value.BASE64, "BASE64"); + + public static final ReidentifyFileResponseV2OutputType SKYFLOW_ID = + new ReidentifyFileResponseV2OutputType(Value.SKYFLOW_ID, "SKYFLOW_ID"); + + public static final ReidentifyFileResponseV2OutputType PRESIGNED_URL = + new ReidentifyFileResponseV2OutputType(Value.PRESIGNED_URL, "PRESIGNED_URL"); + + private final Value value; + + private final String string; + + ReidentifyFileResponseV2OutputType(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof ReidentifyFileResponseV2OutputType + && this.string.equals(((ReidentifyFileResponseV2OutputType) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case BASE64: + return visitor.visitBase64(); + case SKYFLOW_ID: + return visitor.visitSkyflowId(); + case PRESIGNED_URL: + return visitor.visitPresignedUrl(); + case UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static ReidentifyFileResponseV2OutputType valueOf(String value) { + switch (value) { + case "BASE64": + return BASE64; + case "SKYFLOW_ID": + return SKYFLOW_ID; + case "PRESIGNED_URL": + return PRESIGNED_URL; + default: + return new ReidentifyFileResponseV2OutputType(Value.UNKNOWN, value); + } + } + + public enum Value { + BASE64, + + SKYFLOW_ID, + + PRESIGNED_URL, + + UNKNOWN + } + + public interface Visitor { + T visitBase64(); + + T visitSkyflowId(); + + T visitPresignedUrl(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyFileResponseV2Status.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyFileResponseV2Status.java new file mode 100644 index 00000000..250bd1a9 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyFileResponseV2Status.java @@ -0,0 +1,119 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +public final class ReidentifyFileResponseV2Status { + public static final ReidentifyFileResponseV2Status IN_PROGRESS = + new ReidentifyFileResponseV2Status(Value.IN_PROGRESS, "IN_PROGRESS"); + + public static final ReidentifyFileResponseV2Status QUEUED = + new ReidentifyFileResponseV2Status(Value.QUEUED, "QUEUED"); + + public static final ReidentifyFileResponseV2Status SUCCESS = + new ReidentifyFileResponseV2Status(Value.SUCCESS, "SUCCESS"); + + public static final ReidentifyFileResponseV2Status UNKNOWN = + new ReidentifyFileResponseV2Status(Value.UNKNOWN, "UNKNOWN"); + + public static final ReidentifyFileResponseV2Status FAILED = + new ReidentifyFileResponseV2Status(Value.FAILED, "FAILED"); + + private final Value value; + + private final String string; + + ReidentifyFileResponseV2Status(Value value, String string) { + this.value = value; + this.string = string; + } + + public Value getEnumValue() { + return value; + } + + @java.lang.Override + @JsonValue + public String toString() { + return this.string; + } + + @java.lang.Override + public boolean equals(Object other) { + return (this == other) + || (other instanceof ReidentifyFileResponseV2Status + && this.string.equals(((ReidentifyFileResponseV2Status) other).string)); + } + + @java.lang.Override + public int hashCode() { + return this.string.hashCode(); + } + + public T visit(Visitor visitor) { + switch (value) { + case IN_PROGRESS: + return visitor.visitInProgress(); + case QUEUED: + return visitor.visitQueued(); + case SUCCESS: + return visitor.visitSuccess(); + case UNKNOWN: + return visitor.visitUnknown(); + case FAILED: + return visitor.visitFailed(); + case _UNKNOWN: + default: + return visitor.visitUnknown(string); + } + } + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static ReidentifyFileResponseV2Status valueOf(String value) { + switch (value) { + case "IN_PROGRESS": + return IN_PROGRESS; + case "QUEUED": + return QUEUED; + case "SUCCESS": + return SUCCESS; + case "UNKNOWN": + return UNKNOWN; + case "FAILED": + return FAILED; + default: + return new ReidentifyFileResponseV2Status(Value._UNKNOWN, value); + } + } + + public enum Value { + UNKNOWN, + + FAILED, + + SUCCESS, + + IN_PROGRESS, + + QUEUED, + + _UNKNOWN + } + + public interface Visitor { + T visitUnknown(); + + T visitFailed(); + + T visitSuccess(); + + T visitInProgress(); + + T visitQueued(); + + T visitUnknown(String unknownType); + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyStringResponseV2.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyStringResponseV2.java new file mode 100644 index 00000000..2c836067 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/ReidentifyStringResponseV2.java @@ -0,0 +1,134 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = ReidentifyStringResponseV2.Builder.class) +public final class ReidentifyStringResponseV2 { + private final Optional processedText; + + private final Optional metrics; + + private final Map additionalProperties; + + private ReidentifyStringResponseV2( + Optional processedText, Optional metrics, Map additionalProperties) { + this.processedText = processedText; + this.metrics = metrics; + this.additionalProperties = additionalProperties; + } + + /** + * @return Reidentified text. + */ + @JsonProperty("processedText") + public Optional getProcessedText() { + return processedText; + } + + @JsonProperty("metrics") + public Optional getMetrics() { + return metrics; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof ReidentifyStringResponseV2 && equalTo((ReidentifyStringResponseV2) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(ReidentifyStringResponseV2 other) { + return processedText.equals(other.processedText) && metrics.equals(other.metrics); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.processedText, this.metrics); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional processedText = Optional.empty(); + + private Optional metrics = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(ReidentifyStringResponseV2 other) { + processedText(other.getProcessedText()); + metrics(other.getMetrics()); + return this; + } + + /** + *

Reidentified text.

+ */ + @JsonSetter(value = "processedText", nulls = Nulls.SKIP) + public Builder processedText(Optional processedText) { + this.processedText = processedText; + return this; + } + + public Builder processedText(String processedText) { + this.processedText = Optional.ofNullable(processedText); + return this; + } + + @JsonSetter(value = "metrics", nulls = Nulls.SKIP) + public Builder metrics(Optional metrics) { + this.metrics = metrics; + return this; + } + + public Builder metrics(Metrics metrics) { + this.metrics = Optional.ofNullable(metrics); + return this; + } + + public ReidentifyStringResponseV2 build() { + return new ReidentifyStringResponseV2(processedText, metrics, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/detect/rest/types/TransformationShiftDates.java b/common/src/main/java/com/skyflow/generated/detect/rest/types/TransformationShiftDates.java new file mode 100644 index 00000000..78879313 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/detect/rest/types/TransformationShiftDates.java @@ -0,0 +1,140 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.detect.rest.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = TransformationShiftDates.Builder.class) +public final class TransformationShiftDates { + private final Optional minDays; + + private final Optional maxDays; + + private final Map additionalProperties; + + private TransformationShiftDates( + Optional minDays, Optional maxDays, Map additionalProperties) { + this.minDays = minDays; + this.maxDays = maxDays; + this.additionalProperties = additionalProperties; + } + + /** + * @return Minimum number of days to shift dates. + */ + @JsonProperty("minDays") + public Optional getMinDays() { + return minDays; + } + + /** + * @return Maximum number of days to shift dates. + */ + @JsonProperty("maxDays") + public Optional getMaxDays() { + return maxDays; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof TransformationShiftDates && equalTo((TransformationShiftDates) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(TransformationShiftDates other) { + return minDays.equals(other.minDays) && maxDays.equals(other.maxDays); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.minDays, this.maxDays); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional minDays = Optional.empty(); + + private Optional maxDays = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(TransformationShiftDates other) { + minDays(other.getMinDays()); + maxDays(other.getMaxDays()); + return this; + } + + /** + *

Minimum number of days to shift dates.

+ */ + @JsonSetter(value = "minDays", nulls = Nulls.SKIP) + public Builder minDays(Optional minDays) { + this.minDays = minDays; + return this; + } + + public Builder minDays(Integer minDays) { + this.minDays = Optional.ofNullable(minDays); + return this; + } + + /** + *

Maximum number of days to shift dates.

+ */ + @JsonSetter(value = "maxDays", nulls = Nulls.SKIP) + public Builder maxDays(Optional maxDays) { + this.maxDays = maxDays; + return this; + } + + public Builder maxDays(Integer maxDays) { + this.maxDays = Optional.ofNullable(maxDays); + return this; + } + + public TransformationShiftDates build() { + return new TransformationShiftDates(minDays, maxDays, additionalProperties); + } + + public Builder additionalProperty(String key, Object value) { + this.additionalProperties.put(key, value); + return this; + } + + public Builder additionalProperties(Map additionalProperties) { + this.additionalProperties.putAll(additionalProperties); + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/logs/ErrorLogs.java b/common/src/main/java/com/skyflow/logs/ErrorLogs.java index 418d08bc..2051cedc 100644 --- a/common/src/main/java/com/skyflow/logs/ErrorLogs.java +++ b/common/src/main/java/com/skyflow/logs/ErrorLogs.java @@ -1,6 +1,34 @@ package com.skyflow.logs; public enum ErrorLogs { + CHECK_GUARDRAILS_REQUEST_NULL("Invalid %s1 request. CheckGuardrails request can not be null."), + INVALID_TEXT_IN_CHECK_GUARDRAILS("Invalid %s1 request. The text field is required and must be a non-empty string. Specify a valid text."), + CHECK_GUARDRAILS_REQUEST_REJECTED("Check guardrails request resulted in failure."), + GET_RUN_REQUEST_NULL("Invalid %s1 request. GetRun request can not be null."), + INVALID_RUN_ID_IN_GET_RUN("Invalid %s1 request. The runId field is required and must be a non-empty string. Specify a valid run id."), + GET_RUN_REQUEST_REJECTED("Get run request resulted in failure."), + DEIDENTIFY_STRING_REQUEST_NULL("Invalid %s1 request. DeidentifyString request can not be null."), + INVALID_TEXT_IN_DEIDENTIFY_STRING("Invalid %s1 request. The text field is required and must be a non-empty string. Specify a valid text."), + DEIDENTIFY_STRING_REQUEST_REJECTED("DeIdentify string request resulted in failure."), + REIDENTIFY_STRING_REQUEST_NULL("Invalid %s1 request. ReidentifyString request can not be null."), + INVALID_TEXT_IN_REIDENTIFY_STRING("Invalid %s1 request. The text field is required and must be a non-empty string. Specify a valid text."), + REIDENTIFY_STRING_REQUEST_REJECTED("ReIdentify string request resulted in failure."), + DEIDENTIFY_FILE_REQUEST_NULL("Invalid %s1 request. DeidentifyFile request can not be null."), + INVALID_DATA_SOURCE_IN_DEIDENTIFY_FILE("Invalid %s1 request. The dataSource field is required unless a file is supplied."), + INVALID_VALUE_IN_DEIDENTIFY_FILE("Invalid %s1 request. The value field is required and must be a non-empty string unless a file is supplied."), + MULTIPLE_FILE_SOURCES_IN_DEIDENTIFY_FILE("Invalid %s1 request. Specify either value or file, not both."), + FILE_NOT_FOUND_IN_DEIDENTIFY_FILE("Invalid %s1 request. The file to deidentify was not found."), + FAILED_TO_ENCODE_FILE_IN_DEIDENTIFY_FILE("Invalid %s1 request. Failed to read and encode the file to deidentify."), + INVALID_WAIT_TIME_IN_POLL_OPTIONS("Invalid %s1 request. waitTime in pollOptions must be between 1 and 300."), + INVALID_MAX_ATTEMPTS_IN_POLL_OPTIONS("Invalid %s1 request. maxAttempts in pollOptions must be between 1 and 23."), + OUTPUT_DIRECTORY_WITHOUT_POLL_OPTIONS("Invalid %s1 request. outputDirectory requires pollOptions."), + DEIDENTIFY_FILE_REQUEST_REJECTED("DeIdentify file request resulted in failure."), + OUTPUT_DIRECTORY_NOT_CREATED("Could not create output directory %s1."), + OUTPUT_FILE_NOT_WRITTEN("Could not write processed output to %s1."), + REIDENTIFY_FILE_REQUEST_NULL("Invalid %s1 request. ReidentifyFile request can not be null."), + INVALID_DATA_SOURCE_IN_REIDENTIFY_FILE("Invalid %s1 request. The dataSource field is required. Specify BASE64, SKYFLOW_ID or PRESIGNED_URL."), + INVALID_VALUE_IN_REIDENTIFY_FILE("Invalid %s1 request. The value field is required and must be a non-empty string. Specify the base64 content, skyflow id or presigned URL of the file."), + REIDENTIFY_FILE_REQUEST_REJECTED("ReIdentify file request resulted in failure."), // Client initialization VAULT_CONFIG_EXISTS("Vault config with vault ID %s1 already exists."), VAULT_CONFIG_DOES_NOT_EXIST("Vault config with vault ID %s1 doesn't exist."), @@ -184,7 +212,6 @@ public enum ErrorLogs { REIDENTIFY_TEXT_REQUEST_NULL("Invalid %s1 request. ReidentifyText request can not be null."), INVALID_TEXT_IN_REIDENTIFY("Invalid %s1 request. The text field is required and must be a non-empty string. Specify a valid text."), REIDENTIFY_TEXT_REQUEST_REJECTED("ReIdentify text request resulted in failure."), - DEIDENTIFY_FILE_REQUEST_REJECTED("DeIdentify file request resulted in failure."), GET_DETECT_RUN_REQUEST_REJECTED("Get detect run request resulted in failure."), INVALID_NULL_FILE_IN_DEIDENTIFY_FILE("Invalid %s1 request. The file field is required and must not be null. Specify a valid file."), FILE_NOT_FOUND_TO_DEIDENTIFY("Invalid %s1 request. The file field is required and must not be empty. Specify a valid file."), diff --git a/common/src/main/java/com/skyflow/logs/InfoLogs.java b/common/src/main/java/com/skyflow/logs/InfoLogs.java index b4045229..3c332232 100644 --- a/common/src/main/java/com/skyflow/logs/InfoLogs.java +++ b/common/src/main/java/com/skyflow/logs/InfoLogs.java @@ -1,6 +1,37 @@ package com.skyflow.logs; public enum InfoLogs { + CHECK_GUARDRAILS_TRIGGERED("Check guardrails method triggered."), + VALIDATE_CHECK_GUARDRAILS_REQUEST("Validating check guardrails request."), + CHECK_GUARDRAILS_REQUEST_RESOLVED("Check guardrails request resolved."), + CHECK_GUARDRAILS_SUCCESS("Guardrails check completed."), + GET_RUN_TRIGGERED("Get run method triggered."), + VALIDATE_GET_RUN_REQUEST("Validating get run request."), + GET_RUN_REQUEST_RESOLVED("Get run request resolved."), + GET_RUN_SUCCESS("Detect run retrieved."), + DEIDENTIFY_STRING_TRIGGERED("DeIdentify string method triggered."), + VALIDATE_DEIDENTIFY_STRING_REQUEST("Validating deidentify string request."), + DEIDENTIFY_STRING_REQUEST_RESOLVED("DeIdentify string request resolved."), + DEIDENTIFY_STRING_SUCCESS("String data de-identified."), + REIDENTIFY_STRING_TRIGGERED("ReIdentify string method triggered."), + VALIDATE_REIDENTIFY_STRING_REQUEST("Validating reidentify string request."), + REIDENTIFY_STRING_REQUEST_RESOLVED("ReIdentify string request resolved."), + REIDENTIFY_STRING_SUCCESS("String data re-identified."), + DEIDENTIFY_FILE_TRIGGERED("DeIdentify file method triggered."), + VALIDATE_DEIDENTIFY_FILE_REQUEST("Validating deidentify file request."), + DEIDENTIFY_FILE_REQUEST_RESOLVED("DeIdentify file request resolved."), + DEIDENTIFY_FILE_RUN_SUBMITTED("DeIdentify file run %s1 submitted; retrieve it with getRun."), + DEIDENTIFY_FILE_SUCCESS("File data de-identified."), + POLL_RUN_STARTED("Polling run %s1 for up to %s2 seconds and %s3 attempts."), + POLL_RUN_TERMINAL("Run %s1 reached status %s2 after %s3 attempts."), + POLL_RUN_BUDGET_EXHAUSTED("Run %s1 still %s2 after %s3 attempts and %s4 seconds; returning run id."), + POLL_RUN_INTERRUPTED("Polling of run %s1 interrupted while %s2; returning run id."), + OUTPUT_FILE_WRITTEN("Processed output written to %s1."), + OUTPUT_NOT_WRITTEN_FOR_OUTPUT_TYPE("Outputs delivered as %s1 are not written to disk."), + REIDENTIFY_FILE_TRIGGERED("ReIdentify file method triggered."), + VALIDATE_REIDENTIFY_FILE_REQUEST("Validating reidentify file request."), + REIDENTIFY_FILE_REQUEST_RESOLVED("ReIdentify file request resolved."), + REIDENTIFY_FILE_SUCCESS("File data re-identified."), // Client initialization CLIENT_INITIALIZED("Initialized skyflow client."), VALIDATING_VAULT_CONFIG("Validating vault config."), @@ -91,10 +122,6 @@ public enum InfoLogs { VALIDATE_REIDENTIFY_TEXT_REQUEST("Validating reidentify text request."), REIDENTIFY_TEXT_TRIGGERED("ReIdentify text method triggered."), REIDENTIFY_TEXT_REQUEST_RESOLVED("ReIdentify text request resolved."), - DEIDENTIFY_FILE_TRIGGERED("DeIdentify file method triggered."), - VALIDATE_DEIDENTIFY_FILE_REQUEST("Validating deidentify file request."), - DEIDENTIFY_FILE_REQUEST_RESOLVED("DeIdentify file request resolved."), - DEIDENTIFY_FILE_SUCCESS("File deidentified successfully."), GET_DETECT_RUN_TRIGGERED("Get detect run method triggered."), VALIDATE_GET_DETECT_RUN_REQUEST("Validating get detect run request."), REIDENTIFY_TEXT_SUCCESS("Text data re-identified."), diff --git a/common/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java b/common/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java index 882df51c..a98f4e3a 100644 --- a/common/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java +++ b/common/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java @@ -2,7 +2,7 @@ import com.google.gson.*; import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.generated.auth.rest.ApiClient; import com.skyflow.generated.auth.rest.ApiClientBuilder; @@ -56,7 +56,7 @@ private static V1GetAuthTokenResponse generateBearerTokenFromCredentials( try { if (credentialsFile == null) { LogUtil.printErrorLog(ErrorLogs.INVALID_CREDENTIALS_FILE.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidCredentials.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidCredentials.getMessage()); } FileReader reader = new FileReader(String.valueOf(credentialsFile)); try { @@ -68,11 +68,11 @@ private static V1GetAuthTokenResponse generateBearerTokenFromCredentials( } catch (JsonSyntaxException | IllegalStateException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_CREDENTIALS_FILE_FORMAT.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseUtils.parameterizedString( - ErrorMessage.FileInvalidJson.getMessage(), credentialsFile.getPath())); + BaseErrorMessage.FileInvalidJson.getMessage(), credentialsFile.getPath())); } catch (FileNotFoundException e) { LogUtil.printErrorLog(ErrorLogs.CREDENTIALS_FILE_NOT_FOUND.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseUtils.parameterizedString( - ErrorMessage.FileNotFound.getMessage(), credentialsFile.getPath())); + BaseErrorMessage.FileNotFound.getMessage(), credentialsFile.getPath())); } } @@ -83,14 +83,14 @@ private static V1GetAuthTokenResponse generateBearerTokenFromCredentialString( try { if (credentials == null || credentials.isEmpty()) { LogUtil.printErrorLog(ErrorLogs.INVALID_CREDENTIALS_STRING.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidCredentials.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidCredentials.getMessage()); } JsonObject serviceAccountCredentials = JsonParser.parseString(credentials).getAsJsonObject(); return getBearerTokenFromCredentials(serviceAccountCredentials, context, roles); } catch (JsonSyntaxException | IllegalStateException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_CREDENTIALS_STRING_FORMAT.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), - ErrorMessage.CredentialsStringInvalidJson.getMessage()); + BaseErrorMessage.CredentialsStringInvalidJson.getMessage()); } } @@ -101,7 +101,7 @@ private static V1GetAuthTokenResponse getBearerTokenFromCredentials( JsonElement privateKey = credentials.get("privateKey"); if (privateKey == null) { LogUtil.printErrorLog(ErrorLogs.PRIVATE_KEY_IS_REQUIRED.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingPrivateKey.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.MissingPrivateKey.getMessage()); } // Accept both new-form keys (clientId/keyId/tokenUri) and legacy all-caps form for migration @@ -114,7 +114,7 @@ private static V1GetAuthTokenResponse getBearerTokenFromCredentials( } if (clientId == null) { LogUtil.printErrorLog(ErrorLogs.CLIENT_ID_IS_REQUIRED.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingClientId.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.MissingClientId.getMessage()); } JsonElement keyId = credentials.get("keyId"); @@ -126,7 +126,7 @@ private static V1GetAuthTokenResponse getBearerTokenFromCredentials( } if (keyId == null) { LogUtil.printErrorLog(ErrorLogs.KEY_ID_IS_REQUIRED.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingKeyId.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.MissingKeyId.getMessage()); } JsonElement tokenUri = credentials.get("tokenUri"); @@ -138,7 +138,7 @@ private static V1GetAuthTokenResponse getBearerTokenFromCredentials( } if (tokenUri == null) { LogUtil.printErrorLog(ErrorLogs.TOKEN_URI_IS_REQUIRED.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingTokenUri.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.MissingTokenUri.getMessage()); } PrivateKey pvtKey = BaseUtils.getPrivateKeyFromPem(privateKey.getAsString()); @@ -160,7 +160,7 @@ private static V1GetAuthTokenResponse getBearerTokenFromCredentials( return authenticationApi.authenticationServiceGetAuthToken(authTokenBuilder.build()); } catch (MalformedURLException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_TOKEN_URI.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidTokenUri.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidTokenUri.getMessage()); } catch (ApiClientApiException e) { String bodyString = GSON.toJson(e.body()); LogUtil.printErrorLog(ErrorLogs.BEARER_TOKEN_REJECTED.getLog()); @@ -218,7 +218,7 @@ public synchronized String getBearerToken() throws SkyflowException { private static String extractAccessToken(V1GetAuthTokenResponse response) throws SkyflowException { if (!response.getAccessToken().isPresent()) { LogUtil.printErrorLog(ErrorLogs.MISSING_ACCESS_TOKEN.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingAccessToken.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.MissingAccessToken.getMessage()); } return response.getAccessToken().get(); } diff --git a/common/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java b/common/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java index 630a0aee..a4a2c57d 100644 --- a/common/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java +++ b/common/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java @@ -5,7 +5,7 @@ import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; import com.skyflow.logs.InfoLogs; @@ -49,7 +49,7 @@ private static List generateSignedTokenFromCredentialsF try { if (credentialsFile == null) { LogUtil.printErrorLog(ErrorLogs.INVALID_CREDENTIALS_FILE.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidCredentials.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidCredentials.getMessage()); } FileReader reader = new FileReader(String.valueOf(credentialsFile)); try { @@ -61,11 +61,11 @@ private static List generateSignedTokenFromCredentialsF } catch (JsonSyntaxException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_CREDENTIALS_FILE_FORMAT.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseUtils.parameterizedString( - ErrorMessage.FileInvalidJson.getMessage(), credentialsFile.getPath())); + BaseErrorMessage.FileInvalidJson.getMessage(), credentialsFile.getPath())); } catch (FileNotFoundException e) { LogUtil.printErrorLog(ErrorLogs.CREDENTIALS_FILE_NOT_FOUND.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseUtils.parameterizedString( - ErrorMessage.FileNotFound.getMessage(), credentialsFile.getPath())); + BaseErrorMessage.FileNotFound.getMessage(), credentialsFile.getPath())); } return responseToken; } @@ -78,14 +78,14 @@ private static List generateSignedTokensFromCredentials try { if (credentials == null || credentials.isEmpty()) { LogUtil.printErrorLog(ErrorLogs.INVALID_CREDENTIALS_STRING.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidCredentials.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidCredentials.getMessage()); } JsonObject serviceAccountCredentials = JsonParser.parseString(credentials).getAsJsonObject(); responseToken = generateSignedTokensFromCredentials(serviceAccountCredentials, dataTokens, timeToLive, context); } catch (JsonSyntaxException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_CREDENTIALS_STRING_FORMAT.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), - ErrorMessage.CredentialsStringInvalidJson.getMessage()); + BaseErrorMessage.CredentialsStringInvalidJson.getMessage()); } return responseToken; } @@ -98,7 +98,7 @@ private static List generateSignedTokensFromCredentials JsonElement privateKey = credentials.get("privateKey"); if (privateKey == null) { LogUtil.printErrorLog(ErrorLogs.PRIVATE_KEY_IS_REQUIRED.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingPrivateKey.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.MissingPrivateKey.getMessage()); } // Accept both new-form keys (clientId/keyId) and legacy all-caps form for migration @@ -111,7 +111,7 @@ private static List generateSignedTokensFromCredentials } if (clientId == null) { LogUtil.printErrorLog(ErrorLogs.CLIENT_ID_IS_REQUIRED.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingClientId.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.MissingClientId.getMessage()); } JsonElement keyId = credentials.get("keyId"); @@ -123,7 +123,7 @@ private static List generateSignedTokensFromCredentials } if (keyId == null) { LogUtil.printErrorLog(ErrorLogs.KEY_ID_IS_REQUIRED.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingKeyId.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.MissingKeyId.getMessage()); } PrivateKey pvtKey = BaseUtils.getPrivateKeyFromPem(privateKey.getAsString()); signedDataTokens = getSignedToken( diff --git a/common/src/main/java/com/skyflow/serviceaccount/util/Token.java b/common/src/main/java/com/skyflow/serviceaccount/util/Token.java index 0359d944..19edf0c8 100644 --- a/common/src/main/java/com/skyflow/serviceaccount/util/Token.java +++ b/common/src/main/java/com/skyflow/serviceaccount/util/Token.java @@ -5,7 +5,7 @@ import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; import com.skyflow.logs.InfoLogs; @@ -29,7 +29,7 @@ public static boolean isExpired(String token) { JsonElement exp = decoded(token).get("exp"); if (exp == null) { LogUtil.printErrorLog(ErrorLogs.INVALID_BEARER_TOKEN.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.JwtDecodeError.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.JwtDecodeError.getMessage()); } expiryTime = exp.getAsLong(); @@ -44,14 +44,14 @@ static JsonObject decoded(String encodedToken) throws JsonSyntaxException, Skyfl String[] split = encodedToken.split("\\."); if (split.length < 3) { LogUtil.printErrorLog(ErrorLogs.INVALID_BEARER_TOKEN.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.JwtDecodeError.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.JwtDecodeError.getMessage()); } try { byte[] decodedBytes = Base64.getUrlDecoder().decode(split[1]); return JsonParser.parseString(new String(decodedBytes, StandardCharsets.UTF_8)).getAsJsonObject(); } catch (IllegalArgumentException | IllegalStateException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_BEARER_TOKEN.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.JwtDecodeError.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.JwtDecodeError.getMessage()); } } } diff --git a/common/src/main/java/com/skyflow/utils/BaseConstants.java b/common/src/main/java/com/skyflow/utils/BaseConstants.java index e9946a40..8d0ea8d8 100644 --- a/common/src/main/java/com/skyflow/utils/BaseConstants.java +++ b/common/src/main/java/com/skyflow/utils/BaseConstants.java @@ -27,6 +27,7 @@ public class BaseConstants { public static final String SDK_METRIC_RUNTIME_DETAILS_PREFIX = "Java@"; public static final String SDK_AUTH_HEADER_KEY = "x-skyflow-authorization"; public static final String SDK_METRICS_HEADER_KEY = "sky-metadata"; + public static final String PROCESSED_FILE_NAME_PREFIX = "processed-"; public static final String REQUEST_ID_HEADER_KEY = "x-request-id"; public static final String ERROR_FROM_CLIENT_HEADER_KEY = "error-from-client"; diff --git a/common/src/main/java/com/skyflow/utils/BaseUtils.java b/common/src/main/java/com/skyflow/utils/BaseUtils.java index e536c111..8e076c1b 100644 --- a/common/src/main/java/com/skyflow/utils/BaseUtils.java +++ b/common/src/main/java/com/skyflow/utils/BaseUtils.java @@ -15,7 +15,7 @@ import com.skyflow.config.BaseCredentials; import com.skyflow.enums.Env; import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; import com.skyflow.logs.InfoLogs; @@ -87,11 +87,11 @@ public static PrivateKey getPrivateKeyFromPem(String pemKey) throws SkyflowExcep privateKey = parsePkcs8PrivateKey(Base64.getDecoder().decode(privateKeyContent)); } catch (IllegalArgumentException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_KEY_SPEC.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidKeySpec.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidKeySpec.getMessage()); } } else { LogUtil.printErrorLog(ErrorLogs.JWT_INVALID_FORMAT.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.JwtInvalidFormat.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.JwtInvalidFormat.getMessage()); } return privateKey; } @@ -165,10 +165,10 @@ private static PrivateKey parsePkcs8PrivateKey(byte[] pkcs8Bytes) throws Skyflow privateKey = keyFactory.generatePrivate(keySpec); } catch (NoSuchAlgorithmException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_ALGORITHM.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidAlgorithm.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidAlgorithm.getMessage()); } catch (InvalidKeySpecException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_KEY_SPEC.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidKeySpec.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidKeySpec.getMessage()); } return privateKey; } diff --git a/common/src/main/java/com/skyflow/utils/validations/BaseValidations.java b/common/src/main/java/com/skyflow/utils/validations/BaseValidations.java index b542589a..89bfb501 100644 --- a/common/src/main/java/com/skyflow/utils/validations/BaseValidations.java +++ b/common/src/main/java/com/skyflow/utils/validations/BaseValidations.java @@ -2,7 +2,7 @@ import com.skyflow.config.BaseCredentials; import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; import com.skyflow.utils.BaseConstants; @@ -21,7 +21,7 @@ public class BaseValidations { public static void validateCredentials(BaseCredentials credentials) throws SkyflowException { if (credentials == null) { LogUtil.printErrorLog(ErrorLogs.CREDENTIALS_IS_NULL.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.NullCredentials.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.NullCredentials.getMessage()); } int nonNullMembers = 0; String path = credentials.getPath(); @@ -39,38 +39,38 @@ public static void validateCredentials(BaseCredentials credentials) throws Skyfl if (nonNullMembers > 1) { LogUtil.printErrorLog(ErrorLogs.MULTIPLE_TOKEN_GENERATION_MEANS_PASSED.getLog()); throw new SkyflowException( - ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MultipleTokenGenerationMeansPassed.getMessage() + ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.MultipleTokenGenerationMeansPassed.getMessage() ); } else if (nonNullMembers < 1) { LogUtil.printErrorLog(ErrorLogs.NO_TOKEN_GENERATION_MEANS_PASSED.getLog()); throw new SkyflowException( - ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.NoTokenGenerationMeansPassed.getMessage() + ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.NoTokenGenerationMeansPassed.getMessage() ); } else if (path != null && path.trim().isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_CREDENTIALS_PATH.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentialFilePath.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyCredentialFilePath.getMessage()); } else if (credentialsString != null && credentialsString.trim().isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_CREDENTIALS_STRING.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentialsString.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyCredentialsString.getMessage()); } else if (token != null && token.trim().isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_TOKEN_VALUE.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyToken.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyToken.getMessage()); } else if (apiKey != null) { if (apiKey.trim().isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_API_KEY_VALUE.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyApikey.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyApikey.getMessage()); } else { Pattern pattern = Pattern.compile(BaseConstants.API_KEY_REGEX); Matcher matcher = pattern.matcher(apiKey); if (!matcher.matches()) { LogUtil.printErrorLog(ErrorLogs.INVALID_API_KEY.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidApikey.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidApikey.getMessage()); } } } else if (roles != null) { if (roles.isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_ROLES.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRoles.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyRoles.getMessage()); } else { for (int index = 0; index < roles.size(); index++) { String role = roles.get(index); @@ -78,7 +78,7 @@ public static void validateCredentials(BaseCredentials credentials) throws Skyfl LogUtil.printErrorLog(BaseUtils.parameterizedString( ErrorLogs.EMPTY_OR_NULL_ROLE_IN_ROLES.getLog(), Integer.toString(index) )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRoleInRoles.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyRoleInRoles.getMessage()); } } } @@ -88,13 +88,13 @@ public static void validateCredentials(BaseCredentials credentials) throws Skyfl String ctxStr = (String) context; if (ctxStr.trim().isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_OR_NULL_CONTEXT.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyContext.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyContext.getMessage()); } } else if (context instanceof Map) { Map ctxMap = (Map) context; if (ctxMap.isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_OR_NULL_CONTEXT.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyContext.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyContext.getMessage()); } Pattern ctxKeyPattern = Pattern.compile(BaseConstants.CONTEXT_KEY_REGEX); for (Object key : ctxMap.keySet()) { @@ -103,12 +103,12 @@ public static void validateCredentials(BaseCredentials credentials) throws Skyfl LogUtil.printErrorLog(BaseUtils.parameterizedString( ErrorLogs.INVALID_CONTEXT_MAP_KEY.getLog(), keyStr)); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), - BaseUtils.parameterizedString(ErrorMessage.InvalidContextMapKey.getMessage(), keyStr)); + BaseUtils.parameterizedString(BaseErrorMessage.InvalidContextMapKey.getMessage(), keyStr)); } } } else { LogUtil.printErrorLog(ErrorLogs.INVALID_CONTEXT_TYPE.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidContextType.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidContextType.getMessage()); } } } diff --git a/common/src/main/java/com/skyflow/vault/controller/BaseDetectController.java b/common/src/main/java/com/skyflow/vault/controller/BaseDetectController.java new file mode 100644 index 00000000..71f6217d --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/controller/BaseDetectController.java @@ -0,0 +1,379 @@ +package com.skyflow.vault.controller; + +import com.skyflow.BaseVaultClient; +import com.skyflow.config.BaseCredentials; +import com.skyflow.config.BaseVaultConfig; +import com.skyflow.detect.CheckGuardrailsRequest; +import com.skyflow.detect.CheckGuardrailsResponse; +import com.skyflow.detect.DeidentifyStringRequest; +import com.skyflow.detect.DeidentifyStringResponse; +import com.skyflow.detect.GetRunRequest; +import com.skyflow.detect.GetRunResponse; +import com.skyflow.detect.ReidentifyStringRequest; +import com.skyflow.detect.ReidentifyStringResponse; +import com.skyflow.detect.internal.DetectRequestMapper; +import com.skyflow.detect.internal.DetectResponseMapper; +import com.skyflow.detect.internal.DetectValidations; +import com.skyflow.errors.SkyflowException; +import com.skyflow.generated.detect.rest.core.ApiClientApiException; +import com.skyflow.generated.detect.rest.core.ApiClientException; +import com.skyflow.generated.detect.rest.core.ClientOptions; +import com.skyflow.generated.detect.rest.core.Environment; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.resources.filesv2.FilesV2Client; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.GetRunV2Request; +import com.skyflow.generated.detect.rest.resources.guardrailsv2.GuardrailsV2Client; +import com.skyflow.generated.detect.rest.resources.guardrailsv2.requests.DetectGuardrailsRequestV2; +import com.skyflow.generated.detect.rest.resources.stringsv2.StringsV2Client; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.DeidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.ReidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.types.DeidentifyStringResponseV2; +import com.skyflow.generated.detect.rest.types.DetectGuardrailsResponseV2; +import com.skyflow.generated.detect.rest.types.DetectRunsResponseV2; +import com.skyflow.generated.detect.rest.types.ReidentifyStringResponseV2; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.BaseConstants; +import com.skyflow.utils.logger.LogUtil; + +/** + * Shared implementation of the Skyflow Detect V2 API. Each SDK's {@code DetectController} + * extends this class and supplies only what differs per vault type: how the vault URL is + * resolved, the SDK metrics header, and any vault-level timeout and retry overrides. + * + *

Credentials, bearer-token refresh and the vault configuration come from + * {@link BaseVaultClient}. The generated V2 client is created lazily and rebuilt whenever the + * vault URL or the effective HTTP settings change. + * + * @param the SDK's vault configuration type + */ +public abstract class BaseDetectController extends BaseVaultClient { + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String BEARER_PREFIX = "Bearer "; + + private Integer commonTimeoutSeconds; + private Integer commonMaxRetries; + private Long commonInitialRetryDelayMillis; + private Long commonMaxRetryDelayMillis; + + private ClientOptions clientOptions; + private String clientOptionsKey; + private StringsV2Client stringsClient; + private FilesV2Client filesClient; + private GuardrailsV2Client guardrailsClient; + + protected BaseDetectController(V vaultConfig, BaseCredentials credentials) { + super(vaultConfig, credentials); + } + + // ------------------------------------------------------------------ + // Hooks for the concrete SDK controllers + // ------------------------------------------------------------------ + + /** + * @return the vault base URL requests are sent to, for example + * {@code https://.vault.skyflowapis.com} + * @throws SkyflowException if the URL cannot be determined + */ + protected abstract String resolveVaultUrl() throws SkyflowException; + + /** + * @return JSON sent in the {@code sky-metadata} header. Override to report SDK name and version. + */ + protected String getSdkMetrics() { + return "{}"; + } + + /** + * Resolves the bearer token or API key for the next call, refreshing it when expired. + * The default uses the credential chain in {@link BaseVaultClient}. + */ + protected String resolveToken() throws SkyflowException { + setBearerToken(this.vaultConfig.getCredentials()); + return this.token; + } + + /** @return overall call timeout in seconds, or {@code null} for the transport default */ + protected Integer effectiveTimeoutSeconds() { + return commonTimeoutSeconds; + } + + /** @return retries on retryable HTTP statuses, or {@code null} for the transport default */ + protected Integer effectiveMaxRetries() { + return commonMaxRetries; + } + + protected Long effectiveInitialRetryDelayMillis() { + return commonInitialRetryDelayMillis; + } + + protected Long effectiveMaxRetryDelayMillis() { + return commonMaxRetryDelayMillis; + } + + // ------------------------------------------------------------------ + // Lifecycle, driven by each SDK's Skyflow builder. Public because the builders live in + // com.skyflow while this class lives in com.skyflow.vault.controller; applications have + // no reason to call these directly. + // ------------------------------------------------------------------ + + /** Applies client-level credentials; vault-level credentials keep precedence. */ + public void setCommonCredentials(BaseCredentials commonCredentials) throws SkyflowException { + this.commonCredentials = commonCredentials; + prioritiseCredentials(this.vaultConfig.getCredentials()); + onCommonCredentialsUpdated(commonCredentials); + } + + /** + * Called after client-level credentials change. Subclasses that hold other clients (for + * example a V1 delegate) override this to forward the update; the default does nothing. + */ + protected void onCommonCredentialsUpdated(BaseCredentials commonCredentials) throws SkyflowException { + } + + /** Applies client-level timeout and retry settings; vault-level settings keep precedence. */ + public synchronized void setCommonHttpConfig(Integer timeoutSeconds, Integer maxRetries, + Long initialRetryDelayMillis, Long maxRetryDelayMillis) { + this.commonTimeoutSeconds = timeoutSeconds; + this.commonMaxRetries = maxRetries; + this.commonInitialRetryDelayMillis = initialRetryDelayMillis; + this.commonMaxRetryDelayMillis = maxRetryDelayMillis; + invalidateClients(); + } + + /** Replaces the vault configuration, for example after {@code updateVaultConfig}. */ + public synchronized void setVaultConfig(V vaultConfig) throws SkyflowException { + this.vaultConfig = vaultConfig; + invalidateClients(); + } + + /** Forces the vault URL to be resolved again on the next call. */ + public synchronized void refreshVaultUrl() throws SkyflowException { + invalidateClients(); + } + + /** Drops cached generated clients so the next call rebuilds them from current settings. */ + protected synchronized void invalidateClients() { + this.clientOptions = null; + this.clientOptionsKey = null; + this.stringsClient = null; + this.filesClient = null; + this.guardrailsClient = null; + } + + // ------------------------------------------------------------------ + // Detect V2 operations + // ------------------------------------------------------------------ + + /** + * Detects and de-identifies sensitive data in a string. Synchronous. + * + * @param request text plus exactly one of a stored configuration id or an inline configuration + * @return processed text, detected entities and metrics + * @throws SkyflowException on validation failure, credential failure or an API error + */ + public DeidentifyStringResponse deidentifyString(DeidentifyStringRequest request) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.DEIDENTIFY_STRING_TRIGGERED.getLog()); + LogUtil.printInfoLog(InfoLogs.VALIDATE_DEIDENTIFY_STRING_REQUEST.getLog()); + DetectValidations.validateDeidentifyStringRequest(request); + String token = resolveToken(); + DeidentifyStringRequestV2 apiRequest = + DetectRequestMapper.toDeidentifyStringRequest(request, this.vaultConfig.getVaultId()); + try { + DeidentifyStringResponseV2 apiResponse = stringsClient().deidentifyStringV2(apiRequest, requestOptions(token)); + LogUtil.printInfoLog(InfoLogs.DEIDENTIFY_STRING_REQUEST_RESOLVED.getLog()); + DeidentifyStringResponse response = DetectResponseMapper.toDeidentifyStringResponse(apiResponse); + LogUtil.printInfoLog(InfoLogs.DEIDENTIFY_STRING_SUCCESS.getLog()); + return response; + } catch (ApiClientApiException e) { + throw toSkyflowException(e, ErrorLogs.DEIDENTIFY_STRING_REQUEST_REJECTED); + } catch (ApiClientException e) { + LogUtil.printErrorLog(ErrorLogs.DEIDENTIFY_STRING_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e); + } + } + + /** + * Replaces tokens in a string with their original values or a redacted rendering. Synchronous. + * + * @param request text containing tokens plus optional per-group redaction rules + * @return re-identified text and metrics + * @throws SkyflowException on validation failure, credential failure or an API error + */ + public ReidentifyStringResponse reidentifyString(ReidentifyStringRequest request) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.REIDENTIFY_STRING_TRIGGERED.getLog()); + LogUtil.printInfoLog(InfoLogs.VALIDATE_REIDENTIFY_STRING_REQUEST.getLog()); + DetectValidations.validateReidentifyStringRequest(request); + String token = resolveToken(); + ReidentifyStringRequestV2 apiRequest = + DetectRequestMapper.toReidentifyStringRequest(request, this.vaultConfig.getVaultId()); + try { + ReidentifyStringResponseV2 apiResponse = stringsClient().reidentifyStringV2(apiRequest, requestOptions(token)); + LogUtil.printInfoLog(InfoLogs.REIDENTIFY_STRING_REQUEST_RESOLVED.getLog()); + ReidentifyStringResponse response = DetectResponseMapper.toReidentifyStringResponse(apiResponse); + LogUtil.printInfoLog(InfoLogs.REIDENTIFY_STRING_SUCCESS.getLog()); + return response; + } catch (ApiClientApiException e) { + throw toSkyflowException(e, ErrorLogs.REIDENTIFY_STRING_REQUEST_REJECTED); + } catch (ApiClientException e) { + LogUtil.printErrorLog(ErrorLogs.REIDENTIFY_STRING_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e); + } + } + + /** + * Retrieves the status and, once complete, the outputs of a Detect file run. + * + * @param request the run id returned by {@code deidentifyFile} + * @return status, output files, metrics and failure message + * @throws SkyflowException on validation failure, credential failure or an API error + */ + public GetRunResponse getRun(GetRunRequest request) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.GET_RUN_TRIGGERED.getLog()); + LogUtil.printInfoLog(InfoLogs.VALIDATE_GET_RUN_REQUEST.getLog()); + DetectValidations.validateGetRunRequest(request); + String token = resolveToken(); + String runId = request.getRunId().trim(); + GetRunV2Request apiRequest = GetRunV2Request.builder().vaultId(this.vaultConfig.getVaultId()).build(); + try { + DetectRunsResponseV2 apiResponse = filesClient().getRunV2(runId, apiRequest, requestOptions(token)); + LogUtil.printInfoLog(InfoLogs.GET_RUN_REQUEST_RESOLVED.getLog()); + GetRunResponse response = DetectResponseMapper.toGetRunResponse(runId, apiResponse); + LogUtil.printInfoLog(InfoLogs.GET_RUN_SUCCESS.getLog()); + return response; + } catch (ApiClientApiException e) { + throw toSkyflowException(e, ErrorLogs.GET_RUN_REQUEST_REJECTED); + } catch (ApiClientException e) { + LogUtil.printErrorLog(ErrorLogs.GET_RUN_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e); + } + } + + /** + * Screens text for toxicity and denied topics. Synchronous. + * + * @param request text plus the checks to apply + * @return the overall validation result and the individual check outcomes + * @throws SkyflowException on validation failure, credential failure or an API error + */ + public CheckGuardrailsResponse checkGuardrails(CheckGuardrailsRequest request) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.CHECK_GUARDRAILS_TRIGGERED.getLog()); + LogUtil.printInfoLog(InfoLogs.VALIDATE_CHECK_GUARDRAILS_REQUEST.getLog()); + DetectValidations.validateCheckGuardrailsRequest(request); + String token = resolveToken(); + DetectGuardrailsRequestV2 apiRequest = + DetectRequestMapper.toCheckGuardrailsRequest(request, this.vaultConfig.getVaultId()); + try { + DetectGuardrailsResponseV2 apiResponse = guardrailsClient().checkGuardrailsV2(apiRequest, requestOptions(token)); + LogUtil.printInfoLog(InfoLogs.CHECK_GUARDRAILS_REQUEST_RESOLVED.getLog()); + CheckGuardrailsResponse response = DetectResponseMapper.toCheckGuardrailsResponse(apiResponse); + LogUtil.printInfoLog(InfoLogs.CHECK_GUARDRAILS_SUCCESS.getLog()); + return response; + } catch (ApiClientApiException e) { + throw toSkyflowException(e, ErrorLogs.CHECK_GUARDRAILS_REQUEST_REJECTED); + } catch (ApiClientException e) { + LogUtil.printErrorLog(ErrorLogs.CHECK_GUARDRAILS_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e); + } + } + + // ------------------------------------------------------------------ + // Transport plumbing + // ------------------------------------------------------------------ + + /** + * @return client options for the current URL, vault id and HTTP settings, rebuilt when any change + */ + private synchronized ClientOptions clientOptions() throws SkyflowException { + String vaultUrl = resolveVaultUrl(); + String key = clientKey(vaultUrl); + if (clientOptions == null || !key.equals(clientOptionsKey)) { + clientOptions = buildClientOptions(vaultUrl); + clientOptionsKey = key; + stringsClient = null; + filesClient = null; + guardrailsClient = null; + } + return clientOptions; + } + + /** @return the generated strings client for the current settings */ + protected synchronized StringsV2Client stringsClient() throws SkyflowException { + ClientOptions options = clientOptions(); + if (stringsClient == null) { + stringsClient = new StringsV2Client(options); + } + return stringsClient; + } + + /** @return the generated guardrails client for the current settings */ + protected synchronized GuardrailsV2Client guardrailsClient() throws SkyflowException { + ClientOptions options = clientOptions(); + if (guardrailsClient == null) { + guardrailsClient = new GuardrailsV2Client(options); + } + return guardrailsClient; + } + + /** @return the generated files client for the current settings */ + protected synchronized FilesV2Client filesClient() throws SkyflowException { + ClientOptions options = clientOptions(); + if (filesClient == null) { + filesClient = new FilesV2Client(options); + } + return filesClient; + } + + private String clientKey(String vaultUrl) { + return vaultUrl + "|" + this.vaultConfig.getVaultId() + "|" + effectiveTimeoutSeconds() + "|" + + effectiveMaxRetries() + "|" + effectiveInitialRetryDelayMillis() + "|" + effectiveMaxRetryDelayMillis(); + } + + private ClientOptions buildClientOptions(String vaultUrl) { + ClientOptions.Builder builder = ClientOptions.builder().environment(Environment.custom(vaultUrl)); + Integer timeout = effectiveTimeoutSeconds(); + if (timeout != null) { + builder.timeout(timeout); + } + Integer maxRetries = effectiveMaxRetries(); + if (maxRetries != null) { + builder.maxRetries(maxRetries); + } + Long initialDelay = effectiveInitialRetryDelayMillis(); + if (initialDelay != null) { + builder.initialRetryDelayMillis(initialDelay); + } + Long maxDelay = effectiveMaxRetryDelayMillis(); + if (maxDelay != null) { + builder.maxRetryDelayMillis(maxDelay); + } + return builder.build(); + } + + private RequestOptions requestOptions(String token) { + return RequestOptions.builder() + .addHeader(AUTHORIZATION_HEADER, BEARER_PREFIX + token) + .addHeader(BaseConstants.SDK_METRICS_HEADER_KEY, getSdkMetrics()) + .build(); + } + + private static SkyflowException toSkyflowException(ApiClientApiException e, ErrorLogs errorLog) { + LogUtil.printErrorLog(errorLog.getLog()); + return new SkyflowException(e.statusCode(), e, e.headers(), bodyAsString(e.body())); + } + + static String bodyAsString(Object body) { + if (body == null) { + return null; + } + if (body instanceof String) { + return (String) body; + } + try { + return ObjectMappers.JSON_MAPPER.writeValueAsString(body); + } catch (Exception ex) { + return body.toString(); + } + } +} diff --git a/common/src/test/java/com/skyflow/BaseSkyflowTests.java b/common/src/test/java/com/skyflow/BaseSkyflowTests.java index cfd65ad5..45d5278c 100644 --- a/common/src/test/java/com/skyflow/BaseSkyflowTests.java +++ b/common/src/test/java/com/skyflow/BaseSkyflowTests.java @@ -5,7 +5,7 @@ import com.skyflow.enums.Env; import com.skyflow.enums.LogLevel; import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; import org.junit.Assert; @@ -45,7 +45,7 @@ public void testAddingExistingVaultConfigThrows() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdAlreadyInConfigList.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.VaultIdAlreadyInConfigList.getMessage(), e.getMessage()); } } @@ -57,7 +57,7 @@ public void testUpdatingNonExistentVaultConfigInBuilderThrows() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); } } @@ -70,7 +70,7 @@ public void testUpdatingNonExistentVaultConfigInClientThrows() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); } } @@ -81,7 +81,7 @@ public void testRemovingNonExistentVaultConfigInBuilderThrows() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); } } @@ -104,7 +104,7 @@ public void testRemovingVaultConfigWithoutAddingThrows() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); } } @@ -122,7 +122,7 @@ public void testVaultThrowsWhenNoConfigAdded() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); } } @@ -137,7 +137,7 @@ public void testVaultByIdThrowsAfterRemoval() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); } } @@ -163,7 +163,7 @@ public void testAddingInvalidSkyflowCredentialsThrows() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.NoTokenGenerationMeansPassed.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.NoTokenGenerationMeansPassed.getMessage(), e.getMessage()); } } @@ -350,12 +350,12 @@ protected TestSkyflow self() { Object vault() throws SkyflowException { return resolveOrThrow(this.builder.vaultClientsMap, null, - ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, BaseErrorMessage.VaultIdNotInConfigList); } Object vault(String vaultId) throws SkyflowException { return resolveOrThrow(this.builder.vaultClientsMap, vaultId, - ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, BaseErrorMessage.VaultIdNotInConfigList); } private static class TestSkyflowClientBuilder extends BaseSkyflowClientBuilder { @@ -365,7 +365,7 @@ private static class TestSkyflowClientBuilder extends BaseSkyflowClientBuilder { }; + + private static GetRunResponse success() { + return new GetRunResponse("run-1", DetectRunStatus.SUCCESS, DataSourceType.BASE64, + Collections.singletonList(new FileOutput(Base64.getEncoder().encodeToString("redacted".getBytes()), + "REDACTED_FILE", FileDataFormat.TXT)), + new Metrics(null, 1, 8, null, null, null), null); + } + + @Test + public void withoutPollOptionsReturnsRunIdAndNeverFetches() throws Exception { + AtomicInteger fetches = new AtomicInteger(); + DeidentifyFileResponse response = DeidentifyFileFlow.complete( + DeidentifyFileRequest.builder().dataSource(DataSourceType.BASE64).value("x").build(), "run-1", + id -> { fetches.incrementAndGet(); return success(); }, NO_SLEEP); + Assert.assertEquals("run-1", response.getRunId()); + Assert.assertNull(response.getStatus()); + Assert.assertTrue(response.getOutput().isEmpty()); + Assert.assertEquals(0, fetches.get()); + } + + @Test + public void missingRunIdSkipsPollingEvenWhenRequested() throws Exception { + AtomicInteger fetches = new AtomicInteger(); + DeidentifyFileResponse response = DeidentifyFileFlow.complete( + DeidentifyFileRequest.builder().dataSource(DataSourceType.BASE64).value("x") + .pollOptions(PollOptions.builder().build()).build(), null, + id -> { fetches.incrementAndGet(); return success(); }, NO_SLEEP); + Assert.assertNull(response.getRunId()); + Assert.assertNull(response.getStatus()); + Assert.assertEquals(0, fetches.get()); + } + + @Test + public void polledRawRequestMergesRunButWritesNothing() throws Exception { + DeidentifyFileResponse response = DeidentifyFileFlow.complete( + DeidentifyFileRequest.builder().dataSource(DataSourceType.SKYFLOW_ID).value("rec") + .pollOptions(PollOptions.builder().build()).outputDirectory(folder.getRoot().getPath()).build(), + "run-1", id -> success(), NO_SLEEP); + Assert.assertEquals(DetectRunStatus.SUCCESS, response.getStatus()); + Assert.assertEquals(1, response.getOutput().size()); + Assert.assertEquals(Integer.valueOf(8), response.getMetrics().getCharacterCount()); + Assert.assertEquals(0, folder.getRoot().list().length); + } + + @Test + public void polledFileRequestWritesOutputsOnSuccess() throws Exception { + File input = folder.newFile("notes.txt"); + Files.write(input.toPath(), "hello".getBytes()); + File outDir = folder.newFolder("out"); + + DeidentifyFileResponse response = DeidentifyFileFlow.complete( + DeidentifyFileRequest.builder().file(input).pollOptions(PollOptions.builder().build()) + .outputDirectory(outDir.getPath()).build(), + "run-1", id -> success(), NO_SLEEP); + + Assert.assertEquals(DetectRunStatus.SUCCESS, response.getStatus()); + File written = new File(outDir, "processed-notes.txt"); + Assert.assertTrue(written.isFile()); + Assert.assertEquals("redacted", new String(Files.readAllBytes(written.toPath()))); + } + + @Test + public void polledFileRequestWritesNothingWhenRunFailsOrIsPending() throws Exception { + File input = folder.newFile("notes.txt"); + File outDir = folder.newFolder("out"); + DeidentifyFileRequest request = DeidentifyFileRequest.builder().file(input) + .pollOptions(PollOptions.builder().maxAttempts(1).build()).outputDirectory(outDir.getPath()).build(); + + DeidentifyFileResponse failed = DeidentifyFileFlow.complete(request, "run-1", + id -> new GetRunResponse(id, DetectRunStatus.FAILED, null, Collections.emptyList(), null, "bad input"), NO_SLEEP); + Assert.assertEquals(DetectRunStatus.FAILED, failed.getStatus()); + Assert.assertEquals("bad input", failed.getMessage()); + + DeidentifyFileResponse pending = DeidentifyFileFlow.complete(request, "run-2", + id -> new GetRunResponse(id, DetectRunStatus.IN_PROGRESS, null, Collections.emptyList(), null, null), NO_SLEEP); + Assert.assertEquals("run-2", pending.getRunId()); + Assert.assertEquals(DetectRunStatus.IN_PROGRESS, pending.getStatus()); + Assert.assertEquals(0, outDir.list().length); + } +} diff --git a/common/src/test/java/com/skyflow/detect/internal/DeidentifyFileMapperTests.java b/common/src/test/java/com/skyflow/detect/internal/DeidentifyFileMapperTests.java new file mode 100644 index 00000000..41886c3c --- /dev/null +++ b/common/src/test/java/com/skyflow/detect/internal/DeidentifyFileMapperTests.java @@ -0,0 +1,146 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.DeidentifyFileRequest; +import com.skyflow.detect.DeidentifyFileResponse; +import com.skyflow.detect.Detect; +import com.skyflow.detect.DetectConfiguration; +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.Entity; +import com.skyflow.detect.EntityType; +import com.skyflow.detect.FileDataFormat; +import com.skyflow.detect.GetRunResponse; +import com.skyflow.errors.BaseErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.DeidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.types.DetectRunsResponseV2; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.nio.file.Files; +import java.util.Base64; +import java.util.Collections; + +public class DeidentifyFileMapperTests { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private static String wire(DeidentifyFileRequestV2 request) throws Exception { + return ObjectMappers.JSON_MAPPER.writeValueAsString(request); + } + + @Test + public void mapsRawValueRequest() throws Exception { + DeidentifyFileRequestV2 api = DetectRequestMapper.toDeidentifyFileRequest(DeidentifyFileRequest.builder() + .dataSource(DataSourceType.SKYFLOW_ID).value("rec-1").configurationId("cfg-1").build(), "vault-1"); + Assert.assertEquals("SKYFLOW_ID", api.getDataSource().toString()); + Assert.assertEquals("rec-1", api.getValue()); + Assert.assertEquals("cfg-1", api.getConfigurationId().get()); + Assert.assertFalse(api.getDataFormat().isPresent()); + Assert.assertFalse(api.getConfiguration().isPresent()); + String json = wire(api); + Assert.assertTrue(json, json.contains("\"dataSource\":\"SKYFLOW_ID\"")); + Assert.assertTrue(json, json.contains("\"configurationId\":\"cfg-1\"")); + Assert.assertFalse(json, json.contains("dataFormat")); + Assert.assertFalse(json, json.contains("\"configuration\"")); + } + + @Test + public void dataFormatIsSentLowerCase() throws Exception { + DeidentifyFileRequestV2 api = DetectRequestMapper.toDeidentifyFileRequest(DeidentifyFileRequest.builder() + .dataSource(DataSourceType.PRESIGNED_URL).value("https://s3/in").dataFormat(FileDataFormat.PDF).build(), "v"); + Assert.assertTrue(wire(api).contains("\"dataFormat\":\"pdf\"")); + } + + @Test + public void inlineConfigurationGetsVaultId() throws Exception { + DeidentifyFileRequestV2 api = DetectRequestMapper.toDeidentifyFileRequest(DeidentifyFileRequest.builder() + .dataSource(DataSourceType.BASE64).value("Zm9v") + .configuration(DetectConfiguration.builder().detect(Detect.builder().entities(Collections.singletonList( + Entity.builder().entityType(EntityType.NAME).build())).build()).build()) + .build(), "vault-9"); + String json = wire(api); + Assert.assertTrue(json, json.contains("\"vaultId\":\"vault-9\"")); + Assert.assertTrue(json, json.contains("\"entityType\":\"NAME\"")); + } + + @Test + public void fileRequestIsEncodedAndFormatInferred() throws Exception { + File input = folder.newFile("patient-notes.PDF"); + byte[] content = {1, 2, 3, 4, 5}; + Files.write(input.toPath(), content); + + DeidentifyFileRequestV2 api = DetectRequestMapper.toDeidentifyFileRequest( + DeidentifyFileRequest.builder().file(input).build(), "v"); + Assert.assertEquals("BASE64", api.getDataSource().toString()); + Assert.assertEquals(Base64.getEncoder().encodeToString(content), api.getValue()); + Assert.assertEquals("pdf", api.getDataFormat().get().toString()); + + DeidentifyFileRequestV2 explicit = DetectRequestMapper.toDeidentifyFileRequest( + DeidentifyFileRequest.builder().filePath(input.getPath()).dataFormat(FileDataFormat.TXT).build(), "v"); + Assert.assertEquals("txt", explicit.getDataFormat().get().toString()); + } + + @Test + public void localFileIsAlwaysSentAsBase64EvenIfDataSourceWasCleared() throws Exception { + File input = folder.newFile("a.txt"); + Files.write(input.toPath(), new byte[]{1}); + DeidentifyFileRequestV2 api = DetectRequestMapper.toDeidentifyFileRequest( + DeidentifyFileRequest.builder().file(input).dataSource(null).build(), "v"); + Assert.assertEquals("BASE64", api.getDataSource().toString()); + } + + @Test + public void unknownOrMissingExtensionLeavesFormatForTheServer() throws Exception { + for (String name : new String[]{"archive.heic", "README", "trailing."}) { + File input = folder.newFile(name); + Files.write(input.toPath(), new byte[]{9}); + DeidentifyFileRequestV2 api = DetectRequestMapper.toDeidentifyFileRequest( + DeidentifyFileRequest.builder().file(input).build(), "v"); + Assert.assertFalse(name, api.getDataFormat().isPresent()); + } + } + + @Test + public void unreadableFileIsReportedAsEncodeFailure() { + try { + DetectRequestMapper.toDeidentifyFileRequest( + DeidentifyFileRequest.builder().file(new File(folder.getRoot(), "nope.txt")).build(), "v"); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(BaseErrorMessage.FailedToEncodeFile.getMessage(), e.getMessage()); + } + } + + @Test + public void submittedResponseCarriesOnlyRunId() { + DeidentifyFileResponse response = DetectResponseMapper.submittedDeidentifyFileResponse("run-7"); + Assert.assertEquals("run-7", response.getRunId()); + Assert.assertNull(response.getStatus()); + Assert.assertNull(response.getOutputType()); + Assert.assertTrue(response.getOutput().isEmpty()); + Assert.assertNull(response.getMetrics()); + Assert.assertNull(response.getMessage()); + } + + @Test + public void polledResponseMergesRunIdWithRun() throws Exception { + DetectRunsResponseV2 api = ObjectMappers.JSON_MAPPER.readValue( + "{\"status\":\"SUCCESS\",\"outputType\":\"BASE64\",\"output\":[{\"processedFile\":\"UERG\"," + + "\"processedFileType\":\"REDACTED_FILE\",\"processedFileExtension\":\"pdf\"}],\"metrics\":{\"pages\":2}}", + DetectRunsResponseV2.class); + GetRunResponse run = DetectResponseMapper.toGetRunResponse("run-7", api); + DeidentifyFileResponse response = DetectResponseMapper.toDeidentifyFileResponse("run-7", run); + Assert.assertEquals("run-7", response.getRunId()); + Assert.assertEquals(DetectRunStatus.SUCCESS, response.getStatus()); + Assert.assertEquals(DataSourceType.BASE64, response.getOutputType()); + Assert.assertEquals("UERG", response.getOutput().get(0).getProcessedFile()); + Assert.assertEquals(FileDataFormat.PDF, response.getOutput().get(0).getProcessedFileExtension()); + Assert.assertEquals(Integer.valueOf(2), response.getMetrics().getPages()); + } +} diff --git a/common/src/test/java/com/skyflow/detect/internal/DeidentifyFileValidationsTests.java b/common/src/test/java/com/skyflow/detect/internal/DeidentifyFileValidationsTests.java new file mode 100644 index 00000000..9f9a3f62 --- /dev/null +++ b/common/src/test/java/com/skyflow/detect/internal/DeidentifyFileValidationsTests.java @@ -0,0 +1,82 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.DeidentifyFileRequest; +import com.skyflow.detect.PollOptions; +import com.skyflow.errors.BaseErrorMessage; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.SkyflowException; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.nio.file.Files; + +public class DeidentifyFileValidationsTests { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private static void assertRejected(DeidentifyFileRequest request, BaseErrorMessage expected) { + try { + DetectValidations.validateDeidentifyFileRequest(request); + Assert.fail("expected SkyflowException: " + expected); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(expected.getMessage(), e.getMessage()); + } + } + + private static DeidentifyFileRequest.DeidentifyFileRequestBuilder raw() { + return DeidentifyFileRequest.builder().dataSource(DataSourceType.BASE64).value("Zm9v"); + } + + @Test + public void rejectsNullRequest() { + assertRejected(null, BaseErrorMessage.DeidentifyFileRequestNull); + } + + @Test + public void rawValueRequiresDataSourceAndValue() throws SkyflowException { + assertRejected(DeidentifyFileRequest.builder().value("x").build(), BaseErrorMessage.InvalidDataSourceInDeidentifyFile); + assertRejected(DeidentifyFileRequest.builder().dataSource(DataSourceType.SKYFLOW_ID).build(), BaseErrorMessage.InvalidValueInDeidentifyFile); + assertRejected(raw().value(" ").build(), BaseErrorMessage.InvalidValueInDeidentifyFile); + DetectValidations.validateDeidentifyFileRequest(raw().build()); + DetectValidations.validateDeidentifyFileRequest(raw().configurationId("cfg").build()); + } + + @Test + public void fileRequestsNeedAnExistingFileAndNoValue() throws Exception { + File existing = folder.newFile("notes.txt"); + Files.write(existing.toPath(), "hello".getBytes("UTF-8")); + DetectValidations.validateDeidentifyFileRequest(DeidentifyFileRequest.builder().file(existing).build()); + DetectValidations.validateDeidentifyFileRequest(DeidentifyFileRequest.builder().filePath(existing.getPath()).build()); + + assertRejected(DeidentifyFileRequest.builder().file(existing).value("Zm9v").build(), BaseErrorMessage.MultipleFileSourcesInDeidentifyFile); + assertRejected(DeidentifyFileRequest.builder().filePath(new File(folder.getRoot(), "missing.txt").getPath()).build(), + BaseErrorMessage.FileNotFoundToDeidentify); + assertRejected(DeidentifyFileRequest.builder().file(folder.getRoot()).build(), BaseErrorMessage.FileNotFoundToDeidentify); + } + + @Test + public void pollOptionsBoundsAreEnforced() throws SkyflowException { + DetectValidations.validateDeidentifyFileRequest(raw().pollOptions(PollOptions.builder().build()).build()); + DetectValidations.validateDeidentifyFileRequest(raw().pollOptions(PollOptions.builder().waitTime(1).maxAttempts(1).build()).build()); + DetectValidations.validateDeidentifyFileRequest(raw().pollOptions(PollOptions.builder().waitTime(300).maxAttempts(23).build()).build()); + for (int bad : new int[]{0, -1, 301}) { + assertRejected(raw().pollOptions(PollOptions.builder().waitTime(bad).build()).build(), BaseErrorMessage.InvalidWaitTimeInPollOptions); + } + for (int bad : new int[]{0, -5, 24}) { + assertRejected(raw().pollOptions(PollOptions.builder().maxAttempts(bad).build()).build(), BaseErrorMessage.InvalidMaxAttemptsInPollOptions); + } + } + + @Test + public void outputDirectoryRequiresPollOptions() throws SkyflowException { + assertRejected(raw().outputDirectory("/tmp/out").build(), BaseErrorMessage.OutputDirectoryWithoutPollOptions); + DetectValidations.validateDeidentifyFileRequest(raw().outputDirectory(" ").build()); + DetectValidations.validateDeidentifyFileRequest(raw().outputDirectory("/tmp/out").pollOptions(PollOptions.builder().build()).build()); + } +} diff --git a/common/src/test/java/com/skyflow/detect/internal/DetectRequestMapperTests.java b/common/src/test/java/com/skyflow/detect/internal/DetectRequestMapperTests.java new file mode 100644 index 00000000..facad0ec --- /dev/null +++ b/common/src/test/java/com/skyflow/detect/internal/DetectRequestMapperTests.java @@ -0,0 +1,161 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.CheckGuardrailsRequest; +import com.skyflow.detect.CustomType; +import com.skyflow.detect.DeidentificationType; +import com.skyflow.detect.DeidentifyStringRequest; +import com.skyflow.detect.Detect; +import com.skyflow.detect.DetectConfiguration; +import com.skyflow.detect.Entity; +import com.skyflow.detect.EntityType; +import com.skyflow.detect.Image; +import com.skyflow.detect.MaskingMethod; +import com.skyflow.detect.Media; +import com.skyflow.detect.RedactionLevel; +import com.skyflow.detect.RedactionType; +import com.skyflow.detect.ReidentifyStringRequest; +import com.skyflow.detect.ReturnEntitiesType; +import com.skyflow.detect.ShiftDates; +import com.skyflow.detect.Transformation; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.resources.guardrailsv2.requests.DetectGuardrailsRequestV2; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.DeidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.resources.stringsv2.requests.ReidentifyStringRequestV2; +import com.skyflow.generated.detect.rest.types.DetectConfigV2; +import com.skyflow.detect.FileMapping; +import com.skyflow.detect.ObjectEntity; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +public class DetectRequestMapperTests { + + @Test + public void storedConfigurationSendsOnlyTextAndId() throws Exception { + DeidentifyStringRequestV2 mapped = DetectRequestMapper.toDeidentifyStringRequest( + DeidentifyStringRequest.builder().text("hello").configurationId("cfg-1").build(), "vault-1"); + Assert.assertEquals("hello", mapped.getText()); + Assert.assertEquals("cfg-1", mapped.getConfigurationId().get()); + Assert.assertFalse(mapped.getConfiguration().isPresent()); + String json = ObjectMappers.JSON_MAPPER.writeValueAsString(mapped); + Assert.assertTrue(json, json.contains("\"configurationId\":\"cfg-1\"")); + Assert.assertFalse("no vault id outside a configuration: " + json, json.contains("vaultId")); + Assert.assertFalse(json, json.contains("\"configuration\":")); + } + + @Test + public void inlineConfigurationIsScopedToVaultAndFullyMapped() throws Exception { + DetectConfiguration configuration = DetectConfiguration.builder() + .name("inline") + .detect(Detect.builder() + .entities(Arrays.asList( + Entity.builder().entityType(EntityType.NAME) + .deidentificationType(DeidentificationType.VAULT_TOKEN) + .destination("persons.name").build(), + Entity.builder().entityType(EntityType.DOB) + .deidentificationType(DeidentificationType.ENTITY_UNIQUE_COUNTER) + .transformation(Transformation.builder() + .shiftDates(ShiftDates.builder().minDays(10).maxDays(30).build()).build()) + .build())) + .restrict(Collections.singletonList("[0-9]{3}-[0-9]{2}-[0-9]{4}")) + .skip(Collections.singletonList("Skyflow")) + .customTypes(Collections.singletonList( + CustomType.builder().label("EMPLOYEE_ID").match(Collections.singletonList("EMP-[0-9]{6}")).build())) + .returnEntities(ReturnEntitiesType.ALL) + .build()) + .media(Media.builder().image(Image.builder().maskingMethod(MaskingMethod.BLUR).outputOcrText(true).build()).build()) + .build(); + + DeidentifyStringRequestV2 mapped = DetectRequestMapper.toDeidentifyStringRequest( + DeidentifyStringRequest.builder().text("hello").configuration(configuration).build(), "vault-1"); + + DetectConfigV2 config = mapped.getConfiguration().get(); + Assert.assertEquals("vault-1", config.getVaultId()); + Assert.assertEquals("inline", config.getName().get()); + Assert.assertFalse(mapped.getConfigurationId().isPresent()); + + String json = ObjectMappers.JSON_MAPPER.writeValueAsString(mapped); + Assert.assertEquals("vault id appears once, inside the inline configuration", + 1, json.split("\"vaultId\":\"vault-1\"", -1).length - 1); + for (String expected : new String[]{ + "\"vaultId\":\"vault-1\"", "\"entityType\":\"NAME\"", "\"deidentificationType\":\"VAULT_TOKEN\"", + "\"destination\":\"persons.name\"", "\"minDays\":10", "\"maxDays\":30", + "\"restrict\":[\"[0-9]{3}-[0-9]{2}-[0-9]{4}\"]", "\"skip\":[\"Skyflow\"]", + "\"label\":\"EMPLOYEE_ID\"", "\"returnEntities\":\"ALL\"", "\"maskingMethod\":\"BLUR\"", "\"outputOcrText\":true"}) { + Assert.assertTrue("missing " + expected + " in " + json, json.contains(expected)); + } + Assert.assertFalse("unset fields must be omitted: " + json, json.contains("\"description\"")); + } + + @Test + public void reidentifyStringCarriesVaultIdAndRedactionLevels() throws Exception { + ReidentifyStringRequestV2 mapped = DetectRequestMapper.toReidentifyStringRequest( + ReidentifyStringRequest.builder() + .text("[NAME_1]") + .redactionLevel(Arrays.asList( + RedactionLevel.builder().entityName(EntityType.NAME).redactionType(RedactionType.MASKED).build(), + RedactionLevel.builder().tokenGroupName("group").redactionPattern("pattern").build())) + .build(), "vault-1"); + Assert.assertEquals("vault-1", mapped.getVaultId()); + Assert.assertEquals("[NAME_1]", mapped.getText()); + Assert.assertEquals(2, mapped.getRedactionLevel().get().size()); + String json = ObjectMappers.JSON_MAPPER.writeValueAsString(mapped); + Assert.assertTrue(json, json.contains("\"entityName\":\"NAME\"")); + Assert.assertTrue(json, json.contains("\"redactionType\":\"MASKED\"")); + Assert.assertTrue(json, json.contains("\"tokenGroupName\":\"group\"")); + Assert.assertTrue(json, json.contains("\"redactionPattern\":\"pattern\"")); + } + + @Test + public void reidentifyStringWithoutRedactionLevelsOmitsField() throws Exception { + ReidentifyStringRequestV2 mapped = DetectRequestMapper.toReidentifyStringRequest( + ReidentifyStringRequest.builder().text("[NAME_1]").build(), "vault-1"); + Assert.assertFalse(mapped.getRedactionLevel().isPresent()); + Assert.assertFalse(ObjectMappers.JSON_MAPPER.writeValueAsString(mapped).contains("redactionLevel")); + } + + @Test + public void everyPublicEntityTypeMapsToAGeneratedValue() { + for (EntityType type : EntityType.values()) { + Entity entity = Entity.builder().entityType(type).build(); + Assert.assertEquals(type.name(), DetectRequestMapper.toEntity(entity).getEntityType().get().toString()); + RedactionLevel level = RedactionLevel.builder().entityName(type).redactionType(RedactionType.DEFAULT).build(); + Assert.assertEquals(type.name(), + DetectRequestMapper.toRedactionLevels(Collections.singletonList(level)).get(0).getEntityName().get().toString()); + } + } + + @Test + public void checkGuardrailsCarriesVaultIdAndOnlySetOptions() throws Exception { + DetectGuardrailsRequestV2 full = DetectRequestMapper.toCheckGuardrailsRequest( + CheckGuardrailsRequest.builder().text("t").checkToxicity(false).denyTopics(Collections.singletonList("x")).build(), "vault-1"); + Assert.assertEquals("vault-1", full.getVaultId()); + Assert.assertEquals("t", full.getText()); + Assert.assertEquals(Boolean.FALSE, full.getCheckToxicity().get()); + Assert.assertEquals(Collections.singletonList("x"), full.getDenyTopics().get()); + + DetectGuardrailsRequestV2 minimal = DetectRequestMapper.toCheckGuardrailsRequest( + CheckGuardrailsRequest.builder().text("t").build(), "vault-1"); + Assert.assertFalse(minimal.getCheckToxicity().isPresent()); + Assert.assertFalse(minimal.getDenyTopics().isPresent()); + } + + @Test + public void nullElementsInConfigurationListsAreSkipped() throws Exception { + com.skyflow.generated.detect.rest.types.DetectConfigV2 api = DetectRequestMapper.toDetectConfig( + DetectConfiguration.builder() + .detect(Detect.builder() + .entities(Arrays.asList(null, Entity.builder().entityType(EntityType.NAME).build())) + .objectEntities(Arrays.asList((ObjectEntity) null)) + .customTypes(Arrays.asList(null, CustomType.builder().label("ID").build())) + .build()) + .fileMapping(Arrays.asList(null, FileMapping.builder().source("in").build())) + .build(), "v"); + Assert.assertEquals(1, api.getDetect().get().getEntities().get().size()); + Assert.assertTrue(api.getDetect().get().getObjectEntities().get().isEmpty()); + Assert.assertEquals(1, api.getDetect().get().getCustomTypes().get().size()); + Assert.assertEquals(1, api.getFileMapping().get().size()); + } +} diff --git a/common/src/test/java/com/skyflow/detect/internal/DetectResponseMapperTests.java b/common/src/test/java/com/skyflow/detect/internal/DetectResponseMapperTests.java new file mode 100644 index 00000000..0a4ab894 --- /dev/null +++ b/common/src/test/java/com/skyflow/detect/internal/DetectResponseMapperTests.java @@ -0,0 +1,140 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.CheckGuardrailsResponse; +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.DeidentifyStringResponse; +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.FileDataFormat; +import com.skyflow.detect.GetRunResponse; +import com.skyflow.detect.GuardrailsValidation; +import com.skyflow.detect.DetectedEntity; +import com.skyflow.detect.ReidentifyStringResponse; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.types.DeidentifyStringResponseV2; +import com.skyflow.generated.detect.rest.types.DetectGuardrailsResponseV2; +import com.skyflow.generated.detect.rest.types.DetectRunsResponseV2; +import com.skyflow.generated.detect.rest.types.ReidentifyStringResponseV2; +import org.junit.Assert; +import org.junit.Test; + +public class DetectResponseMapperTests { + + @Test + public void mapsFullDeidentifyStringResponse() throws Exception { + String json = "{\"processedText\":\"My name is [NAME_1].\",\"entities\":[{\"token\":\"NAME_1\",\"value\":\"\"," + + "\"location\":{\"startIndex\":11,\"endIndex\":19,\"startIndexProcessed\":11,\"endIndexProcessed\":19}," + + "\"entityType\":\"NAME\",\"entityScores\":{\"NAME\":0.9152}}]," + + "\"metrics\":{\"size\":0.05,\"wordCount\":4,\"characterCount\":20,\"duration\":0.2}}"; + DeidentifyStringResponseV2 api = ObjectMappers.JSON_MAPPER.readValue(json, DeidentifyStringResponseV2.class); + + DeidentifyStringResponse response = DetectResponseMapper.toDeidentifyStringResponse(api); + Assert.assertEquals("My name is [NAME_1].", response.getProcessedText()); + Assert.assertEquals(1, response.getEntities().size()); + DetectedEntity entity = response.getEntities().get(0); + Assert.assertEquals("NAME_1", entity.getToken()); + Assert.assertEquals("", entity.getValue()); + Assert.assertEquals("NAME", entity.getEntityType()); + Assert.assertEquals(Integer.valueOf(11), entity.getLocation().getStartIndex()); + Assert.assertEquals(Integer.valueOf(19), entity.getLocation().getEndIndexProcessed()); + Assert.assertEquals(0.9152, entity.getEntityScores().get("NAME"), 1e-9); + Assert.assertEquals(Integer.valueOf(4), response.getMetrics().getWordCount()); + Assert.assertEquals(Integer.valueOf(20), response.getMetrics().getCharacterCount()); + Assert.assertEquals(0.05, response.getMetrics().getSize(), 1e-9); + Assert.assertNull(response.getMetrics().getPages()); + } + + @Test + public void emptyResponseYieldsEmptyEntitiesNotNull() throws Exception { + DeidentifyStringResponseV2 api = ObjectMappers.JSON_MAPPER.readValue("{}", DeidentifyStringResponseV2.class); + DeidentifyStringResponse response = DetectResponseMapper.toDeidentifyStringResponse(api); + Assert.assertNull(response.getProcessedText()); + Assert.assertNotNull(response.getEntities()); + Assert.assertTrue(response.getEntities().isEmpty()); + Assert.assertNull(response.getMetrics()); + } + + @Test + public void nullListElementsFromTheApiAreSkipped() throws Exception { + DeidentifyStringResponseV2 strings = ObjectMappers.JSON_MAPPER.readValue( + "{\"entities\":[null,{\"token\":\"NAME_1\"},null]}", DeidentifyStringResponseV2.class); + Assert.assertEquals(1, DetectResponseMapper.toDeidentifyStringResponse(strings).getEntities().size()); + + DetectRunsResponseV2 run = ObjectMappers.JSON_MAPPER.readValue( + "{\"status\":\"SUCCESS\",\"output\":[null,{\"processedFile\":\"x\"}]}", DetectRunsResponseV2.class); + Assert.assertEquals(1, DetectResponseMapper.toGetRunResponse("r", run).getOutput().size()); + } + + @Test + public void guardrailsResponseWithoutValidationMapsToUnknown() throws Exception { + DetectGuardrailsResponseV2 api = ObjectMappers.JSON_MAPPER.readValue("{}", DetectGuardrailsResponseV2.class); + CheckGuardrailsResponse response = DetectResponseMapper.toCheckGuardrailsResponse(api); + Assert.assertNull(response.getText()); + Assert.assertEquals(GuardrailsValidation.UNKNOWN, response.getValidation()); + Assert.assertNull(response.getToxic()); + Assert.assertNull(response.getDeniedTopic()); + } + + @Test + public void entityWithoutScoresGetsEmptyMap() throws Exception { + DeidentifyStringResponseV2 api = ObjectMappers.JSON_MAPPER.readValue( + "{\"entities\":[{\"token\":\"NAME_1\"}]}", DeidentifyStringResponseV2.class); + DetectedEntity entity = DetectResponseMapper.toDeidentifyStringResponse(api).getEntities().get(0); + Assert.assertNotNull(entity.getEntityScores()); + Assert.assertTrue(entity.getEntityScores().isEmpty()); + Assert.assertNull(entity.getLocation()); + } + + @Test + public void mapsReidentifyStringResponse() throws Exception { + ReidentifyStringResponseV2 api = ObjectMappers.JSON_MAPPER.readValue( + "{\"processedText\":\"My name is John Doe.\",\"metrics\":{\"wordCount\":5}}", ReidentifyStringResponseV2.class); + ReidentifyStringResponse response = DetectResponseMapper.toReidentifyStringResponse(api); + Assert.assertEquals("My name is John Doe.", response.getProcessedText()); + Assert.assertEquals(Integer.valueOf(5), response.getMetrics().getWordCount()); + } + + @Test + public void mapsCompletedRun() throws Exception { + DetectRunsResponseV2 api = ObjectMappers.JSON_MAPPER.readValue( + "{\"status\":\"SUCCESS\",\"outputType\":\"PRESIGNED_URL\",\"output\":[{\"processedFile\":\"https://x/y.pdf\"," + + "\"processedFileType\":\"REDACTED_FILE\",\"processedFileExtension\":\"pdf\"}],\"metrics\":{\"pages\":2}}", + DetectRunsResponseV2.class); + GetRunResponse run = DetectResponseMapper.toGetRunResponse("run-1", api); + Assert.assertEquals("run-1", run.getRunId()); + Assert.assertEquals(DetectRunStatus.SUCCESS, run.getStatus()); + Assert.assertEquals(DataSourceType.PRESIGNED_URL, run.getOutputType()); + Assert.assertEquals("https://x/y.pdf", run.getOutput().get(0).getProcessedFile()); + Assert.assertEquals(FileDataFormat.PDF, run.getOutput().get(0).getProcessedFileExtension()); + Assert.assertEquals(Integer.valueOf(2), run.getMetrics().getPages()); + } + + @Test + public void runWithoutStatusIsUnknownAndUnknownWireValuesAreTolerated() throws Exception { + GetRunResponse empty = DetectResponseMapper.toGetRunResponse("r", + ObjectMappers.JSON_MAPPER.readValue("{}", DetectRunsResponseV2.class)); + Assert.assertEquals(DetectRunStatus.UNKNOWN, empty.getStatus()); + Assert.assertTrue(empty.getOutput().isEmpty()); + Assert.assertNull(empty.getOutputType()); + + GetRunResponse odd = DetectResponseMapper.toGetRunResponse("r", ObjectMappers.JSON_MAPPER.readValue( + "{\"status\":\"NEW_STATE\",\"outputType\":\"FTP\",\"output\":[{\"processedFileExtension\":\"heic\"}]}", + DetectRunsResponseV2.class)); + Assert.assertEquals(DetectRunStatus.UNKNOWN, odd.getStatus()); + Assert.assertNull(odd.getOutputType()); + Assert.assertNull(odd.getOutput().get(0).getProcessedFileExtension()); + } + + @Test + public void mapsGuardrailsResponseAndToleratesUnknownValidation() throws Exception { + CheckGuardrailsResponse failed = DetectResponseMapper.toCheckGuardrailsResponse(ObjectMappers.JSON_MAPPER.readValue( + "{\"text\":\"t\",\"validation\":\"FAILED\",\"toxic\":true}", DetectGuardrailsResponseV2.class)); + Assert.assertEquals("t", failed.getText()); + Assert.assertEquals(GuardrailsValidation.FAILED, failed.getValidation()); + Assert.assertEquals(Boolean.TRUE, failed.getToxic()); + Assert.assertNull(failed.getDeniedTopic()); + + CheckGuardrailsResponse odd = DetectResponseMapper.toCheckGuardrailsResponse(ObjectMappers.JSON_MAPPER.readValue( + "{\"text\":\"t\",\"validation\":\"MAYBE\"}", DetectGuardrailsResponseV2.class)); + Assert.assertEquals(GuardrailsValidation.UNKNOWN, odd.getValidation()); + } +} diff --git a/common/src/test/java/com/skyflow/detect/internal/DetectValidationsTests.java b/common/src/test/java/com/skyflow/detect/internal/DetectValidationsTests.java new file mode 100644 index 00000000..dd154096 --- /dev/null +++ b/common/src/test/java/com/skyflow/detect/internal/DetectValidationsTests.java @@ -0,0 +1,138 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.CheckGuardrailsRequest; +import com.skyflow.detect.DeidentifyStringRequest; +import com.skyflow.detect.DetectConfiguration; +import com.skyflow.detect.EntityType; +import com.skyflow.detect.GetRunRequest; +import com.skyflow.detect.RedactionLevel; +import com.skyflow.detect.RedactionType; +import com.skyflow.detect.ReidentifyStringRequest; +import com.skyflow.errors.BaseErrorMessage; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.SkyflowException; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +public class DetectValidationsTests { + + private static void assertInvalid(SkyflowException e, BaseErrorMessage expected) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(expected.getMessage(), e.getMessage()); + } + + @Test + public void deidentifyStringRejectsNullRequest() { + try { + DetectValidations.validateDeidentifyStringRequest(null); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + assertInvalid(e, BaseErrorMessage.DeidentifyStringRequestNull); + } + } + + @Test + public void deidentifyStringRejectsBlankText() { + try { + DetectValidations.validateDeidentifyStringRequest( + DeidentifyStringRequest.builder().text(" ").configurationId("cfg").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + assertInvalid(e, BaseErrorMessage.InvalidTextInDeidentifyString); + } + } + + @Test + public void deidentifyStringDoesNotConstrainConfigurationSources() throws SkyflowException { + // configurationId and configuration are both optional on the API: none, either or both pass through + DetectValidations.validateDeidentifyStringRequest(DeidentifyStringRequest.builder().text("hello").build()); + DetectValidations.validateDeidentifyStringRequest(DeidentifyStringRequest.builder().text("hello") + .configurationId("cfg").configuration(DetectConfiguration.builder().build()).build()); + DetectValidations.validateDeidentifyStringRequest( + DeidentifyStringRequest.builder().text("hello").configurationId("cfg").build()); + DetectValidations.validateDeidentifyStringRequest( + DeidentifyStringRequest.builder().text("hello") + .configuration(DetectConfiguration.builder().build()).build()); + } + + @Test + public void reidentifyStringRejectsNullAndBlank() { + try { + DetectValidations.validateReidentifyStringRequest(null); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + assertInvalid(e, BaseErrorMessage.ReidentifyStringRequestNull); + } + try { + DetectValidations.validateReidentifyStringRequest(ReidentifyStringRequest.builder().text("").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + assertInvalid(e, BaseErrorMessage.InvalidTextInReidentifyString); + } + } + + @Test + public void reidentifyStringAcceptsValidRedactionLevels() throws SkyflowException { + DetectValidations.validateReidentifyStringRequest(ReidentifyStringRequest.builder() + .text("[NAME_1]") + .redactionLevel(Arrays.asList( + RedactionLevel.builder().entityName(EntityType.NAME).redactionType(RedactionType.MASKED).build(), + RedactionLevel.builder().tokenGroupName("group").redactionPattern("pattern").build())) + .build()); + DetectValidations.validateReidentifyStringRequest(ReidentifyStringRequest.builder().text("[NAME_1]").build()); + } + + @Test + public void reidentifyStringPassesAnyRedactionLevelShapeThrough() throws SkyflowException { + // The API marks every RedactionLevel field optional; the server applies its own rules. + DetectValidations.validateReidentifyStringRequest(ReidentifyStringRequest.builder().text("[NAME_1]") + .redactionLevel(Arrays.asList( + RedactionLevel.builder().build(), + RedactionLevel.builder().tokenGroupName("g").build(), + RedactionLevel.builder().entityName(EntityType.NAME).redactionType(RedactionType.MASKED).redactionPattern("p").build(), + null)) + .build()); + } + + @Test + public void getRunRequiresRunId() throws SkyflowException { + try { + DetectValidations.validateGetRunRequest(null); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + assertInvalid(e, BaseErrorMessage.GetRunRequestNull); + } + try { + DetectValidations.validateGetRunRequest(GetRunRequest.builder().runId(" ").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + assertInvalid(e, BaseErrorMessage.InvalidRunIdInGetRun); + } + DetectValidations.validateGetRunRequest(GetRunRequest.builder().runId("run-1").build()); + } + + @Test + public void checkGuardrailsRequiresOnlyText() throws SkyflowException { + try { + DetectValidations.validateCheckGuardrailsRequest(null); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + assertInvalid(e, BaseErrorMessage.CheckGuardrailsRequestNull); + } + try { + DetectValidations.validateCheckGuardrailsRequest(CheckGuardrailsRequest.builder().build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + assertInvalid(e, BaseErrorMessage.InvalidTextInCheckGuardrails); + } + // denyTopics is optional on the API and its entries are not inspected by the SDK. + DetectValidations.validateCheckGuardrailsRequest( + CheckGuardrailsRequest.builder().text("hi").denyTopics(Arrays.asList("politics", "", null)).build()); + DetectValidations.validateCheckGuardrailsRequest(CheckGuardrailsRequest.builder().text("hi").build()); + DetectValidations.validateCheckGuardrailsRequest( + CheckGuardrailsRequest.builder().text("hi").checkToxicity(true).denyTopics(Collections.singletonList("politics")).build()); + } +} diff --git a/common/src/test/java/com/skyflow/detect/internal/FileOutputWriterTests.java b/common/src/test/java/com/skyflow/detect/internal/FileOutputWriterTests.java new file mode 100644 index 00000000..4672d7a0 --- /dev/null +++ b/common/src/test/java/com/skyflow/detect/internal/FileOutputWriterTests.java @@ -0,0 +1,119 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.FileDataFormat; +import com.skyflow.detect.FileOutput; +import com.skyflow.errors.SkyflowException; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.List; + +public class FileOutputWriterTests { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private static String b64(String text) { + return Base64.getEncoder().encodeToString(text.getBytes()); + } + + @Test + public void writesEachOutputWithV1StyleNames() throws Exception { + List written = FileOutputWriter.write(Arrays.asList( + new FileOutput(b64("%PDF"), "REDACTED_FILE", FileDataFormat.PDF), + new FileOutput(b64("[]"), "ENTITIES", null)), + DataSourceType.BASE64, "invoice.pdf", folder.getRoot().getPath()); + + Assert.assertEquals(2, written.size()); + Assert.assertEquals("processed-invoice.pdf", written.get(0).getName()); + Assert.assertEquals("processed-entities-invoice.json", written.get(1).getName()); + Assert.assertEquals("%PDF", new String(Files.readAllBytes(written.get(0).toPath()))); + Assert.assertEquals("[]", new String(Files.readAllBytes(written.get(1).toPath()))); + Assert.assertEquals(folder.getRoot(), written.get(0).getParentFile()); + } + + @Test + public void entityOutputsCarryTheirTypeInTheName() throws Exception { + List written = FileOutputWriter.write(Arrays.asList( + new FileOutput(b64("a"), "ENTITIES", FileDataFormat.JSON), + new FileOutput(b64("b"), "OBJECT_ENTITIES", FileDataFormat.JSON)), + DataSourceType.BASE64, "scan.png", folder.getRoot().getPath()); + Assert.assertEquals("processed-entities-scan.json", written.get(0).getName()); + Assert.assertEquals("processed-object-entities-scan.json", written.get(1).getName()); + } + + @Test + public void jsonInputDoesNotCollideWithItsEntitiesOutput() throws Exception { + List written = FileOutputWriter.write(Arrays.asList( + new FileOutput(b64("{}"), "REDACTED_TEXT", FileDataFormat.JSON), + new FileOutput(b64("[]"), "ENTITIES", FileDataFormat.JSON)), + DataSourceType.BASE64, "data.json", folder.getRoot().getPath()); + Assert.assertEquals("processed-data.json", written.get(0).getName()); + Assert.assertEquals("processed-entities-data.json", written.get(1).getName()); + } + + @Test + public void nameCollisionsGetTheFileTypeInserted() throws Exception { + List written = FileOutputWriter.write(Arrays.asList( + new FileOutput(b64("a"), "REDACTED_FILE", FileDataFormat.TXT), + new FileOutput(b64("b"), "REDACTED_TEXT", FileDataFormat.TXT)), + DataSourceType.BASE64, "notes.txt", folder.getRoot().getPath()); + Assert.assertEquals("processed-notes.txt", written.get(0).getName()); + Assert.assertEquals("processed-notes.redacted_text.txt", written.get(1).getName()); + } + + @Test + public void createsMissingOutputDirectory() throws Exception { + File nested = new File(folder.getRoot(), "a/b/c"); + List written = FileOutputWriter.write( + Collections.singletonList(new FileOutput(b64("x"), "REDACTED_TEXT", FileDataFormat.TXT)), + DataSourceType.BASE64, "notes.txt", nested.getPath()); + Assert.assertTrue(nested.isDirectory()); + Assert.assertEquals(new File(nested, "processed-notes.txt"), written.get(0)); + } + + @Test + public void presignedUrlOutputsAreNotWritten() throws Exception { + List written = FileOutputWriter.write( + Collections.singletonList(new FileOutput("https://s3/out", "REDACTED_FILE", FileDataFormat.PDF)), + DataSourceType.PRESIGNED_URL, "invoice.pdf", folder.getRoot().getPath()); + Assert.assertTrue(written.isEmpty()); + Assert.assertEquals(0, folder.getRoot().list().length); + } + + @Test + public void emptyOrNullOutputsWriteNothing() throws Exception { + Assert.assertTrue(FileOutputWriter.write(null, DataSourceType.BASE64, "x.txt", folder.getRoot().getPath()).isEmpty()); + Assert.assertTrue(FileOutputWriter.write(Collections.emptyList(), DataSourceType.BASE64, "x.txt", folder.getRoot().getPath()).isEmpty()); + Assert.assertTrue(FileOutputWriter.write(Collections.singletonList(new FileOutput(null, "ENTITIES", null)), + DataSourceType.BASE64, "x.txt", folder.getRoot().getPath()).isEmpty()); + } + + @Test + public void invalidBase64IsReportedAsWriteFailure() { + try { + FileOutputWriter.write(Collections.singletonList(new FileOutput("not base64!", "REDACTED_FILE", FileDataFormat.TXT)), + DataSourceType.BASE64, "x.txt", folder.getRoot().getPath()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage(), e.getMessage().contains("processed-x.txt")); + } + } + + @Test + public void nameHelpers() { + Assert.assertEquals("notes", FileOutputWriter.stripExtension("notes.txt")); + Assert.assertEquals("archive.tar", FileOutputWriter.stripExtension("archive.tar.gz")); + Assert.assertEquals("README", FileOutputWriter.stripExtension("README")); + Assert.assertEquals(".hidden", FileOutputWriter.stripExtension(".hidden")); + Assert.assertEquals("output", FileOutputWriter.stripExtension(null)); + } +} diff --git a/common/src/test/java/com/skyflow/detect/polling/RunPollerTests.java b/common/src/test/java/com/skyflow/detect/polling/RunPollerTests.java new file mode 100644 index 00000000..1030eec6 --- /dev/null +++ b/common/src/test/java/com/skyflow/detect/polling/RunPollerTests.java @@ -0,0 +1,162 @@ +package com.skyflow.detect.polling; + +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.GetRunResponse; +import com.skyflow.detect.PollOptions; +import com.skyflow.errors.SkyflowException; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public class RunPollerTests { + + private static GetRunResponse run(DetectRunStatus status) { + return new GetRunResponse("run-1", status, status == DetectRunStatus.SUCCESS ? DataSourceType.BASE64 : null, + Collections.emptyList(), null, status == DetectRunStatus.FAILED ? "boom" : null); + } + + /** Serves a scripted sequence of statuses and records every sleep instead of sleeping. */ + private static final class Script implements RunPoller.RunFetcher, RunPoller.Sleeper { + final Iterator statuses; + final List sleepsMillis = new ArrayList<>(); + int fetches; + + Script(DetectRunStatus... statuses) { + this.statuses = Arrays.asList(statuses).iterator(); + } + + @Override + public GetRunResponse fetch(String runId) { + Assert.assertEquals("run-1", runId); + fetches++; + Assert.assertTrue("fetched more times than scripted", statuses.hasNext()); + return run(statuses.next()); + } + + @Override + public void sleep(long millis) { + sleepsMillis.add(millis); + } + } + + private static PollOptions options(Integer waitTime, Integer maxAttempts) { + return PollOptions.builder().waitTime(waitTime).maxAttempts(maxAttempts).build(); + } + + @Test + public void terminalStatusesStopImmediatelyWithoutSleeping() throws SkyflowException { + for (DetectRunStatus terminal : new DetectRunStatus[]{DetectRunStatus.SUCCESS, DetectRunStatus.FAILED, DetectRunStatus.UNKNOWN}) { + Script script = new Script(terminal); + GetRunResponse result = RunPoller.poll("run-1", options(null, null), script, script); + Assert.assertEquals(terminal, result.getStatus()); + Assert.assertEquals(1, script.fetches); + Assert.assertTrue(script.sleepsMillis.isEmpty()); + } + } + + @Test + public void nonTerminalStatusesKeepPollingWithExponentialBackoff() throws SkyflowException { + Script script = new Script(DetectRunStatus.QUEUED, DetectRunStatus.QUEUED, DetectRunStatus.IN_PROGRESS, + DetectRunStatus.IN_PROGRESS, DetectRunStatus.SUCCESS); + GetRunResponse result = RunPoller.poll("run-1", options(null, null), script, script); + Assert.assertEquals(DetectRunStatus.SUCCESS, result.getStatus()); + Assert.assertEquals(5, script.fetches); + Assert.assertEquals(Arrays.asList(1000L, 2000L, 4000L, 8000L), script.sleepsMillis); + } + + @Test + public void defaultWaitTimeIsSpentExactlyThenRunIdIsReturned() throws SkyflowException { + DetectRunStatus[] forever = new DetectRunStatus[30]; + Arrays.fill(forever, DetectRunStatus.IN_PROGRESS); + Script script = new Script(forever); + GetRunResponse result = RunPoller.poll("run-1", options(null, null), script, script); + Assert.assertEquals(DetectRunStatus.IN_PROGRESS, result.getStatus()); + // 1+2+4+8+16 = 31, then the 32 s step is trimmed to 29 so the total is exactly 60. + Assert.assertEquals(Arrays.asList(1000L, 2000L, 4000L, 8000L, 16000L, 29000L), script.sleepsMillis); + Assert.assertEquals(7, script.fetches); + } + + @Test + public void delayPlateausAt32SecondsForLongWaits() throws SkyflowException { + DetectRunStatus[] forever = new DetectRunStatus[30]; + Arrays.fill(forever, DetectRunStatus.QUEUED); + Script script = new Script(forever); + RunPoller.poll("run-1", options(300, null), script, script); + // 1+2+4+8+16 = 31, then eight 32 s steps reach 287, and the last step is trimmed to 13 so the total is exactly 300. + Assert.assertEquals(Arrays.asList(1000L, 2000L, 4000L, 8000L, 16000L, 32000L, 32000L, 32000L, 32000L, 32000L, + 32000L, 32000L, 32000L, 13000L), script.sleepsMillis); + Assert.assertEquals(15, script.fetches); + } + + @Test + public void maxAttemptsCapsLookupsBeforeWaitTime() throws SkyflowException { + Script script = new Script(DetectRunStatus.IN_PROGRESS, DetectRunStatus.IN_PROGRESS, DetectRunStatus.IN_PROGRESS); + GetRunResponse result = RunPoller.poll("run-1", options(300, 3), script, script); + Assert.assertEquals(DetectRunStatus.IN_PROGRESS, result.getStatus()); + Assert.assertEquals(3, script.fetches); + Assert.assertEquals(Arrays.asList(1000L, 2000L), script.sleepsMillis); + } + + @Test + public void singleAttemptNeverSleeps() throws SkyflowException { + Script script = new Script(DetectRunStatus.QUEUED); + RunPoller.poll("run-1", options(null, 1), script, script); + Assert.assertEquals(1, script.fetches); + Assert.assertTrue(script.sleepsMillis.isEmpty()); + } + + @Test + public void shortWaitTimeTrimsTheFirstSleep() throws SkyflowException { + Script script = new Script(DetectRunStatus.QUEUED, DetectRunStatus.QUEUED); + RunPoller.poll("run-1", options(1, null), script, script); + Assert.assertEquals(Collections.singletonList(1000L), script.sleepsMillis); + Assert.assertEquals(2, script.fetches); + } + + @Test + public void interruptionReturnsLastRunAndRestoresFlag() throws SkyflowException { + Script script = new Script(DetectRunStatus.IN_PROGRESS, DetectRunStatus.SUCCESS); + RunPoller.Sleeper interrupting = millis -> { + throw new InterruptedException(); + }; + try { + GetRunResponse result = RunPoller.poll("run-1", options(null, null), script, interrupting); + Assert.assertEquals(DetectRunStatus.IN_PROGRESS, result.getStatus()); + Assert.assertEquals(1, script.fetches); + Assert.assertTrue(Thread.currentThread().isInterrupted()); + } finally { + Thread.interrupted(); // clear so later tests are unaffected + } + } + + @Test + public void nullRunOrStatusFromFetcherIsTerminalUnknown() throws SkyflowException { + GetRunResponse fromNull = RunPoller.poll("run-1", options(null, null), runId -> null, millis -> { }); + Assert.assertEquals("run-1", fromNull.getRunId()); + Assert.assertEquals(DetectRunStatus.UNKNOWN, fromNull.getStatus()); + Assert.assertTrue(fromNull.getOutput().isEmpty()); + + GetRunResponse fromNullStatus = RunPoller.poll("run-1", options(null, null), + runId -> new GetRunResponse(runId, null, null, Collections.emptyList(), null, null), millis -> { }); + Assert.assertEquals(DetectRunStatus.UNKNOWN, fromNullStatus.getStatus()); + } + + @Test + public void fetchFailurePropagates() { + RunPoller.RunFetcher failing = runId -> { + throw new SkyflowException(404, "run not found"); + }; + try { + RunPoller.poll("run-1", options(null, null), failing, millis -> { }); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(404, e.getHttpCode()); + } + } +} diff --git a/common/src/test/java/com/skyflow/serviceaccount/util/BearerTokenTests.java b/common/src/test/java/com/skyflow/serviceaccount/util/BearerTokenTests.java index 141548f8..9557b48b 100644 --- a/common/src/test/java/com/skyflow/serviceaccount/util/BearerTokenTests.java +++ b/common/src/test/java/com/skyflow/serviceaccount/util/BearerTokenTests.java @@ -3,7 +3,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.generated.auth.rest.core.ApiClientException; import com.skyflow.generated.auth.rest.types.V1GetAuthTokenResponse; @@ -102,7 +102,7 @@ public void testEmptyCredentialsFilePath() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); Assert.assertEquals( - BaseUtils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), ""), e.getMessage() + BaseUtils.parameterizedString(BaseErrorMessage.FileNotFound.getMessage(), ""), e.getMessage() ); } } @@ -117,7 +117,7 @@ public void testInvalidFilePath() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); Assert.assertEquals( - BaseUtils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), invalidFilePath), + BaseUtils.parameterizedString(BaseErrorMessage.FileNotFound.getMessage(), invalidFilePath), e.getMessage() ); } @@ -133,7 +133,7 @@ public void testInvalidCredentialsFile() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); Assert.assertEquals( - BaseUtils.parameterizedString(ErrorMessage.FileInvalidJson.getMessage(), invalidJsonFilePath), + BaseUtils.parameterizedString(BaseErrorMessage.FileInvalidJson.getMessage(), invalidJsonFilePath), e.getMessage() ); } @@ -147,7 +147,7 @@ public void testEmptyCredentialsString() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.InvalidCredentials.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.InvalidCredentials.getMessage(), e.getMessage()); } } @@ -159,7 +159,7 @@ public void testInvalidCredentialsString() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); } } @@ -174,7 +174,7 @@ public void testNoPrivateKeyInCredentialsForCredentials() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.MissingPrivateKey.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.MissingPrivateKey.getMessage(), e.getMessage()); } } @@ -188,7 +188,7 @@ public void testNoClientIDInCredentialsForCredentials() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.MissingClientId.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.MissingClientId.getMessage(), e.getMessage()); } } @@ -202,7 +202,7 @@ public void testNoKeyIDInCredentialsForCredentials() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.MissingKeyId.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.MissingKeyId.getMessage(), e.getMessage()); } } @@ -216,7 +216,7 @@ public void testNoTokenURIInCredentialsForCredentials() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.MissingTokenUri.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.MissingTokenUri.getMessage(), e.getMessage()); } } @@ -230,7 +230,7 @@ public void testInvalidPrivateKeyInCredentialsForCredentials() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.JwtInvalidFormat.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.JwtInvalidFormat.getMessage(), e.getMessage()); } } @@ -243,7 +243,7 @@ public void testInvalidKeySpecInCredentialsForCredentials() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.InvalidKeySpec.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.InvalidKeySpec.getMessage(), e.getMessage()); } } @@ -257,7 +257,7 @@ public void testInvalidTokenURIInCredentialsForCredentials() throws SkyflowExcep Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.InvalidTokenUri.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.InvalidTokenUri.getMessage(), e.getMessage()); } } @@ -274,7 +274,7 @@ public void testBearerTokenWithNewFormCredentialKeys() { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); // InvalidKeySpec confirms all credential fields were resolved — failure is at RSA parsing, not field lookup Assert.assertEquals( - BaseUtils.parameterizedString(ErrorMessage.InvalidKeySpec.getMessage(), BaseConstants.SDK_PREFIX), + BaseUtils.parameterizedString(BaseErrorMessage.InvalidKeySpec.getMessage(), BaseConstants.SDK_PREFIX), e.getMessage()); } } @@ -371,7 +371,7 @@ public void testExtractAccessTokenMissingThrowsSkyflowException() throws Excepti Assert.assertTrue(e.getCause() instanceof SkyflowException); SkyflowException skyflowException = (SkyflowException) e.getCause(); Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), skyflowException.getHttpCode()); - Assert.assertEquals(ErrorMessage.MissingAccessToken.getMessage(), skyflowException.getMessage()); + Assert.assertEquals(BaseErrorMessage.MissingAccessToken.getMessage(), skyflowException.getMessage()); } } diff --git a/common/src/test/java/com/skyflow/serviceaccount/util/SignedDataTokensTests.java b/common/src/test/java/com/skyflow/serviceaccount/util/SignedDataTokensTests.java index dab89e53..790de436 100644 --- a/common/src/test/java/com/skyflow/serviceaccount/util/SignedDataTokensTests.java +++ b/common/src/test/java/com/skyflow/serviceaccount/util/SignedDataTokensTests.java @@ -4,7 +4,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.utils.BaseConstants; import com.skyflow.utils.BaseUtils; @@ -94,7 +94,7 @@ public void testEmptyCredentialsFilePath() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(BaseUtils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), ""), e.getMessage()); + Assert.assertEquals(BaseUtils.parameterizedString(BaseErrorMessage.FileNotFound.getMessage(), ""), e.getMessage()); } } @@ -108,7 +108,7 @@ public void testInvalidFilePath() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); Assert.assertEquals( - BaseUtils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), invalidFilePath), + BaseUtils.parameterizedString(BaseErrorMessage.FileNotFound.getMessage(), invalidFilePath), e.getMessage()); } } @@ -123,7 +123,7 @@ public void testInvalidCredentialsFile() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); Assert.assertEquals( - BaseUtils.parameterizedString(ErrorMessage.FileInvalidJson.getMessage(), invalidJsonFilePath), + BaseUtils.parameterizedString(BaseErrorMessage.FileInvalidJson.getMessage(), invalidJsonFilePath), e.getMessage() ); } @@ -138,7 +138,7 @@ public void testEmptyCredentialsString() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); // InvalidCredentials has no %s1 placeholder, so no extra arg is passed here. - Assert.assertEquals(ErrorMessage.InvalidCredentials.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.InvalidCredentials.getMessage(), e.getMessage()); } catch (Exception e) { Assert.fail(INVALID_EXCEPTION_THROWN + ": " + e); } @@ -153,7 +153,7 @@ public void testInvalidCredentialsString() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); // CredentialsStringInvalidJson has no %s1 placeholder, so no extra arg is passed here. - Assert.assertEquals(ErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); } } @@ -167,7 +167,7 @@ public void testNoPrivateKeyInCredentials() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.MissingPrivateKey.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.MissingPrivateKey.getMessage(), e.getMessage()); } } @@ -181,7 +181,7 @@ public void testNoClientIDInCredentials() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.MissingClientId.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.MissingClientId.getMessage(), e.getMessage()); } } @@ -195,7 +195,7 @@ public void testNoKeyIDInCredentials() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.MissingKeyId.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.MissingKeyId.getMessage(), e.getMessage()); } } @@ -209,7 +209,7 @@ public void testInvalidPrivateKeyInCredentials() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.JwtInvalidFormat.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.JwtInvalidFormat.getMessage(), e.getMessage()); } } @@ -225,7 +225,7 @@ public void testInvalidKeySpecInCredentials() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.InvalidKeySpec.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.InvalidKeySpec.getMessage(), e.getMessage()); } } @@ -245,7 +245,7 @@ public void testSignedDataTokensWithNewFormCredentialKeys() { } catch (SkyflowException e) { // Failure at RSA key parsing (not field lookup) confirms new-form keys clientId/keyId were found Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.InvalidKeySpec.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.InvalidKeySpec.getMessage(), e.getMessage()); } } diff --git a/common/src/test/java/com/skyflow/utils/BaseUtilsTests.java b/common/src/test/java/com/skyflow/utils/BaseUtilsTests.java index bc1bd216..257e9d9b 100644 --- a/common/src/test/java/com/skyflow/utils/BaseUtilsTests.java +++ b/common/src/test/java/com/skyflow/utils/BaseUtilsTests.java @@ -4,7 +4,7 @@ import com.skyflow.config.Credentials; import com.skyflow.enums.Env; import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.SkyflowException; import org.junit.Assert; import org.junit.BeforeClass; @@ -105,7 +105,7 @@ public void testGenerateBearerTokenWithCredentialsFile() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); Assert.assertEquals( - BaseUtils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), filePath), + BaseUtils.parameterizedString(BaseErrorMessage.FileNotFound.getMessage(), filePath), e.getMessage() ); } @@ -122,7 +122,7 @@ public void testGenerateBearerTokenWithCredentialsString() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); } } @@ -166,7 +166,7 @@ public void testGenerateBearerTokenWithCredentialsFileAndMapContext() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); Assert.assertEquals( - BaseUtils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), filePath), + BaseUtils.parameterizedString(BaseErrorMessage.FileNotFound.getMessage(), filePath), e.getMessage() ); } @@ -187,7 +187,7 @@ public void testGenerateBearerTokenWithCredentialsStringAndMapContext() { Assert.fail("Map context should not cause a ClassCastException"); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); } } @@ -205,7 +205,7 @@ public void testGenerateBearerTokenWithContextNeitherStringNorMap() { Assert.fail("Non String/Map context should not cause a ClassCastException"); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); } } @@ -216,7 +216,7 @@ public void testGetPrivateKeyFromPemWithMissingHeader() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.JwtInvalidFormat.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.JwtInvalidFormat.getMessage(), e.getMessage()); } } @@ -230,7 +230,7 @@ public void testGetPrivateKeyFromPemWithInvalidBase64Body() { Assert.fail("Invalid base64 content should be wrapped into a SkyflowException, not thrown raw"); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.InvalidKeySpec.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.InvalidKeySpec.getMessage(), e.getMessage()); } } diff --git a/common/src/test/java/com/skyflow/vault/controller/BaseDetectControllerTests.java b/common/src/test/java/com/skyflow/vault/controller/BaseDetectControllerTests.java new file mode 100644 index 00000000..4b267bb3 --- /dev/null +++ b/common/src/test/java/com/skyflow/vault/controller/BaseDetectControllerTests.java @@ -0,0 +1,385 @@ +package com.skyflow.vault.controller; + +import com.skyflow.config.BaseVaultConfig; +import com.skyflow.detect.DeidentifyStringRequest; +import com.skyflow.detect.DeidentifyStringResponse; +import com.skyflow.detect.Detect; +import com.skyflow.detect.DetectConfiguration; +import com.skyflow.detect.Entity; +import com.skyflow.detect.CheckGuardrailsRequest; +import com.skyflow.detect.CheckGuardrailsResponse; +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.EntityType; +import com.skyflow.detect.FileDataFormat; +import com.skyflow.detect.GetRunRequest; +import com.skyflow.detect.GetRunResponse; +import com.skyflow.detect.GuardrailsValidation; +import com.skyflow.detect.RedactionLevel; +import com.skyflow.detect.ReidentifyStringRequest; +import com.skyflow.detect.ReidentifyStringResponse; +import com.skyflow.enums.Env; +import com.skyflow.errors.BaseErrorMessage; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.SkyflowException; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; + +public class BaseDetectControllerTests { + + /** Minimal SDK controller: fixed URL, counted tokens, no credential chain. */ + private static final class TestDetectController extends BaseDetectController { + private String vaultUrl; + private final AtomicInteger tokenCalls = new AtomicInteger(); + private SkyflowException tokenFailure; + + TestDetectController(BaseVaultConfig config, String vaultUrl) { + super(config, null); + this.vaultUrl = vaultUrl; + } + + @Override + protected String resolveVaultUrl() { + return vaultUrl; + } + + @Override + protected String getSdkMetrics() { + return "{\"sdk_name_version\":\"test\"}"; + } + + @Override + protected String resolveToken() throws SkyflowException { + if (tokenFailure != null) { + throw tokenFailure; + } + return "token-" + tokenCalls.incrementAndGet(); + } + } + + private MockWebServer server; + private TestDetectController controller; + + private static BaseVaultConfig config(String vaultId) { + BaseVaultConfig config = new BaseVaultConfig(); + config.setVaultId(vaultId); + config.setClusterId("cluster"); + config.setEnv(Env.PROD); + return config; + } + + @Before + public void setUp() throws Exception { + server = new MockWebServer(); + server.start(); + controller = new TestDetectController(config("vault-1"), server.url("").toString()); + controller.setCommonHttpConfig(null, 0, null, null); + } + + @After + public void tearDown() throws Exception { + server.shutdown(); + } + + @Test + public void deidentifyStringSendsScopedRequestAndMapsResponse() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setHeader("Content-Type", "application/json") + .setBody("{\"processedText\":\"My name is [NAME_1].\",\"entities\":[{\"token\":\"NAME_1\",\"entityType\":\"NAME\"," + + "\"location\":{\"startIndex\":11,\"endIndex\":19}}],\"metrics\":{\"wordCount\":4}}")); + + DeidentifyStringResponse response = controller.deidentifyString(DeidentifyStringRequest.builder() + .text("My name is John Doe.") + .configuration(DetectConfiguration.builder() + .detect(Detect.builder().entities(Collections.singletonList( + Entity.builder().entityType(EntityType.NAME).build())).build()) + .build()) + .build()); + + Assert.assertEquals("My name is [NAME_1].", response.getProcessedText()); + Assert.assertEquals("NAME_1", response.getEntities().get(0).getToken()); + Assert.assertEquals(Integer.valueOf(4), response.getMetrics().getWordCount()); + + RecordedRequest recorded = server.takeRequest(); + Assert.assertEquals("POST", recorded.getMethod()); + Assert.assertEquals("/v2/detect/deidentify/string", recorded.getPath()); + Assert.assertEquals("Bearer token-1", recorded.getHeader("Authorization")); + Assert.assertEquals("{\"sdk_name_version\":\"test\"}", recorded.getHeader("sky-metadata")); + String body = recorded.getBody().readUtf8(); + Assert.assertTrue(body, body.contains("\"text\":\"My name is John Doe.\"")); + Assert.assertTrue(body, body.contains("\"vaultId\":\"vault-1\"")); + Assert.assertTrue(body, body.contains("\"entityType\":\"NAME\"")); + Assert.assertFalse(body, body.contains("configurationId")); + } + + @Test + public void reidentifyStringSendsVaultIdInBodyAndRefreshesTokenPerCall() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"processedText\":\"My name is John Doe.\"}")); + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"processedText\":\"My name is J**n D*e.\"}")); + + ReidentifyStringResponse first = controller.reidentifyString( + ReidentifyStringRequest.builder().text("My name is [NAME_1].").build()); + ReidentifyStringResponse second = controller.reidentifyString(ReidentifyStringRequest.builder() + .text("My name is [NAME_1].") + .redactionLevel(Collections.singletonList( + RedactionLevel.builder().tokenGroupName("group").redactionPattern("mask").build())) + .build()); + + Assert.assertEquals("My name is John Doe.", first.getProcessedText()); + Assert.assertEquals("My name is J**n D*e.", second.getProcessedText()); + + RecordedRequest r1 = server.takeRequest(); + Assert.assertEquals("/v2/detect/reidentify/string", r1.getPath()); + Assert.assertEquals("Bearer token-1", r1.getHeader("Authorization")); + String body1 = r1.getBody().readUtf8(); + Assert.assertTrue(body1, body1.contains("\"vaultId\":\"vault-1\"")); + Assert.assertFalse(body1, body1.contains("redactionLevel")); + + RecordedRequest r2 = server.takeRequest(); + Assert.assertEquals("Bearer token-2", r2.getHeader("Authorization")); + String body2 = r2.getBody().readUtf8(); + Assert.assertTrue(body2, body2.contains("\"tokenGroupName\":\"group\"")); + Assert.assertTrue(body2, body2.contains("\"redactionPattern\":\"mask\"")); + } + + @Test + public void getRunQueriesRunByIdScopedToVault() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody( + "{\"status\":\"SUCCESS\",\"outputType\":\"BASE64\",\"output\":[{\"processedFile\":\"QUJD\"," + + "\"processedFileType\":\"REDACTED_FILE\",\"processedFileExtension\":\"pdf\"}," + + "{\"processedFile\":\"e30=\",\"processedFileType\":\"ENTITIES\",\"processedFileExtension\":\"json\"}]," + + "\"metrics\":{\"pages\":3,\"size\":12.5}}")); + + GetRunResponse response = controller.getRun(GetRunRequest.builder().runId(" run-42 ").build()); + + Assert.assertEquals("run-42", response.getRunId()); + Assert.assertEquals(DetectRunStatus.SUCCESS, response.getStatus()); + Assert.assertEquals(DataSourceType.BASE64, response.getOutputType()); + Assert.assertEquals(2, response.getOutput().size()); + Assert.assertEquals("QUJD", response.getOutput().get(0).getProcessedFile()); + Assert.assertEquals("REDACTED_FILE", response.getOutput().get(0).getProcessedFileType()); + Assert.assertEquals(FileDataFormat.PDF, response.getOutput().get(0).getProcessedFileExtension()); + Assert.assertEquals(FileDataFormat.JSON, response.getOutput().get(1).getProcessedFileExtension()); + Assert.assertEquals(Integer.valueOf(3), response.getMetrics().getPages()); + Assert.assertNull(response.getMessage()); + + RecordedRequest recorded = server.takeRequest(); + Assert.assertEquals("GET", recorded.getMethod()); + Assert.assertEquals("/v2/detect/runs/run-42?vaultId=vault-1", recorded.getPath()); + Assert.assertEquals("Bearer token-1", recorded.getHeader("Authorization")); + Assert.assertEquals("{\"sdk_name_version\":\"test\"}", recorded.getHeader("sky-metadata")); + } + + @Test + public void getRunReportsPendingAndFailedRuns() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"status\":\"IN_PROGRESS\"}")); + server.enqueue(new MockResponse().setResponseCode(200).setBody( + "{\"status\":\"FAILED\",\"message\":\"unsupported file\",\"output\":[]}")); + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"status\":\"SOMETHING_NEW\"}")); + + GetRunResponse pending = controller.getRun(GetRunRequest.builder().runId("r1").build()); + Assert.assertEquals(DetectRunStatus.IN_PROGRESS, pending.getStatus()); + Assert.assertNotNull(pending.getOutput()); + Assert.assertTrue(pending.getOutput().isEmpty()); + Assert.assertNull(pending.getOutputType()); + Assert.assertNull(pending.getMetrics()); + + GetRunResponse failed = controller.getRun(GetRunRequest.builder().runId("r2").build()); + Assert.assertEquals(DetectRunStatus.FAILED, failed.getStatus()); + Assert.assertEquals("unsupported file", failed.getMessage()); + + GetRunResponse unknown = controller.getRun(GetRunRequest.builder().runId("r3").build()); + Assert.assertEquals("unrecognised statuses degrade to UNKNOWN", DetectRunStatus.UNKNOWN, unknown.getStatus()); + } + + @Test + public void getRunValidatesRunIdBeforeTokenOrNetwork() throws Exception { + for (GetRunRequest bad : new GetRunRequest[]{null, GetRunRequest.builder().build(), GetRunRequest.builder().runId(" ").build()}) { + try { + controller.getRun(bad); + Assert.fail("expected SkyflowException for " + bad); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + } + } + Assert.assertEquals(0, controller.tokenCalls.get()); + Assert.assertEquals(0, server.getRequestCount()); + } + + @Test + public void getRunMapsNotFound() throws Exception { + server.enqueue(new MockResponse().setResponseCode(404) + .setBody("{\"error\":{\"grpc_code\":5,\"http_code\":404,\"message\":\"run not found\",\"http_status\":\"Not Found\",\"details\":[]}}")); + try { + controller.getRun(GetRunRequest.builder().runId("missing").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(404, e.getHttpCode()); + Assert.assertEquals("run not found", e.getMessage()); + } + } + + @Test + public void checkGuardrailsSendsScopedRequestAndMapsResponse() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody( + "{\"text\":\"tell me about politics\",\"validation\":\"FAILED\",\"toxic\":false,\"deniedTopic\":true}")); + + CheckGuardrailsResponse response = controller.checkGuardrails(CheckGuardrailsRequest.builder() + .text("tell me about politics") + .checkToxicity(true) + .denyTopics(Arrays.asList("politics", "medical advice")) + .build()); + + Assert.assertEquals("tell me about politics", response.getText()); + Assert.assertEquals(GuardrailsValidation.FAILED, response.getValidation()); + Assert.assertEquals(Boolean.FALSE, response.getToxic()); + Assert.assertEquals(Boolean.TRUE, response.getDeniedTopic()); + + RecordedRequest recorded = server.takeRequest(); + Assert.assertEquals("POST", recorded.getMethod()); + Assert.assertEquals("/v2/detect/guardrails", recorded.getPath()); + Assert.assertEquals("Bearer token-1", recorded.getHeader("Authorization")); + Assert.assertEquals("{\"sdk_name_version\":\"test\"}", recorded.getHeader("sky-metadata")); + String body = recorded.getBody().readUtf8(); + Assert.assertTrue(body, body.contains("\"text\":\"tell me about politics\"")); + Assert.assertTrue(body, body.contains("\"vaultId\":\"vault-1\"")); + Assert.assertTrue(body, body.contains("\"checkToxicity\":true")); + Assert.assertTrue(body, body.contains("\"denyTopics\":[\"politics\",\"medical advice\"]")); + } + + @Test + public void checkGuardrailsOmitsUnsetOptionsAndMapsPassed() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"text\":\"hello\",\"validation\":\"PASSED\"}")); + CheckGuardrailsResponse response = controller.checkGuardrails(CheckGuardrailsRequest.builder().text("hello").build()); + Assert.assertEquals(GuardrailsValidation.PASSED, response.getValidation()); + Assert.assertNull(response.getToxic()); + Assert.assertNull(response.getDeniedTopic()); + String body = server.takeRequest().getBody().readUtf8(); + Assert.assertFalse(body, body.contains("checkToxicity")); + Assert.assertFalse(body, body.contains("denyTopics")); + } + + @Test + public void checkGuardrailsValidatesBeforeTokenOrNetwork() throws Exception { + CheckGuardrailsRequest[] bad = { + null, + CheckGuardrailsRequest.builder().text(" ").build(), + }; + for (CheckGuardrailsRequest request : bad) { + try { + controller.checkGuardrails(request); + Assert.fail("expected SkyflowException for " + request); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + } + } + Assert.assertEquals(0, controller.tokenCalls.get()); + Assert.assertEquals(0, server.getRequestCount()); + } + + @Test + public void deidentifyStringWithStoredConfigurationSendsOnlyTextAndId() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"processedText\":\"[NAME_1]\"}")); + controller.deidentifyString(DeidentifyStringRequest.builder().text("John").configurationId("cfg-1").build()); + String body = server.takeRequest().getBody().readUtf8(); + Assert.assertEquals("{\"text\":\"John\",\"configurationId\":\"cfg-1\"}", body); + } + + @Test + public void apiErrorIsMappedToSkyflowExceptionWithServerMessage() throws Exception { + server.enqueue(new MockResponse().setResponseCode(400).setHeader("x-request-id", "req-123") + .setBody("{\"error\":{\"grpc_code\":3,\"http_code\":400,\"message\":\"configuration not found\"," + + "\"http_status\":\"Bad Request\",\"details\":[]}}")); + try { + controller.deidentifyString(DeidentifyStringRequest.builder().text("hello").configurationId("missing").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(400, e.getHttpCode()); + Assert.assertEquals("configuration not found", e.getMessage()); + Assert.assertEquals("req-123", e.getRequestId()); + Assert.assertEquals(Integer.valueOf(3), e.getGrpcCode()); + } + } + + @Test + public void unmodelledStatusIsStillMapped() throws Exception { + server.enqueue(new MockResponse().setResponseCode(404) + .setBody("{\"error\":{\"grpc_code\":5,\"http_code\":404,\"message\":\"not found\",\"http_status\":\"Not Found\",\"details\":[]}}")); + try { + controller.reidentifyString(ReidentifyStringRequest.builder().text("[NAME_1]").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(404, e.getHttpCode()); + Assert.assertEquals("not found", e.getMessage()); + } + } + + @Test + public void validationFailsBeforeTokenOrNetwork() throws Exception { + try { + controller.deidentifyString(DeidentifyStringRequest.builder().text(" ").configurationId("cfg").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(BaseErrorMessage.InvalidTextInDeidentifyString.getMessage(), e.getMessage()); + } + Assert.assertEquals(0, controller.tokenCalls.get()); + Assert.assertEquals(0, server.getRequestCount()); + } + + @Test + public void tokenFailurePropagatesWithoutNetworkCall() throws Exception { + controller.tokenFailure = new SkyflowException(401, "no credentials"); + try { + controller.reidentifyString(ReidentifyStringRequest.builder().text("[NAME_1]").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals("no credentials", e.getMessage()); + } + Assert.assertEquals(0, server.getRequestCount()); + } + + @Test + public void clientIsRebuiltWhenUrlOrHttpConfigChanges() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); + controller.reidentifyString(ReidentifyStringRequest.builder().text("[NAME_1]").build()); + Object first = controller.stringsClient(); + Assert.assertSame("same settings must reuse the client", first, controller.stringsClient()); + + controller.setCommonHttpConfig(30, 0, null, null); + Object afterConfig = controller.stringsClient(); + Assert.assertNotSame("changed http config must rebuild", first, afterConfig); + + MockWebServer other = new MockWebServer(); + other.start(); + try { + controller.vaultUrl = other.url("").toString(); + other.enqueue(new MockResponse().setResponseCode(200).setBody("{\"processedText\":\"moved\"}")); + Assert.assertEquals("moved", controller.reidentifyString( + ReidentifyStringRequest.builder().text("[NAME_1]").build()).getProcessedText()); + Assert.assertNotSame("changed url must rebuild", afterConfig, controller.stringsClient()); + Assert.assertEquals(1, other.getRequestCount()); + } finally { + other.shutdown(); + } + } + + @Test + public void setVaultConfigRescopesRequests() throws Exception { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); + controller.reidentifyString(ReidentifyStringRequest.builder().text("[NAME_1]").build()); + controller.setVaultConfig(config("vault-2")); + controller.reidentifyString(ReidentifyStringRequest.builder().text("[NAME_1]").build()); + Assert.assertTrue(server.takeRequest().getBody().readUtf8().contains("\"vaultId\":\"vault-1\"")); + Assert.assertTrue(server.takeRequest().getBody().readUtf8().contains("\"vaultId\":\"vault-2\"")); + } +} diff --git a/flowvault/README.md b/flowvault/README.md index c6d78f26..a668231c 100644 --- a/flowvault/README.md +++ b/flowvault/README.md @@ -49,6 +49,11 @@ The `flowvault` module is a Skyflow Java SDK built for high-throughput vault ope - [Get](#get) - [Update](#update) - [Delete](#delete) +- [Detect](#detect) + - [Deidentify string](#deidentify-string) + - [Reidentify string](#reidentify-string) + - [Get run](#get-run) + - [Check guardrails](#check-guardrails) - [Custom Request Headers](#custom-request-headers) - [Error Handling](#error-handling) - [Two layers of errors](#two-layers-of-errors) @@ -1624,6 +1629,212 @@ for (DeleteResponseRecord record : deleteResponse.getRecords()) { } ``` +# Detect + +Skyflow Detect finds and de-identifies sensitive data in free text, then re-identifies it on demand. The flowvault SDK exposes the Detect V2 API through `DetectController`, returned by `skyflowClient.detect()` and `skyflowClient.detect(vaultId)`. Every request is scoped to that vault, and the controller honours the same credentials, [timeouts and retries](#timeouts-and-retries) as `vault()`. + +Detect configurations are created in Studio or through the management API. The SDK does not create, update or delete them. + +| Method | Request | Response | Description | +|--------|---------|----------|-------------| +| `deidentifyString(DeidentifyStringRequest)` | `com.skyflow.detect.DeidentifyStringRequest` | `com.skyflow.detect.DeidentifyStringResponse` | Detect and replace sensitive data in a string | +| `deidentifyFile(DeidentifyFileRequest)` | `com.skyflow.detect.DeidentifyFileRequest` | `com.skyflow.detect.DeidentifyFileResponse` | Submit a file for de-identification, optionally polling the run to completion | +| `reidentifyString(ReidentifyStringRequest)` | `com.skyflow.detect.ReidentifyStringRequest` | `com.skyflow.detect.ReidentifyStringResponse` | Replace tokens in a string with their original or redacted values | +| `reidentifyFile(ReidentifyFileRequest)` | `com.skyflow.detect.ReidentifyFileRequest` | `com.skyflow.detect.ReidentifyFileResponse` | Replace tokens in a file with their original or redacted values | +| `getRun(GetRunRequest)` | `com.skyflow.detect.GetRunRequest` | `com.skyflow.detect.GetRunResponse` | Retrieve the status and outputs of a Detect file run | +| `checkGuardrails(CheckGuardrailsRequest)` | `com.skyflow.detect.CheckGuardrailsRequest` | `com.skyflow.detect.CheckGuardrailsResponse` | Screen text for toxicity and denied topics | + +> Detect V2 is in beta. + +## Deidentify string + +`DeidentifyStringRequest` takes the text plus, optionally, `configurationId` (a stored Detect configuration) and/or `configuration` (an inline `DetectConfiguration`). Both are optional on the API and are sent exactly as supplied. The SDK fills the vault id from the controller. + +```java +import com.skyflow.detect.*; + +// Stored configuration +DeidentifyStringResponse response = skyflowClient.detect().deidentifyString( + DeidentifyStringRequest.builder() + .text("My name is John Doe, and my email is johndoe@acme.com.") + .configurationId("") + .build()); + +System.out.println(response.getProcessedText()); +// My name is [NAME_1], and my email is [EMAIL_ADDRESS_1]. + +// Inline configuration +DetectConfiguration configuration = DetectConfiguration.builder() + .detect(Detect.builder() + .entities(Arrays.asList( + Entity.builder() + .entityType(EntityType.NAME) + .deidentificationType(DeidentificationType.VAULT_TOKEN) + .destination("") // token group that stores the value + .build(), + Entity.builder() + .entityType(EntityType.ALL) + .deidentificationType(DeidentificationType.ENTITY_UNIQUE_COUNTER) + .build())) + .skip(Collections.singletonList("Skyflow")) + .returnEntities(ReturnEntitiesType.EXCLUDE_SENSITIVE_DATA) + .build()) + .build(); + +response = skyflowClient.detect().deidentifyString( + DeidentifyStringRequest.builder() + .text("My name is John Doe, and my email is johndoe@acme.com.") + .configuration(configuration) + .build()); + +for (DetectedEntity entity : response.getEntities()) { + System.out.println(entity.getEntityType() + " " + entity.getToken() + + " [" + entity.getLocation().getStartIndex() + "," + entity.getLocation().getEndIndex() + "]"); +} +System.out.println(response.getMetrics().getWordCount()); +``` + +`DeidentifyStringResponse` carries `getProcessedText()`, `getEntities()` (a `List` with token, entity type, location and confidence scores; never `null`) and `getMetrics()`. + +## Deidentify file + +File de-identification is asynchronous on the API: the submit call returns a run id and the result is retrieved from the run. `deidentifyFile` exposes both modes. + +- **Submit only** (default): one call, returns `getRunId()`. `getStatus()` is `null` and nothing is written to disk. Retrieve the result later with [`getRun`](#get-run). +- **Submit and poll**: set `pollOptions`. The SDK polls the run with exponential backoff (1, 2, 4, 8, 16, then 32 seconds between lookups) until it is `SUCCESS`, `FAILED` or `UNKNOWN`, or until `waitTime` (default 60 s, max 300) or `maxAttempts` (default 23, max 23) is used up. If the budget runs out the response carries the run id with `IN_PROGRESS` or `QUEUED` so you can resume with `getRun`. + +The file is supplied in one of three ways: `dataSource` plus `value` (`BASE64` content, a `SKYFLOW_ID`, or a `PRESIGNED_URL`), or `file(File)` / `filePath(String)`, which read and encode a local file and infer `dataFormat` from its extension. Supply exactly one of `value`, `file` or `filePath`. Either `configurationId` or an inline `configuration` is passed through as given. + +```java +// Submit only +DeidentifyFileResponse submitted = skyflowClient.detect().deidentifyFile( + DeidentifyFileRequest.builder() + .filePath("/data/intake/patient-notes.pdf") // BASE64 + pdf inferred + .configurationId("") + .build()); +String runId = submitted.getRunId(); + +// Submit and poll +DeidentifyFileResponse response = skyflowClient.detect().deidentifyFile( + DeidentifyFileRequest.builder() + .filePath("/data/intake/patient-notes.pdf") + .configurationId("") + .pollOptions(PollOptions.builder().waitTime(120).build()) + .outputDirectory("/data/processed") // optional + .build()); + +switch (response.getStatus()) { + case SUCCESS: + // For file-based requests the outputs are already on disk: + // /data/processed/processed-patient-notes.pdf the redacted file + // /data/processed/processed-entities-patient-notes.json the detected entities + // response.getOutput() and getMetrics() carry the same data. + break; + case FAILED: + System.err.println(response.getMessage()); + break; + default: // IN_PROGRESS or QUEUED: budget ran out, resume with getRun(runId) + break; +} +``` + +Outputs are written only when polling reached `SUCCESS`, the request was built from a local file, and the run delivered them as base64. A request built from a `SKYFLOW_ID` or `PRESIGNED_URL` has no local name to derive from, and presigned-URL outputs are not fetched, so in those cases read `getOutput()` and write the files yourself. Files are named `processed-.`, with the entities output as `processed-entities-.json` (and `processed-object-entities-.json` for object entities); `outputDirectory` is created if missing and defaults to the working directory. Polling blocks the calling thread. + +## Reidentify string + +`ReidentifyStringRequest` takes the tokenised text and an optional list of `RedactionLevel` rules. For flowvault vaults each rule targets a `tokenGroupName` and applies a named `redactionPattern`. Without rules every token is replaced by its original value. + +```java +ReidentifyStringResponse response = skyflowClient.detect().reidentifyString( + ReidentifyStringRequest.builder() + .text("My name is [NAME_1].") + .redactionLevel(Collections.singletonList( + RedactionLevel.builder() + .tokenGroupName("") + .redactionPattern("") + .build())) + .build()); + +System.out.println(response.getProcessedText()); +// My name is J**n D*e. +``` + +A `RedactionLevel` is expected to name one source (`tokenGroupName` or `entityName`) and one replacement (`redactionPattern` or `redactionType`). Every field is optional on the API, so the SDK passes the list through as given and the server enforces the combination rules, including which fields apply to flowvault vaults. + +## Reidentify file + +`reidentifyFile` is synchronous: the re-identified file comes back in the response, not through a run id. `ReidentifyFileRequest` takes a `dataSource` (`BASE64`, `SKYFLOW_ID` or `PRESIGNED_URL`), the matching `value`, an optional `dataFormat` and the same optional `RedactionLevel` rules as `reidentifyString`. Re-identification supports text-based formats such as TXT, CSV, JSON, JSONL, XML, DOCX, XLSX and PPTX. + +```java +byte[] content = Files.readAllBytes(Paths.get("tokenised.txt")); + +ReidentifyFileResponse response = skyflowClient.detect().reidentifyFile( + ReidentifyFileRequest.builder() + .dataSource(DataSourceType.BASE64) + .value(Base64.getEncoder().encodeToString(content)) + .dataFormat(FileDataFormat.TXT) + .redactionLevel(Collections.singletonList( + RedactionLevel.builder() + .tokenGroupName("") + .redactionPattern("") + .build())) + .build()); + +if (response.getStatus() == DetectRunStatus.SUCCESS) { + for (FileOutput output : response.getOutput()) { + // output.getProcessedFile() is base64 or a presigned URL, per response.getOutputType() + byte[] processed = Base64.getDecoder().decode(output.getProcessedFile()); + } +} +``` + +`ReidentifyFileResponse` carries `getStatus()`, `getOutputType()`, `getOutput()` (never `null`) and `getMetrics()`. This method is specific to the flowvault SDK. + +## Get run + +File de-identification is asynchronous: the submit call returns a run id, and `getRun` retrieves the run's status and, once it has succeeded, its outputs. `GetRunResponse.getOutput()` is empty and `getOutputType()` and `getMetrics()` are `null` until the run completes. + +```java +GetRunResponse run = skyflowClient.detect().getRun( + GetRunRequest.builder().runId("").build()); + +switch (run.getStatus()) { + case SUCCESS: + for (FileOutput output : run.getOutput()) { + // output.getProcessedFile() is base64 or a presigned URL, per run.getOutputType() + // output.getProcessedFileType() is e.g. REDACTED_FILE or ENTITIES + // output.getProcessedFileExtension() is a FileDataFormat such as PDF + } + break; + case FAILED: + System.err.println(run.getMessage()); + break; + default: // QUEUED, IN_PROGRESS, UNKNOWN + // poll again later +} +``` + +## Check guardrails + +`checkGuardrails` screens text for toxicity and for a list of denied topics, which makes it a natural pre-LLM check. Each check runs only when requested: `getToxic()` is `null` unless `checkToxicity(true)` was set, and `getDeniedTopic()` is `null` unless `denyTopics` was supplied. `getValidation()` is `FAILED` if any requested check triggered. + +```java +CheckGuardrailsResponse response = skyflowClient.detect().checkGuardrails( + CheckGuardrailsRequest.builder() + .text(userPrompt) + .checkToxicity(true) + .denyTopics(Arrays.asList("politics", "medical advice")) + .build()); + +if (response.getValidation() == GuardrailsValidation.FAILED) { + throw new IllegalArgumentException("blocked: toxic=" + response.getToxic() + " deniedTopic=" + response.getDeniedTopic()); +} +``` + +All Detect methods throw `SkyflowException` as described in [Error Handling](#error-handling). Client-side validation fails before any network call. + +See the samples [DeidentifyStringSample.java](samples/src/main/java/com/example/detect/DeidentifyStringSample.java), [ReidentifyStringSample.java](samples/src/main/java/com/example/detect/ReidentifyStringSample.java) [GetRunSample.java](samples/src/main/java/com/example/detect/GetRunSample.java) and [CheckGuardrailsSample.java](samples/src/main/java/com/example/detect/CheckGuardrailsSample.java). + # Custom Request Headers To include custom HTTP headers on an outgoing request — bulk or unary — pass a `RequestInterceptor` via that operation's options object. The headers available are defined by the `CustomHeaderKey` enum: diff --git a/flowvault/api-report/skyflow-flowvault-java.baseline.jar b/flowvault/api-report/skyflow-flowvault-java.baseline.jar index 1defab788fc417ec802d64ef31280779b3bf4a5e..733abb00cced1a4d560ad72fbc1204990731cb76 100644 GIT binary patch delta 516312 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≫)5 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ºUK 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 y-?UV7>9|2aqsb89)1mwvC@!jN0JBj?D=zt;$1.0.0 compile + + com.squareup.okhttp3 + mockwebserver + ${okhttp-version} + test + diff --git a/flowvault/samples/src/main/java/com/example/detect/CheckGuardrailsSample.java b/flowvault/samples/src/main/java/com/example/detect/CheckGuardrailsSample.java new file mode 100644 index 00000000..a48efaf2 --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/detect/CheckGuardrailsSample.java @@ -0,0 +1,53 @@ +package com.example.detect; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.detect.CheckGuardrailsRequest; +import com.skyflow.detect.CheckGuardrailsResponse; +import com.skyflow.detect.GuardrailsValidation; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; + +import java.util.Arrays; + +/** + * Screens a prompt for toxicity and denied topics before it is sent to an LLM. + */ +public class CheckGuardrailsSample { + public static void main(String[] args) throws SkyflowException { + Credentials credentials = new Credentials(); + credentials.setPath(""); // Replace with the path to the credentials file + + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); // Replace with the ID of the vault + vaultConfig.setClusterId(""); // Replace with the cluster ID of the vault + vaultConfig.setEnv(Env.PROD); // Set the environment (e.g., DEV, STAGE, PROD) + + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .addSkyflowCredentials(credentials) + .build(); + + String userPrompt = "Which party should I vote for?"; + try { + CheckGuardrailsResponse response = skyflowClient.detect().checkGuardrails( + CheckGuardrailsRequest.builder() + .text(userPrompt) + .checkToxicity(true) + .denyTopics(Arrays.asList("politics", "medical advice")) + .build()); + + if (response.getValidation() == GuardrailsValidation.FAILED) { + System.out.println("Blocked: toxic=" + response.getToxic() + " deniedTopic=" + response.getDeniedTopic()); + } else { + System.out.println("Prompt passed guardrails."); + } + } catch (SkyflowException e) { + System.err.println("Error during checkGuardrails:"); + e.printStackTrace(); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/detect/DeidentifyFileSample.java b/flowvault/samples/src/main/java/com/example/detect/DeidentifyFileSample.java new file mode 100644 index 00000000..c373f181 --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/detect/DeidentifyFileSample.java @@ -0,0 +1,66 @@ +package com.example.detect; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.detect.DeidentifyFileRequest; +import com.skyflow.detect.DeidentifyFileResponse; +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.PollOptions; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; + +/** + * De-identifies a local file with the Detect V2 API. The first call submits the run and returns its + * id; the second submits and polls, and writes the outputs next to the input on success. + */ +public class DeidentifyFileSample { + public static void main(String[] args) throws SkyflowException { + Credentials credentials = new Credentials(); + credentials.setPath(""); // Replace with the path to the credentials file + + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); // Replace with the ID of the vault + vaultConfig.setClusterId(""); // Replace with the cluster ID of the vault + vaultConfig.setEnv(Env.PROD); // Set the environment (e.g., DEV, STAGE, PROD) + + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .addSkyflowCredentials(credentials) + .build(); + + try { + // Submit only: the API's own behaviour. Keep the run id and retrieve the result with getRun. + DeidentifyFileResponse submitted = skyflowClient.detect().deidentifyFile( + DeidentifyFileRequest.builder() + .filePath("") // BASE64 and the format are derived from the file + .configurationId("") // Replace with a Detect configuration id + .build()); + System.out.println("Submitted run " + submitted.getRunId()); + + // Submit and poll: waits up to 90 seconds with exponential backoff, then writes + // processed-. and processed-.json into the output directory. + DeidentifyFileResponse polled = skyflowClient.detect().deidentifyFile( + DeidentifyFileRequest.builder() + .filePath("") + .configurationId("") + .pollOptions(PollOptions.builder().waitTime(90).build()) + .outputDirectory("") // optional; defaults to the working directory + .build()); + + System.out.println("Run " + polled.getRunId() + " status: " + polled.getStatus()); + if (polled.getStatus() == DetectRunStatus.SUCCESS) { + System.out.println("Outputs: " + polled.getOutput().size() + ", metrics: " + polled.getMetrics()); + } else if (polled.getStatus() == DetectRunStatus.FAILED) { + System.err.println("Run failed: " + polled.getMessage()); + } else { + System.out.println("Still running; poll later with getRun(" + polled.getRunId() + ")"); + } + } catch (SkyflowException e) { + System.err.println("Error during deidentifyFile:"); + e.printStackTrace(); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/detect/DeidentifyStringSample.java b/flowvault/samples/src/main/java/com/example/detect/DeidentifyStringSample.java new file mode 100644 index 00000000..b38ba42e --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/detect/DeidentifyStringSample.java @@ -0,0 +1,94 @@ +package com.example.detect; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.detect.DeidentificationType; +import com.skyflow.detect.DeidentifyStringRequest; +import com.skyflow.detect.DeidentifyStringResponse; +import com.skyflow.detect.Detect; +import com.skyflow.detect.DetectConfiguration; +import com.skyflow.detect.DetectedEntity; +import com.skyflow.detect.Entity; +import com.skyflow.detect.EntityType; +import com.skyflow.detect.ReturnEntitiesType; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; + +import java.util.Arrays; +import java.util.Collections; + +/** + * De-identifies sensitive data in a string with the Detect V2 API, first with a stored + * configuration id and then with an inline configuration. + */ +public class DeidentifyStringSample { + public static void main(String[] args) throws SkyflowException { + Credentials credentials = new Credentials(); + credentials.setPath(""); // Replace with the path to the credentials file + + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); // Replace with the ID of the vault + vaultConfig.setClusterId(""); // Replace with the cluster ID of the vault + vaultConfig.setEnv(Env.PROD); // Set the environment (e.g., DEV, STAGE, PROD) + + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .addSkyflowCredentials(credentials) + .build(); + + String text = "My name is John Doe, and my email is johndoe@acme.com."; + + // 1. Stored configuration, created in Studio or through the management API. + try { + DeidentifyStringResponse response = skyflowClient.detect().deidentifyString( + DeidentifyStringRequest.builder() + .text(text) + .configurationId("") // Replace with a Detect configuration id + .build()); + System.out.println("Processed text: " + response.getProcessedText()); + } catch (SkyflowException e) { + System.err.println("Error during deidentifyString (stored configuration):"); + e.printStackTrace(); + } + + // 2. Inline configuration, nothing to create beforehand. + DetectConfiguration configuration = DetectConfiguration.builder() + .detect(Detect.builder() + .entities(Arrays.asList( + Entity.builder() + .entityType(EntityType.NAME) + .deidentificationType(DeidentificationType.VAULT_TOKEN) + .destination("") // flowvault: token group that stores the value + .build(), + Entity.builder() + .entityType(EntityType.ALL) + .deidentificationType(DeidentificationType.ENTITY_UNIQUE_COUNTER) + .build())) + .skip(Collections.singletonList("Skyflow")) + .returnEntities(ReturnEntitiesType.EXCLUDE_SENSITIVE_DATA) + .build()) + .build(); + + try { + DeidentifyStringResponse response = skyflowClient.detect().deidentifyString( + DeidentifyStringRequest.builder() + .text(text) + .configuration(configuration) + .build()); + System.out.println("Processed text: " + response.getProcessedText()); + for (DetectedEntity entity : response.getEntities()) { + System.out.printf("%-16s %-18s [%d,%d] %s%n", + entity.getEntityType(), entity.getToken(), + entity.getLocation().getStartIndex(), entity.getLocation().getEndIndex(), + entity.getEntityScores()); + } + System.out.println("Metrics: " + response.getMetrics()); + } catch (SkyflowException e) { + System.err.println("Error during deidentifyString (inline configuration):"); + e.printStackTrace(); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/detect/GetRunSample.java b/flowvault/samples/src/main/java/com/example/detect/GetRunSample.java new file mode 100644 index 00000000..cc3e7667 --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/detect/GetRunSample.java @@ -0,0 +1,62 @@ +package com.example.detect; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.FileOutput; +import com.skyflow.detect.GetRunRequest; +import com.skyflow.detect.GetRunResponse; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; + +import java.io.File; +import java.nio.file.Files; +import java.util.Base64; + +/** + * Retrieves a Detect file run by id and, once it has succeeded, writes the base64 outputs to disk. + */ +public class GetRunSample { + public static void main(String[] args) throws Exception { + Credentials credentials = new Credentials(); + credentials.setPath(""); // Replace with the path to the credentials file + + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); // Replace with the ID of the vault + vaultConfig.setClusterId(""); // Replace with the cluster ID of the vault + vaultConfig.setEnv(Env.PROD); // Set the environment (e.g., DEV, STAGE, PROD) + + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .addSkyflowCredentials(credentials) + .build(); + + try { + GetRunResponse run = skyflowClient.detect().getRun( + GetRunRequest.builder() + .runId("") // Replace with the run id returned by deidentifyFile + .build()); + + System.out.println("Run " + run.getRunId() + " status: " + run.getStatus()); + if (run.getStatus() == DetectRunStatus.SUCCESS) { + for (FileOutput output : run.getOutput()) { + String name = "processed-" + run.getRunId() + "-" + output.getProcessedFileType() + + "." + output.getProcessedFileExtension().name().toLowerCase(); + Files.write(new File(name).toPath(), Base64.getDecoder().decode(output.getProcessedFile())); + System.out.println("Wrote " + name); + } + System.out.println("Metrics: " + run.getMetrics()); + } else if (run.getStatus() == DetectRunStatus.FAILED) { + System.err.println("Run failed: " + run.getMessage()); + } else { + System.out.println("Run still pending, poll again later."); + } + } catch (SkyflowException e) { + System.err.println("Error during getRun:"); + e.printStackTrace(); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/detect/ReidentifyFileSample.java b/flowvault/samples/src/main/java/com/example/detect/ReidentifyFileSample.java new file mode 100644 index 00000000..587d58eb --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/detect/ReidentifyFileSample.java @@ -0,0 +1,74 @@ +package com.example.detect; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.FileDataFormat; +import com.skyflow.detect.FileOutput; +import com.skyflow.detect.RedactionLevel; +import com.skyflow.detect.ReidentifyFileRequest; +import com.skyflow.detect.ReidentifyFileResponse; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; + +import java.io.File; +import java.nio.file.Files; +import java.util.Base64; +import java.util.Collections; + +/** + * Re-identifies the tokens in a text file with the Detect V2 API. The file is sent as base64 and + * the re-identified file is returned synchronously and written next to the input. + */ +public class ReidentifyFileSample { + public static void main(String[] args) throws Exception { + Credentials credentials = new Credentials(); + credentials.setPath(""); // Replace with the path to the credentials file + + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); // Replace with the ID of the vault + vaultConfig.setClusterId(""); // Replace with the cluster ID of the vault + vaultConfig.setEnv(Env.PROD); // Set the environment (e.g., DEV, STAGE, PROD) + + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .addSkyflowCredentials(credentials) + .build(); + + File input = new File(""); // Replace with a file produced by deidentification + String base64 = Base64.getEncoder().encodeToString(Files.readAllBytes(input.toPath())); + + try { + ReidentifyFileResponse response = skyflowClient.detect().reidentifyFile( + ReidentifyFileRequest.builder() + .dataSource(DataSourceType.BASE64) + .value(base64) + .dataFormat(FileDataFormat.TXT) + // Optional: render one token group with a named redaction pattern instead of its plain value. + .redactionLevel(Collections.singletonList( + RedactionLevel.builder() + .tokenGroupName("") // Replace with a token group name + .redactionPattern("") // Replace with a redaction pattern name + .build())) + .build()); + + System.out.println("Status: " + response.getStatus()); + if (response.getStatus() == DetectRunStatus.SUCCESS) { + for (FileOutput output : response.getOutput()) { + String name = "reidentified-" + input.getName(); + Files.write(new File(input.getParentFile(), name).toPath(), + Base64.getDecoder().decode(output.getProcessedFile())); + System.out.println("Wrote " + name + " (" + output.getProcessedFileType() + ")"); + } + System.out.println("Metrics: " + response.getMetrics()); + } + } catch (SkyflowException e) { + System.err.println("Error during reidentifyFile:"); + e.printStackTrace(); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/detect/ReidentifyStringSample.java b/flowvault/samples/src/main/java/com/example/detect/ReidentifyStringSample.java new file mode 100644 index 00000000..508bcc6c --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/detect/ReidentifyStringSample.java @@ -0,0 +1,59 @@ +package com.example.detect; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.detect.RedactionLevel; +import com.skyflow.detect.ReidentifyStringRequest; +import com.skyflow.detect.ReidentifyStringResponse; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; + +import java.util.Collections; + +/** + * Re-identifies tokens in a string with the Detect V2 API. For flowvault vaults, redaction + * levels target a token group and apply a named redaction pattern. + */ +public class ReidentifyStringSample { + public static void main(String[] args) throws SkyflowException { + Credentials credentials = new Credentials(); + credentials.setPath(""); // Replace with the path to the credentials file + + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); // Replace with the ID of the vault + vaultConfig.setClusterId(""); // Replace with the cluster ID of the vault + vaultConfig.setEnv(Env.PROD); // Set the environment (e.g., DEV, STAGE, PROD) + + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .addSkyflowCredentials(credentials) + .build(); + + try { + // Plain re-identification: every token is replaced by its original value. + ReidentifyStringResponse response = skyflowClient.detect().reidentifyString( + ReidentifyStringRequest.builder() + .text("My name is [NAME_1] and my email is [EMAIL_ADDRESS_1].") + .build()); + System.out.println("Processed text: " + response.getProcessedText()); + + // Targeted rendering: tokens from one token group are rendered with a named pattern. + ReidentifyStringResponse masked = skyflowClient.detect().reidentifyString( + ReidentifyStringRequest.builder() + .text("My name is [NAME_1].") + .redactionLevel(Collections.singletonList( + RedactionLevel.builder() + .tokenGroupName("") // Replace with a token group name + .redactionPattern("") // Replace with a redaction pattern name + .build())) + .build()); + System.out.println("Masked text: " + masked.getProcessedText()); + } catch (SkyflowException e) { + System.err.println("Error during reidentifyString:"); + e.printStackTrace(); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/Skyflow.java b/flowvault/src/main/java/com/skyflow/Skyflow.java index c2647824..b1955b63 100644 --- a/flowvault/src/main/java/com/skyflow/Skyflow.java +++ b/flowvault/src/main/java/com/skyflow/Skyflow.java @@ -3,7 +3,7 @@ import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; import com.skyflow.enums.LogLevel; -import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; import com.skyflow.logs.InfoLogs; @@ -12,6 +12,7 @@ import com.skyflow.utils.Utils; import com.skyflow.utils.logger.LogUtil; import com.skyflow.utils.validations.Validations; +import com.skyflow.vault.controller.DetectController; import com.skyflow.vault.controller.VaultController; import java.util.LinkedHashMap; @@ -55,16 +56,38 @@ public Skyflow updateVaultConfig(VaultConfig vaultConfig) throws SkyflowExceptio } public VaultController vault() throws SkyflowException { - return resolveOrThrow(this.builder.vaultClientsMap, null, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + return resolveOrThrow(this.builder.vaultClientsMap, null, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, BaseErrorMessage.VaultIdNotInConfigList); } public VaultController vault(String vaultId) throws SkyflowException { - return resolveOrThrow(this.builder.vaultClientsMap, vaultId, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + return resolveOrThrow(this.builder.vaultClientsMap, vaultId, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, BaseErrorMessage.VaultIdNotInConfigList); + } + + /** + * Detect operations for the single configured vault. + * + * @return the detect controller + * @throws SkyflowException if no vault is configured + */ + public DetectController detect() throws SkyflowException { + return resolveOrThrow(this.builder.detectClientsMap, null, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, BaseErrorMessage.VaultIdNotInConfigList); + } + + /** + * Detect operations for a specific configured vault. + * + * @param vaultId id of a configured vault + * @return the detect controller + * @throws SkyflowException if the vault is not configured + */ + public DetectController detect(String vaultId) throws SkyflowException { + return resolveOrThrow(this.builder.detectClientsMap, vaultId, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, BaseErrorMessage.VaultIdNotInConfigList); } public static final class SkyflowClientBuilder extends BaseSkyflowClientBuilder { private final LinkedHashMap vaultClientsMap = new LinkedHashMap<>(); + private final LinkedHashMap detectClientsMap = new LinkedHashMap<>(); // Client-wide HTTP config. Resolution per vault, most specific first: // VaultConfig value -> the value set here -> SDK default (60s call timeout, 0 retries). // null here means "not set", so the SDK default applies to vaults that don't override it. @@ -89,6 +112,9 @@ protected void onVaultConfigAdded(VaultConfig vaultConfig) throws SkyflowExcepti this.writeTimeout, this.maxRetries, this.initialRetryDelayMillis, this.maxRetryDelayMillis); this.vaultClientsMap.put(vaultConfig.getVaultId(), controller); + DetectController detectController = new DetectController(vaultConfig, this.skyflowCredentials); + detectController.setCommonHttpConfig(this.timeout, this.maxRetries, this.initialRetryDelayMillis, this.maxRetryDelayMillis); + this.detectClientsMap.put(vaultConfig.getVaultId(), detectController); LogUtil.printInfoLog(Utils.parameterizedString(InfoLogs.VAULT_CONTROLLER_INITIALIZED.getLog(), vaultConfig.getVaultId())); } @@ -106,11 +132,20 @@ protected void onVaultConfigUpdated(VaultConfig updatedConfig) throws SkyflowExc updated.setCommonHttpConfig(this.timeout, this.connectTimeout, this.readTimeout, this.writeTimeout, this.maxRetries, this.initialRetryDelayMillis, this.maxRetryDelayMillis); + DetectController updatedDetect = this.detectClientsMap.get(updatedConfig.getVaultId()); + if (updatedDetect == null) { + updatedDetect = new DetectController(updatedConfig, this.skyflowCredentials); + this.detectClientsMap.put(updatedConfig.getVaultId(), updatedDetect); + } else { + updatedDetect.setVaultConfig(updatedConfig); + } + updatedDetect.setCommonHttpConfig(this.timeout, this.maxRetries, this.initialRetryDelayMillis, this.maxRetryDelayMillis); } @Override protected void onVaultConfigRemoved(String vaultId) throws SkyflowException { this.vaultClientsMap.remove(vaultId); + this.detectClientsMap.remove(vaultId); } @Override @@ -123,6 +158,9 @@ protected void onCredentialsUpdated(Credentials credentials) throws SkyflowExcep for (VaultController vault : this.vaultClientsMap.values()) { vault.setCommonCredentials(credentials); } + for (DetectController detect : this.detectClientsMap.values()) { + detect.setCommonCredentials(credentials); + } } @Override @@ -179,6 +217,10 @@ private void carryVaultOverrides(VaultConfig incoming) throws SkyflowException { if (controller != null) { controller.refreshVaultUrl(); } + DetectController detectController = this.detectClientsMap.get(incoming.getVaultId()); + if (detectController != null) { + detectController.refreshVaultUrl(); + } } } @@ -294,6 +336,10 @@ private void propagateHttpConfig() { this.writeTimeout, this.maxRetries, this.initialRetryDelayMillis, this.maxRetryDelayMillis); } + for (DetectController detect : this.detectClientsMap.values()) { + detect.setCommonHttpConfig(this.timeout, this.maxRetries, this.initialRetryDelayMillis, + this.maxRetryDelayMillis); + } } public Skyflow build() { diff --git a/flowvault/src/main/java/com/skyflow/detect/ReidentifyFileRequest.java b/flowvault/src/main/java/com/skyflow/detect/ReidentifyFileRequest.java new file mode 100644 index 00000000..9aa7135e --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/detect/ReidentifyFileRequest.java @@ -0,0 +1,118 @@ +package com.skyflow.detect; + +import java.util.List; + +/** + * Request for {@code reidentifyFile}. Supply the file through exactly one {@link DataSourceType}: + * the base64-encoded content, the skyflow id of a vault record that holds the file, or an S3 + * presigned URL. The vault id is filled in by the controller. + */ +public final class ReidentifyFileRequest { + private final DataSourceType dataSource; + private final String value; + private final FileDataFormat dataFormat; + private final List redactionLevel; + + private ReidentifyFileRequest(ReidentifyFileRequestBuilder builder) { + this.dataSource = builder.dataSource; + this.value = builder.value; + this.dataFormat = builder.dataFormat; + this.redactionLevel = builder.redactionLevel; + } + + /** + * @return a new builder + */ + public static ReidentifyFileRequestBuilder builder() { + return new ReidentifyFileRequestBuilder(); + } + + /** + * @return How {@code value} supplies the file. Required. + */ + public DataSourceType getDataSource() { + return dataSource; + } + + /** + * @return Base64 content, skyflow id or presigned URL of the file, according to {@code dataSource}. Required. + */ + public String getValue() { + return value; + } + + /** + * @return Format of the file. Optional; the API infers it when omitted. Re-identification supports text formats + * such as TXT, CSV, JSON, JSONL, XML, DOCX, XLSX and PPTX. + */ + public FileDataFormat getDataFormat() { + return dataFormat; + } + + /** + * @return Optional per-group rendering rules. + */ + public List getRedactionLevel() { + return redactionLevel; + } + + @Override + public String toString() { + return "ReidentifyFileRequest{" + + "dataSource=" + dataSource + + ", value=" + value + + ", dataFormat=" + dataFormat + + ", redactionLevel=" + redactionLevel + + '}'; + } + + public static final class ReidentifyFileRequestBuilder { + private DataSourceType dataSource; + private String value; + private FileDataFormat dataFormat; + private List redactionLevel; + + private ReidentifyFileRequestBuilder() { + } + + /** + * @param dataSource How {@code value} supplies the file. Required. + * @return this builder + */ + public ReidentifyFileRequestBuilder dataSource(DataSourceType dataSource) { + this.dataSource = dataSource; + return this; + } + + /** + * @param value Base64 content, skyflow id or presigned URL of the file, according to {@code dataSource}. Required. + * @return this builder + */ + public ReidentifyFileRequestBuilder value(String value) { + this.value = value; + return this; + } + + /** + * @param dataFormat Format of the file. Optional. + * @return this builder + */ + public ReidentifyFileRequestBuilder dataFormat(FileDataFormat dataFormat) { + this.dataFormat = dataFormat; + return this; + } + + /** + * @param redactionLevel Optional per-group rendering rules. + * @return this builder + */ + public ReidentifyFileRequestBuilder redactionLevel(List redactionLevel) { + this.redactionLevel = redactionLevel; + return this; + } + + public ReidentifyFileRequest build() { + return new ReidentifyFileRequest(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/detect/ReidentifyFileResponse.java b/flowvault/src/main/java/com/skyflow/detect/ReidentifyFileResponse.java new file mode 100644 index 00000000..17d32f3e --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/detect/ReidentifyFileResponse.java @@ -0,0 +1,59 @@ +package com.skyflow.detect; + +import java.util.List; + +/** + * Response of {@code reidentifyFile}. File re-identification is synchronous, so a {@code SUCCESS} + * status carries the output in the same response. + */ +public final class ReidentifyFileResponse { + private final DetectRunStatus status; + private final DataSourceType outputType; + private final List output; + private final Metrics metrics; + + public ReidentifyFileResponse(DetectRunStatus status, DataSourceType outputType, List output, Metrics metrics) { + this.status = status; + this.outputType = outputType; + this.output = output; + this.metrics = metrics; + } + + /** + * @return Processing status. UNKNOWN when the API did not report one. + */ + public DetectRunStatus getStatus() { + return status; + } + + /** + * @return How output files are delivered, or null when not reported. + */ + public DataSourceType getOutputType() { + return outputType; + } + + /** + * @return Output artifacts. Empty when none were returned, never null. + */ + public List getOutput() { + return output; + } + + /** + * @return Processing metrics, or null when not reported. + */ + public Metrics getMetrics() { + return metrics; + } + + @Override + public String toString() { + return "ReidentifyFileResponse{" + + "status=" + status + + ", outputType=" + outputType + + ", output=" + output + + ", metrics=" + metrics + + '}'; + } +} diff --git a/flowvault/src/main/java/com/skyflow/detect/internal/ReidentifyFileMapper.java b/flowvault/src/main/java/com/skyflow/detect/internal/ReidentifyFileMapper.java new file mode 100644 index 00000000..d1653166 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/detect/internal/ReidentifyFileMapper.java @@ -0,0 +1,69 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.FileOutput; +import com.skyflow.detect.ReidentifyFileRequest; +import com.skyflow.detect.ReidentifyFileResponse; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.ReidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.resources.filesv2.types.ReidentifyFileRequestV2DataFormat; +import com.skyflow.generated.detect.rest.resources.filesv2.types.ReidentifyFileRequestV2DataSource; +import com.skyflow.generated.detect.rest.types.ReidentifyFileResponseV2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Converts the flowvault {@code reidentifyFile} models to and from the generated V2 API types. + * Internal to the SDK. + */ +public final class ReidentifyFileMapper { + private ReidentifyFileMapper() { + } + + public static ReidentifyFileRequestV2 toApiRequest(ReidentifyFileRequest request, String vaultId) { + ReidentifyFileRequestV2._FinalStage builder = ReidentifyFileRequestV2.builder() + .dataSource(ReidentifyFileRequestV2DataSource.valueOf(request.getDataSource().name())) + .value(request.getValue()) + .vaultId(vaultId); + if (request.getDataFormat() != null) { + // The wire value for a file format is the lower-case extension. + builder.dataFormat(ReidentifyFileRequestV2DataFormat.valueOf(request.getDataFormat().name().toLowerCase())); + } + if (request.getRedactionLevel() != null) { + builder.redactionLevel(DetectRequestMapper.toRedactionLevels(request.getRedactionLevel())); + } + return builder.build(); + } + + public static ReidentifyFileResponse toResponse(ReidentifyFileResponseV2 response) { + List outputs = new ArrayList<>(); + List apiOutputs = response.getOutput().orElse(null); + if (apiOutputs != null) { + for (com.skyflow.generated.detect.rest.types.FileOutput apiOutput : apiOutputs) { + if (apiOutput != null) { + outputs.add(DetectResponseMapper.toFileOutput(apiOutput)); + } + } + } + return new ReidentifyFileResponse( + response.getStatus().map(s -> enumOrDefault(DetectRunStatus.class, s.toString(), DetectRunStatus.UNKNOWN)) + .orElse(DetectRunStatus.UNKNOWN), + response.getOutputType().map(t -> enumOrDefault(DataSourceType.class, t.toString(), null)).orElse(null), + Collections.unmodifiableList(outputs), + response.getMetrics().map(DetectResponseMapper::toMetrics).orElse(null)); + } + + /** Maps a wire value onto a public enum, tolerating values this SDK version does not know. */ + private static > E enumOrDefault(Class type, String value, E fallback) { + if (value == null) { + return fallback; + } + try { + return Enum.valueOf(type, value); + } catch (IllegalArgumentException unknown) { + return fallback; + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/detect/internal/ReidentifyFileValidations.java b/flowvault/src/main/java/com/skyflow/detect/internal/ReidentifyFileValidations.java new file mode 100644 index 00000000..0eafdcc8 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/detect/internal/ReidentifyFileValidations.java @@ -0,0 +1,43 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.ReidentifyFileRequest; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.utils.BaseUtils; +import com.skyflow.utils.logger.LogUtil; + + +/** + * Client-side validation for the flowvault {@code reidentifyFile} request. Internal to the SDK. + */ +public final class ReidentifyFileValidations { + private static final String INTERFACE_NAME = "detect"; + + private ReidentifyFileValidations() { + } + + public static void validateReidentifyFileRequest(ReidentifyFileRequest request) throws SkyflowException { + if (request == null) { + throw invalid(ErrorLogs.REIDENTIFY_FILE_REQUEST_NULL, ErrorMessage.ReidentifyFileRequestNull); + } + if (request.getDataSource() == null) { + throw invalid(ErrorLogs.INVALID_DATA_SOURCE_IN_REIDENTIFY_FILE, ErrorMessage.InvalidDataSourceInReidentifyFile); + } + if (isBlank(request.getValue())) { + throw invalid(ErrorLogs.INVALID_VALUE_IN_REIDENTIFY_FILE, ErrorMessage.InvalidValueInReidentifyFile); + } + // redactionLevel entries are optional on the API and are passed through as given. + } + + + private static boolean isBlank(String value) { + return value == null || value.trim().isEmpty(); + } + + private static SkyflowException invalid(ErrorLogs log, ErrorMessage message) { + LogUtil.printErrorLog(BaseUtils.parameterizedString(log.getLog(), INTERFACE_NAME)); + return new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), message.getMessage()); + } +} diff --git a/flowvault/src/main/java/com/skyflow/errors/ErrorMessage.java b/flowvault/src/main/java/com/skyflow/errors/ErrorMessage.java new file mode 100644 index 00000000..cceec8b8 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/errors/ErrorMessage.java @@ -0,0 +1,77 @@ +package com.skyflow.errors; + +import com.skyflow.utils.Constants; + +/** + * Validation messages specific to the flowvault SDK. Messages shared by every SDK live in + * {@link BaseErrorMessage} in common; this enum holds only what common and skyvault do not use. + */ +public enum ErrorMessage { + // Vault config + EmptyVaultUrl("%s0 Initialization failed. Vault URL is empty. Specify a valid vault URL."), + InvalidVaultUrlFormat("%s0 Initialization failed. Vault URL must start with 'https://'."), + EitherVaultUrlOrClusterIdRequired("%s0 Initialization failed. Specify either 'clusterId' or 'vaultURL'."), + // Insert / upsert + TableSpecifiedInRequestAndRecordObject("%s0 Validation error. Table name cannot be specified at both the request and record levels. Please specify the table name in only one place."), + TableNotSpecifiedInRequestAndRecordObject("%s0 Validation error. Table name is missing. Table name should be specified at one place either at the request level or record level. Please specify the table name at one place."), + UpsertTableRequestAtRecordLevel("%s0 Validation error. Table name should be present at each record level when upsert is present at record level."), + UpsertTableRequestAtRequestLevel("%s0 Validation error. Upsert should be present at each record level when table name is present at record level."), + EmptyRecords("%s0 Validation error. 'records' can't be empty. Specify records."), + EmptyKeyInRecords("%s0 Validation error. Invalid key in data in records. Specify a valid key."), + EmptyValueInRecords("%s0 Validation error. Invalid value in records. Specify a valid value."), + RecordsKeyError("%s0 Validation error. 'records' key is missing from the payload. Specify a 'records' key."), + InvalidUpsertUpdateType("%s0 Validation error. Invalid upsert updateType. Specify either 'UPDATE' or 'REPLACE'."), + EmptyUpsertValues("%s0 Validation error. Upsert column values can't be empty. Specify at least one upsert column."), + RecordSizeExceedError("%s0 Maximum number of records exceeded. The limit is 100000."), + InvalidRecord("%s0 Validation error. InsertRecord object in the list is invalid. Specify a valid InsertRecord object."), + BothSingleTableFieldsAndRecordsSpecified("%s0 Validation error. Both single-table lookup fields ('table', 'ids', 'fields', 'uniqueValues', 'columnRedactions') and 'records' can't be specified. Either specify single-table fields or 'records'."), + // Detokenize / delete tokens + EmptyValues("%s0 Validation error. 'values' can't be empty. Specify values."), + EmptyValueInValues("%s0 Validation error. Invalid value in values. Specify a valid value."), + EmptyValueInTokens("%s0 Validation error. Invalid value in tokens. Specify a valid value."), + TokensSizeExceedError("%s0 Maximum number of tokens exceeded. The limit is 100000."), + DeleteTokensRequestNull("%s0 Validation error. DeleteTokensRequest object is null. Specify a valid DeleteTokensRequest object."), + EmptyDeleteTokensData("%s0 Validation error. Tokens list is empty. Specify at least one token to delete."), + EmptyTokenInDeleteTokensData("%s0 Validation error. Invalid token in delete tokens request. Specify a valid token."), + DeleteTokensSizeExceedError("%s0 Maximum number of tokens exceeded. The limit is 100000."), + NullTokenGroupRedactions("%s0 Validation error. TokenGroupRedaction in the list is null. Specify a valid TokenGroupRedactions object."), + NullRedactionInTokenGroup("%s0 Validation error. Redaction in TokenGroupRedactions is null or empty. Specify a valid redaction."), + NullTokenGroupNameInTokenGroup("%s0 Validation error. TokenGroupName in TokenGroupRedactions is null or empty. Specify a valid tokenGroupName."), + // Get / update + IdsOrUniqueValuesKeyError("%s0 Validation error. 'ids' or 'uniqueValues' key is missing from the payload. Specify ids or uniqueValues in payload."), + BothIdsAndUniqueValuesSpecified("%s0 Validation error. Both Skyflow IDs and unique values can't be specified. Either specify Skyflow IDs or unique values."), + EmptyUniqueValues("%s0 Validation error. 'uniqueValues' can't be empty. Specify at least one unique value."), + EmptyUniqueValueInUniqueValues("%s0 Validation error. Invalid unique value in 'uniqueValues'. Specify a valid unique value."), + NullColumnRedactions("%s0 Validation error. Column redaction object can not be null. Specify a valid column redaction object."), + NullColumnNameInColumnRedaction("%s0 Validation error. Column name can not be null or empty in column redaction. Specify a valid column name."), + NullRedactionInColumnRedaction("%s0 Validation error. Redaction can not be null or empty in column redaction. Specify a valid redaction."), + NullGetRecordRequest("%s0 Validation error. Record in 'records' is null. Specify a valid record."), + UpdateRecordNull("%s0 Validation error. UpdateRequestRecord object in the list is null. Specify a valid UpdateRequestRecord object."), + RecordSkyflowIdKeyError("%s0 Validation error. 'skyflowId' key is missing from the record. Specify a 'skyflowId' key."), + EmptySkyflowIdInRecord("%s0 Validation error. 'skyflowId' can't be empty in the record. Specify a valid skyflow ID."), + // Tokenize + EmptyTokenizeData("%s0 Validation error. Tokenize data is empty. Specify at least one tokenize record."), + TokenizeRecordNull("%s0 Validation error. TokenizeRecord in the list is null. Specify a valid TokenizeRecord object."), + InvalidBulkTokenizeRecordType("%s0 Validation error. Record in the list must be of type BulkTokenizeRequestRecord. Specify a valid BulkTokenizeRequestRecord object."), + EmptyValueInTokenizeRecord("%s0 Validation error. Value in TokenizeRecord is null or empty. Specify a valid value."), + EmptyTokenGroupNamesInTokenizeRecord("%s0 Validation error. TokenGroupNames in TokenizeRecord is null or empty. Specify at least one token group name."), + EmptyTokenGroupNameInTokenizeRecord("%s0 Validation error. Token group name in TokenizeRecord is null or empty. Specify a valid token group name."), + TokenizeDataSizeExceedError("%s0 Maximum number of tokenize records exceeded. The limit is 100000."), + MissingIndexInBulkTokenizeRecord("%s0 Validation error. Index in BulkTokenizeRequestRecord is null. Specify an index for every record."), + DuplicateIndexInBulkTokenizeRecord("%s0 Validation error. Duplicate index in BulkTokenizeRequestRecord. Specify a unique index for every record."), + // Detect (flowvault only) + ReidentifyFileRequestNull("%s0 Validation error. ReidentifyFile request cannot be null."), + InvalidDataSourceInReidentifyFile("%s0 Validation error. Invalid dataSource in reidentify file request. Specify BASE64, SKYFLOW_ID or PRESIGNED_URL."), + InvalidValueInReidentifyFile("%s0 Validation error. Invalid value in reidentify file request. Specify the non-empty base64 content, skyflow id or presigned URL of the file."), + ; + + private final String message; + + ErrorMessage(String message) { + this.message = message.replace("%s0", Constants.SDK_PREFIX); + } + + public String getMessage() { + return message; + } +} diff --git a/flowvault/src/main/java/com/skyflow/utils/validations/Validations.java b/flowvault/src/main/java/com/skyflow/utils/validations/Validations.java index 084b01d7..257685f1 100644 --- a/flowvault/src/main/java/com/skyflow/utils/validations/Validations.java +++ b/flowvault/src/main/java/com/skyflow/utils/validations/Validations.java @@ -5,6 +5,7 @@ import com.skyflow.enums.InterfaceName; import com.skyflow.generated.rest.types.FlowEnumUpdateType; import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; @@ -42,38 +43,38 @@ public static void validateCredentials(Credentials credentials) throws SkyflowEx if (nonNullMembers > 1) { LogUtil.printErrorLog(ErrorLogs.MULTIPLE_TOKEN_GENERATION_MEANS_PASSED.getLog()); throw new SkyflowException( - ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MultipleTokenGenerationMeansPassed.getMessage() + ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.MultipleTokenGenerationMeansPassed.getMessage() ); } else if (nonNullMembers < 1) { LogUtil.printErrorLog(ErrorLogs.NO_TOKEN_GENERATION_MEANS_PASSED.getLog()); throw new SkyflowException( - ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.NoTokenGenerationMeansPassed.getMessage() + ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.NoTokenGenerationMeansPassed.getMessage() ); } else if (path != null && path.trim().isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_CREDENTIALS_PATH.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentialFilePath.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyCredentialFilePath.getMessage()); } else if (credentialsString != null && credentialsString.trim().isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_CREDENTIALS_STRING.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentialsString.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyCredentialsString.getMessage()); } else if (token != null && token.trim().isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_TOKEN_VALUE.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyToken.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyToken.getMessage()); } else if (apiKey != null) { if (apiKey.trim().isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_API_KEY_VALUE.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyApikey.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyApikey.getMessage()); } else { Pattern pattern = Pattern.compile(Constants.API_KEY_REGEX); Matcher matcher = pattern.matcher(apiKey); if (!matcher.matches()) { LogUtil.printErrorLog(ErrorLogs.INVALID_API_KEY.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidApikey.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidApikey.getMessage()); } } } else if (roles != null) { if (roles.isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_ROLES.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRoles.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyRoles.getMessage()); } else { for (int index = 0; index < roles.size(); index++) { String role = roles.get(index); @@ -81,7 +82,7 @@ public static void validateCredentials(Credentials credentials) throws SkyflowEx LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.EMPTY_OR_NULL_ROLE_IN_ROLES.getLog(), Integer.toString(index) )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRoleInRoles.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyRoleInRoles.getMessage()); } } } @@ -91,13 +92,13 @@ public static void validateCredentials(Credentials credentials) throws SkyflowEx String ctxStr = (String) context; if (ctxStr.trim().isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_OR_NULL_CONTEXT.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyContext.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyContext.getMessage()); } } else if (context instanceof Map) { Map ctxMap = (Map) context; if (ctxMap.isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_OR_NULL_CONTEXT.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyContext.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyContext.getMessage()); } Pattern ctxKeyPattern = Pattern.compile(Constants.CONTEXT_KEY_REGEX); for (Object key : ctxMap.keySet()) { @@ -106,12 +107,12 @@ public static void validateCredentials(Credentials credentials) throws SkyflowEx LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.INVALID_CONTEXT_MAP_KEY.getLog(), keyStr)); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), - Utils.parameterizedString(ErrorMessage.InvalidContextMapKey.getMessage(), keyStr)); + Utils.parameterizedString(BaseErrorMessage.InvalidContextMapKey.getMessage(), keyStr)); } } } else { LogUtil.printErrorLog(ErrorLogs.INVALID_CONTEXT_TYPE.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidContextType.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidContextType.getMessage()); } } } @@ -120,7 +121,7 @@ public static void validateCredentials(Credentials credentials) throws SkyflowEx public static void validateVaultConfiguration(VaultConfig vaultConfig) throws SkyflowException { if (vaultConfig == null) { LogUtil.printErrorLog(ErrorLogs.VAULT_CONFIG_IS_NULL.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.NullVaultConfig.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.NullVaultConfig.getMessage()); } String vaultId = vaultConfig.getVaultId(); String clusterId = vaultConfig.getClusterId(); @@ -129,10 +130,10 @@ public static void validateVaultConfiguration(VaultConfig vaultConfig) throws Sk if (vaultId == null) { LogUtil.printErrorLog(ErrorLogs.VAULT_ID_IS_REQUIRED.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidVaultId.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InvalidVaultId.getMessage()); } else if (vaultId.trim().isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_VAULT_ID.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyVaultId.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyVaultId.getMessage()); } else if (credentials != null) { validateCredentials(credentials); } @@ -153,7 +154,7 @@ public static void validateVaultConfiguration(VaultConfig vaultConfig) throws Sk throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EitherVaultUrlOrClusterIdRequired.getMessage()); } else if (clusterId.trim().isEmpty()) { LogUtil.printErrorLog(ErrorLogs.EMPTY_CLUSTER_ID.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyClusterId.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyClusterId.getMessage()); } } } @@ -164,7 +165,7 @@ public static void validateInsertRequest(InsertRequest insertRequest) throws Sky LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.INSERT_REQUEST_NULL.getLog(), InterfaceName.INSERT.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InsertRequestNull.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.InsertRequestNull.getMessage()); } List records = insertRequest.getRecords(); if (records == null) { @@ -225,14 +226,14 @@ private static void validateInsertRecordTokens(Map tokens) throw LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.EMPTY_TOKENS.getLog(), InterfaceName.INSERT.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyTokens.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyTokens.getMessage()); } for (String key : tokens.keySet()) { if (key == null || key.trim().isEmpty()) { LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.EMPTY_OR_NULL_KEY_IN_TOKENS.getLog(), InterfaceName.INSERT.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyKeyInTokens.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyKeyInTokens.getMessage()); } Object value = tokens.get(key); if (value == null || value.toString().trim().isEmpty()) { @@ -332,14 +333,14 @@ public static void validateDetokenizeRequest(DetokenizeRequest request) throws S LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.DETOKENIZE_REQUEST_NULL.getLog(), InterfaceName.DETOKENIZE.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.DetokenizeRequestNull.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.DetokenizeRequestNull.getMessage()); } List tokens = request.getTokens(); if (tokens == null || tokens.isEmpty()) { LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.EMPTY_DETOKENIZE_DATA.getLog(), InterfaceName.DETOKENIZE.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyDetokenizeData.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyDetokenizeData.getMessage()); } for (int index = 0; index < tokens.size(); index++) { @@ -349,7 +350,7 @@ public static void validateDetokenizeRequest(DetokenizeRequest request) throws S ErrorLogs.EMPTY_OR_NULL_TOKEN_IN_DETOKENIZE_DATA.getLog(), InterfaceName.DETOKENIZE.getName(), String.valueOf(index))); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyTokenInDetokenizeData.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyTokenInDetokenizeData.getMessage()); } } @@ -418,7 +419,7 @@ public static void validateDeleteRequest(DeleteRequest deleteRequest) throws Sky LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.DELETE_REQUEST_NULL.getLog(), InterfaceName.DELETE_RECORDS.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.DeleteRequestNull.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.DeleteRequestNull.getMessage()); } validateTableRequired(deleteRequest.getTableName(), InterfaceName.DELETE_RECORDS); validateIdsOrUniqueValues(deleteRequest.getSkyflowIds(), deleteRequest.getUniqueValues(), InterfaceName.DELETE_RECORDS); @@ -458,19 +459,19 @@ public static void validateUpdateRequest(UpdateRequest updateRequest) throws Sky LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.UPDATE_REQUEST_NULL.getLog(), InterfaceName.UPDATE.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.UpdateRequestNull.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.UpdateRequestNull.getMessage()); } String tableName = updateRequest.getTableName(); if (tableName == null) { LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.TABLE_IS_REQUIRED.getLog(), InterfaceName.UPDATE.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.TableKeyError.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.TableKeyError.getMessage()); } else if (tableName.trim().isEmpty()) { LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.EMPTY_TABLE_NAME.getLog(), InterfaceName.UPDATE.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyTable.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyTable.getMessage()); } List records = updateRequest.getRecords(); @@ -532,14 +533,14 @@ public static void validateUpdateRequest(UpdateRequest updateRequest) throws Sky LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.EMPTY_TOKENS.getLog(), InterfaceName.UPDATE.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyTokens.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyTokens.getMessage()); } for (String key : tokens.keySet()) { if (key == null || key.trim().isEmpty()) { LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.EMPTY_OR_NULL_KEY_IN_TOKENS.getLog(), InterfaceName.UPDATE.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyKeyInTokens.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyKeyInTokens.getMessage()); } Object value = tokens.get(key); if (value == null || value.toString().trim().isEmpty()) { @@ -559,7 +560,7 @@ public static void validateGetRequest(GetRequest getRequest) throws SkyflowExcep LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.GET_REQUEST_NULL.getLog(), InterfaceName.GET.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.GetRequestNull.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.GetRequestNull.getMessage()); } boolean hasSingleTableFields = hasText(getRequest.getTableName()) @@ -603,12 +604,12 @@ private static void validateTableRequired(String table, InterfaceName interfaceN LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.TABLE_IS_REQUIRED.getLog(), interfaceName.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.TableKeyError.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.TableKeyError.getMessage()); } else if (table.trim().isEmpty()) { LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.EMPTY_TABLE_NAME.getLog(), interfaceName.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyTable.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyTable.getMessage()); } } @@ -638,7 +639,7 @@ private static void validateIdsOrUniqueValues(List ids, List fields) throws SkyflowException LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.EMPTY_FIELDS.getLog(), InterfaceName.GET.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyFields.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyFields.getMessage()); } for (int index = 0; index < fields.size(); index++) { String field = fields.get(index); @@ -671,7 +672,7 @@ private static void validateFields(List fields) throws SkyflowException LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.EMPTY_OR_NULL_FIELD_IN_FIELDS.getLog(), InterfaceName.GET.getName(), String.valueOf(index) )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyFieldInFields.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.EmptyFieldInFields.getMessage()); } } } @@ -711,7 +712,7 @@ public static void validateBulkTokenizeRequest(BulkTokenizeRequest request) thro LogUtil.printErrorLog(Utils.parameterizedString( ErrorLogs.TOKENIZE_REQUEST_NULL.getLog(), InterfaceName.TOKENIZE.getName() )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.TokenizeRequestNull.getMessage()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseErrorMessage.TokenizeRequestNull.getMessage()); } List records = request.getRecords(); if (records == null || records.isEmpty()) { diff --git a/flowvault/src/main/java/com/skyflow/vault/controller/DetectController.java b/flowvault/src/main/java/com/skyflow/vault/controller/DetectController.java new file mode 100644 index 00000000..3f86640d --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/controller/DetectController.java @@ -0,0 +1,183 @@ +package com.skyflow.vault.controller; + +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.detect.DeidentifyFileRequest; +import com.skyflow.detect.DeidentifyFileResponse; +import com.skyflow.detect.GetRunRequest; +import com.skyflow.detect.ReidentifyFileRequest; +import com.skyflow.detect.ReidentifyFileResponse; +import com.skyflow.detect.internal.DeidentifyFileFlow; +import com.skyflow.detect.internal.DetectRequestMapper; +import com.skyflow.detect.internal.DetectValidations; +import com.skyflow.detect.internal.ReidentifyFileMapper; +import com.skyflow.detect.internal.ReidentifyFileValidations; +import com.skyflow.errors.SkyflowException; +import com.skyflow.generated.detect.rest.core.ApiClientApiException; +import com.skyflow.generated.detect.rest.core.ApiClientException; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.core.RequestOptions; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.DeidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.ReidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.types.DeidentifyFileResponseV2; +import com.skyflow.generated.detect.rest.types.ReidentifyFileResponseV2; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.BaseConstants; +import com.skyflow.utils.Utils; +import com.skyflow.utils.logger.LogUtil; + +/** + * Skyflow Detect operations for a flowvault vault, returned by {@code Skyflow.detect()} and + * {@code Skyflow.detect(vaultId)}. The shared Detect V2 methods are inherited from + * {@link BaseDetectController}; this class adds the flowvault-only {@link #deidentifyFile} and + * {@link #reidentifyFile} and + * supplies the flowvault URL rules, SDK metrics and the vault-level timeout and retry overrides + * from {@link VaultConfig}. + */ +public final class DetectController extends BaseDetectController { + private static final int DEFAULT_TIMEOUT_SECONDS = 60; + private static final int DEFAULT_MAX_RETRIES = 0; + private static final long DEFAULT_INITIAL_RETRY_DELAY_MILLIS = 500L; + private static final long DEFAULT_MAX_RETRY_DELAY_MILLIS = 2000L; + + public DetectController(VaultConfig vaultConfig, Credentials credentials) { + super(vaultConfig, credentials); + } + + /** Same precedence as the vault controller: environment variable, explicit vaultUrl, then cluster id and env. */ + @Override + protected String resolveVaultUrl() throws SkyflowException { + String vaultUrl = Utils.getEnvVaultUrl(); + if (vaultUrl == null || vaultUrl.isEmpty()) { + vaultUrl = getVaultConfig().getVaultUrl(); + } + if (vaultUrl == null || vaultUrl.isEmpty()) { + vaultUrl = Utils.getVaultUrl(getVaultConfig().getClusterId(), getVaultConfig().getEnv()); + } + return vaultUrl; + } + + @Override + protected String getSdkMetrics() { + return Utils.getMetrics().toString(); + } + + @Override + protected Integer effectiveTimeoutSeconds() { + return firstNonNull(getVaultConfig().getTimeout(), super.effectiveTimeoutSeconds(), DEFAULT_TIMEOUT_SECONDS); + } + + @Override + protected Integer effectiveMaxRetries() { + return firstNonNull(getVaultConfig().getMaxRetries(), super.effectiveMaxRetries(), DEFAULT_MAX_RETRIES); + } + + @Override + protected Long effectiveInitialRetryDelayMillis() { + return firstNonNull(getVaultConfig().getInitialRetryDelayMillis(), super.effectiveInitialRetryDelayMillis(), + DEFAULT_INITIAL_RETRY_DELAY_MILLIS); + } + + @Override + protected Long effectiveMaxRetryDelayMillis() { + return firstNonNull(getVaultConfig().getMaxRetryDelayMillis(), super.effectiveMaxRetryDelayMillis(), + DEFAULT_MAX_RETRY_DELAY_MILLIS); + } + + // ------------------------------------------------------------------ + // Flowvault-only Detect V2 operations + // ------------------------------------------------------------------ + + /** + * Detects and de-identifies sensitive data in a file. The API is asynchronous: by default this + * submits the run and returns its id for use with {@link #getRun}. With + * {@link DeidentifyFileRequest#getPollOptions() pollOptions} the SDK polls the run with exponential + * backoff and returns the completed run, or the run id with a non-terminal status if the budget + * runs out. On success, a request built from a local file has its outputs written to disk. + * + * @param request the file (base64, skyflow id, presigned URL, or a local file) plus a stored or inline configuration + * @return run id, and the run's status, outputs and metrics when polled + * @throws SkyflowException on validation failure, credential failure, an API error or an output write failure + */ + public DeidentifyFileResponse deidentifyFile(DeidentifyFileRequest request) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.DEIDENTIFY_FILE_TRIGGERED.getLog()); + LogUtil.printInfoLog(InfoLogs.VALIDATE_DEIDENTIFY_FILE_REQUEST.getLog()); + DetectValidations.validateDeidentifyFileRequest(request); + String token = resolveToken(); + DeidentifyFileRequestV2 apiRequest = DetectRequestMapper.toDeidentifyFileRequest(request, getVaultConfig().getVaultId()); + String runId; + try { + DeidentifyFileResponseV2 apiResponse = filesClient().deidentifyFileV2(apiRequest, requestOptions(token)); + LogUtil.printInfoLog(InfoLogs.DEIDENTIFY_FILE_REQUEST_RESOLVED.getLog()); + runId = apiResponse.getRunId().orElse(null); + } catch (ApiClientApiException e) { + LogUtil.printErrorLog(ErrorLogs.DEIDENTIFY_FILE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.statusCode(), e, e.headers(), responseBody(e.body())); + } catch (ApiClientException e) { + LogUtil.printErrorLog(ErrorLogs.DEIDENTIFY_FILE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e); + } + DeidentifyFileResponse response = DeidentifyFileFlow.complete(request, runId, + id -> getRun(GetRunRequest.builder().runId(id).build())); + LogUtil.printInfoLog(InfoLogs.DEIDENTIFY_FILE_SUCCESS.getLog()); + return response; + } + + /** + * Replaces tokens in a file with their original values or a redacted rendering. Synchronous: + * the re-identified file is returned in the response rather than through a run id. + * + * @param request the file (base64, skyflow id or presigned URL) plus optional per-group redaction rules + * @return status, output files and metrics + * @throws SkyflowException on validation failure, credential failure or an API error + */ + public ReidentifyFileResponse reidentifyFile(ReidentifyFileRequest request) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.REIDENTIFY_FILE_TRIGGERED.getLog()); + LogUtil.printInfoLog(InfoLogs.VALIDATE_REIDENTIFY_FILE_REQUEST.getLog()); + ReidentifyFileValidations.validateReidentifyFileRequest(request); + String token = resolveToken(); + ReidentifyFileRequestV2 apiRequest = ReidentifyFileMapper.toApiRequest(request, getVaultConfig().getVaultId()); + try { + ReidentifyFileResponseV2 apiResponse = filesClient().reidentifyFileV2(apiRequest, requestOptions(token)); + LogUtil.printInfoLog(InfoLogs.REIDENTIFY_FILE_REQUEST_RESOLVED.getLog()); + ReidentifyFileResponse response = ReidentifyFileMapper.toResponse(apiResponse); + LogUtil.printInfoLog(InfoLogs.REIDENTIFY_FILE_SUCCESS.getLog()); + return response; + } catch (ApiClientApiException e) { + LogUtil.printErrorLog(ErrorLogs.REIDENTIFY_FILE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.statusCode(), e, e.headers(), responseBody(e.body())); + } catch (ApiClientException e) { + LogUtil.printErrorLog(ErrorLogs.REIDENTIFY_FILE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e); + } + } + + private RequestOptions requestOptions(String token) { + return RequestOptions.builder() + .addHeader("Authorization", "Bearer " + token) + .addHeader(BaseConstants.SDK_METRICS_HEADER_KEY, getSdkMetrics()) + .build(); + } + + private static String responseBody(Object body) { + if (body == null) { + return null; + } + if (body instanceof String) { + return (String) body; + } + try { + return ObjectMappers.JSON_MAPPER.writeValueAsString(body); + } catch (Exception ex) { + return body.toString(); + } + } + + private static T firstNonNull(T vaultLevel, T clientLevel, T defaultValue) { + if (vaultLevel != null) { + return vaultLevel; + } + return clientLevel != null ? clientLevel : defaultValue; + } +} diff --git a/flowvault/src/test/java/com/skyflow/detect/internal/ReidentifyFileMapperTests.java b/flowvault/src/test/java/com/skyflow/detect/internal/ReidentifyFileMapperTests.java new file mode 100644 index 00000000..88da01e4 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/detect/internal/ReidentifyFileMapperTests.java @@ -0,0 +1,182 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.EntityType; +import com.skyflow.detect.FileDataFormat; +import com.skyflow.detect.FileOutput; +import com.skyflow.detect.RedactionLevel; +import com.skyflow.detect.RedactionType; +import com.skyflow.detect.ReidentifyFileRequest; +import com.skyflow.detect.ReidentifyFileResponse; +import com.skyflow.generated.detect.rest.core.ObjectMappers; +import com.skyflow.generated.detect.rest.resources.filesv2.requests.ReidentifyFileRequestV2; +import com.skyflow.generated.detect.rest.types.ReidentifyFileResponseV2; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +/** Request mapping is checked on the serialised wire JSON, response mapping from raw API JSON. */ +public class ReidentifyFileMapperTests { + + private static String wire(ReidentifyFileRequestV2 request) throws Exception { + return ObjectMappers.JSON_MAPPER.writeValueAsString(request); + } + + private static ReidentifyFileResponse fromJson(String json) throws Exception { + return ReidentifyFileMapper.toResponse(ObjectMappers.JSON_MAPPER.readValue(json, ReidentifyFileResponseV2.class)); + } + + // ─── request ────────────────────────────────────────────────────────────── + + @Test + public void mapsRequiredFieldsAndInjectsVaultId() throws Exception { + ReidentifyFileRequestV2 api = ReidentifyFileMapper.toApiRequest(ReidentifyFileRequest.builder() + .dataSource(DataSourceType.BASE64).value("W05BTUVfMV0=").build(), "vault-1"); + + Assert.assertEquals("BASE64", api.getDataSource().toString()); + Assert.assertEquals("W05BTUVfMV0=", api.getValue()); + Assert.assertEquals("vault-1", api.getVaultId()); + Assert.assertFalse(api.getDataFormat().isPresent()); + Assert.assertFalse(api.getRedactionLevel().isPresent()); + + String json = wire(api); + Assert.assertTrue(json, json.contains("\"dataSource\":\"BASE64\"")); + Assert.assertTrue(json, json.contains("\"value\":\"W05BTUVfMV0=\"")); + Assert.assertTrue(json, json.contains("\"vaultId\":\"vault-1\"")); + Assert.assertFalse(json, json.contains("dataFormat")); + Assert.assertFalse(json, json.contains("redactionLevel")); + } + + @Test + public void mapsEveryDataSource() throws Exception { + for (DataSourceType source : DataSourceType.values()) { + ReidentifyFileRequestV2 api = ReidentifyFileMapper.toApiRequest(ReidentifyFileRequest.builder() + .dataSource(source).value("x").build(), "v"); + Assert.assertEquals(source.name(), api.getDataSource().toString()); + Assert.assertTrue(wire(api).contains("\"dataSource\":\"" + source.name() + "\"")); + } + } + + @Test + public void dataFormatIsSentAsLowerCaseWireValue() throws Exception { + for (FileDataFormat format : new FileDataFormat[]{FileDataFormat.TXT, FileDataFormat.CSV, FileDataFormat.JSON, + FileDataFormat.JSONL, FileDataFormat.XML, FileDataFormat.DOCX, FileDataFormat.XLSX, FileDataFormat.PPTX}) { + ReidentifyFileRequestV2 api = ReidentifyFileMapper.toApiRequest(ReidentifyFileRequest.builder() + .dataSource(DataSourceType.SKYFLOW_ID).value("rec").dataFormat(format).build(), "v"); + Assert.assertEquals(format.name().toLowerCase(), api.getDataFormat().get().toString()); + Assert.assertTrue(wire(api).contains("\"dataFormat\":\"" + format.name().toLowerCase() + "\"")); + } + } + + @Test + public void mapsFlowvaultAndSkyvaultStyleRedactionLevels() throws Exception { + ReidentifyFileRequestV2 api = ReidentifyFileMapper.toApiRequest(ReidentifyFileRequest.builder() + .dataSource(DataSourceType.PRESIGNED_URL).value("https://s3/presigned") + .redactionLevel(Arrays.asList( + RedactionLevel.builder().tokenGroupName("names").redactionPattern("mask-middle").build(), + RedactionLevel.builder().entityName(EntityType.EMAIL_ADDRESS).redactionType(RedactionType.MASKED).build())) + .build(), "v"); + + Assert.assertEquals(2, api.getRedactionLevel().get().size()); + String json = wire(api); + Assert.assertTrue(json, json.contains("\"tokenGroupName\":\"names\"")); + Assert.assertTrue(json, json.contains("\"redactionPattern\":\"mask-middle\"")); + Assert.assertTrue(json, json.contains("\"entityName\":\"EMAIL_ADDRESS\"")); + Assert.assertTrue(json, json.contains("\"redactionType\":\"MASKED\"")); + // Unset halves of each level must not be serialised as nulls. + Assert.assertFalse(json, json.contains("null")); + } + + @Test + public void emptyRedactionLevelListIsSentAsEmptyArray() throws Exception { + ReidentifyFileRequestV2 api = ReidentifyFileMapper.toApiRequest(ReidentifyFileRequest.builder() + .dataSource(DataSourceType.BASE64).value("x").redactionLevel(Collections.emptyList()).build(), "v"); + Assert.assertTrue(api.getRedactionLevel().get().isEmpty()); + Assert.assertTrue(wire(api).contains("\"redactionLevel\":[]")); + } + + // ─── response ───────────────────────────────────────────────────────────── + + @Test + public void mapsFullResponse() throws Exception { + ReidentifyFileResponse response = fromJson("{\"status\":\"SUCCESS\",\"outputType\":\"PRESIGNED_URL\"," + + "\"output\":[{\"processedFile\":\"https://s3/out-1\",\"processedFileType\":\"REIDENTIFIED_FILE\",\"processedFileExtension\":\"docx\"}," + + "{\"processedFile\":\"https://s3/out-2\",\"processedFileType\":\"ENTITIES\",\"processedFileExtension\":\"json\"}]," + + "\"metrics\":{\"size\":12.5,\"wordCount\":120,\"characterCount\":800,\"pages\":2,\"slides\":0,\"duration\":1.25}}"); + + Assert.assertEquals(DetectRunStatus.SUCCESS, response.getStatus()); + Assert.assertEquals(DataSourceType.PRESIGNED_URL, response.getOutputType()); + Assert.assertEquals(2, response.getOutput().size()); + FileOutput first = response.getOutput().get(0); + Assert.assertEquals("https://s3/out-1", first.getProcessedFile()); + Assert.assertEquals("REIDENTIFIED_FILE", first.getProcessedFileType()); + Assert.assertEquals(FileDataFormat.DOCX, first.getProcessedFileExtension()); + Assert.assertEquals("ENTITIES", response.getOutput().get(1).getProcessedFileType()); + Assert.assertEquals(FileDataFormat.JSON, response.getOutput().get(1).getProcessedFileExtension()); + Assert.assertEquals(12.5, response.getMetrics().getSize(), 1e-9); + Assert.assertEquals(Integer.valueOf(120), response.getMetrics().getWordCount()); + Assert.assertEquals(Integer.valueOf(800), response.getMetrics().getCharacterCount()); + Assert.assertEquals(Integer.valueOf(2), response.getMetrics().getPages()); + Assert.assertEquals(Integer.valueOf(0), response.getMetrics().getSlides()); + Assert.assertEquals(1.25, response.getMetrics().getDuration(), 1e-9); + } + + @Test + public void mapsEveryKnownStatusAndOutputType() throws Exception { + for (DetectRunStatus status : DetectRunStatus.values()) { + Assert.assertEquals(status, fromJson("{\"status\":\"" + status.name() + "\"}").getStatus()); + } + for (DataSourceType type : DataSourceType.values()) { + Assert.assertEquals(type, fromJson("{\"outputType\":\"" + type.name() + "\"}").getOutputType()); + } + } + + @Test + public void emptyResponseYieldsDefaultsNotNulls() throws Exception { + ReidentifyFileResponse response = fromJson("{}"); + Assert.assertEquals(DetectRunStatus.UNKNOWN, response.getStatus()); + Assert.assertNull(response.getOutputType()); + Assert.assertNotNull(response.getOutput()); + Assert.assertTrue(response.getOutput().isEmpty()); + Assert.assertNull(response.getMetrics()); + } + + @Test + public void unknownWireValuesDegradeGracefully() throws Exception { + ReidentifyFileResponse response = fromJson("{\"status\":\"BRAND_NEW\",\"outputType\":\"FTP\"," + + "\"output\":[{\"processedFile\":\"x\",\"processedFileType\":\"NEW_KIND\",\"processedFileExtension\":\"heic\"}]}"); + Assert.assertEquals(DetectRunStatus.UNKNOWN, response.getStatus()); + Assert.assertNull(response.getOutputType()); + FileOutput output = response.getOutput().get(0); + Assert.assertEquals("x", output.getProcessedFile()); + Assert.assertEquals("NEW_KIND", output.getProcessedFileType()); + Assert.assertNull(output.getProcessedFileExtension()); + } + + @Test + public void nullOutputElementsAreSkipped() throws Exception { + Assert.assertEquals(1, fromJson("{\"output\":[null,{\"processedFile\":\"x\"},null]}").getOutput().size()); + } + + @Test + public void outputWithMissingFieldsMapsToNulls() throws Exception { + FileOutput output = fromJson("{\"output\":[{}]}").getOutput().get(0); + Assert.assertNull(output.getProcessedFile()); + Assert.assertNull(output.getProcessedFileType()); + Assert.assertNull(output.getProcessedFileExtension()); + } + + @Test + public void outputListIsUnmodifiable() throws Exception { + ReidentifyFileResponse response = fromJson("{\"output\":[{\"processedFile\":\"x\"}]}"); + try { + response.getOutput().clear(); + Assert.fail("expected UnsupportedOperationException"); + } catch (UnsupportedOperationException expected) { + Assert.assertEquals(1, response.getOutput().size()); + } + } +} diff --git a/flowvault/src/test/java/com/skyflow/detect/internal/ReidentifyFileValidationsTests.java b/flowvault/src/test/java/com/skyflow/detect/internal/ReidentifyFileValidationsTests.java new file mode 100644 index 00000000..5d1cc3fb --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/detect/internal/ReidentifyFileValidationsTests.java @@ -0,0 +1,85 @@ +package com.skyflow.detect.internal; + +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.EntityType; +import com.skyflow.detect.RedactionLevel; +import com.skyflow.detect.RedactionType; +import com.skyflow.detect.ReidentifyFileRequest; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +public class ReidentifyFileValidationsTests { + + private static ReidentifyFileRequest.ReidentifyFileRequestBuilder valid() { + return ReidentifyFileRequest.builder().dataSource(DataSourceType.BASE64).value("Zm9v"); + } + + private static void assertRejected(ReidentifyFileRequest request, ErrorMessage expected) { + try { + ReidentifyFileValidations.validateReidentifyFileRequest(request); + Assert.fail("expected SkyflowException: " + expected); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(expected.getMessage(), e.getMessage()); + } + } + + @Test + public void rejectsNullRequest() { + assertRejected(null, ErrorMessage.ReidentifyFileRequestNull); + } + + @Test + public void rejectsMissingDataSource() { + assertRejected(ReidentifyFileRequest.builder().value("Zm9v").build(), ErrorMessage.InvalidDataSourceInReidentifyFile); + } + + @Test + public void rejectsNullOrBlankValue() { + assertRejected(ReidentifyFileRequest.builder().dataSource(DataSourceType.BASE64).build(), ErrorMessage.InvalidValueInReidentifyFile); + assertRejected(valid().value("").build(), ErrorMessage.InvalidValueInReidentifyFile); + assertRejected(valid().value(" \t").build(), ErrorMessage.InvalidValueInReidentifyFile); + } + + @Test + public void checksFieldsInOrderDataSourceBeforeValue() { + // Both missing: dataSource is reported first. + assertRejected(ReidentifyFileRequest.builder().build(), ErrorMessage.InvalidDataSourceInReidentifyFile); + } + + @Test + public void acceptsMinimalAndFullValidRequests() throws SkyflowException { + ReidentifyFileValidations.validateReidentifyFileRequest(valid().build()); + ReidentifyFileValidations.validateReidentifyFileRequest(valid() + .dataSource(DataSourceType.PRESIGNED_URL).value("https://example.com/f") + .redactionLevel(Arrays.asList( + RedactionLevel.builder().tokenGroupName("g").redactionPattern("p").build(), + RedactionLevel.builder().entityName(EntityType.NAME).redactionType(RedactionType.REDACTED).build(), + RedactionLevel.builder().tokenGroupName("g2").redactionType(RedactionType.PLAIN_TEXT).build(), + RedactionLevel.builder().entityName(EntityType.SSN).redactionPattern("p2").build())) + .build()); + } + + @Test + public void nullOrEmptyRedactionLevelListIsAllowed() throws SkyflowException { + ReidentifyFileValidations.validateReidentifyFileRequest(valid().redactionLevel(null).build()); + ReidentifyFileValidations.validateReidentifyFileRequest(valid().redactionLevel(Collections.emptyList()).build()); + } + + @Test + public void anyRedactionLevelShapePassesThrough() throws SkyflowException { + // Every RedactionLevel field is optional on the API; the server applies its own rules. + ReidentifyFileValidations.validateReidentifyFileRequest(valid().redactionLevel(Arrays.asList( + null, + RedactionLevel.builder().build(), + RedactionLevel.builder().tokenGroupName("g").build(), + RedactionLevel.builder().tokenGroupName("g").entityName(EntityType.NAME) + .redactionPattern("p").redactionType(RedactionType.MASKED).build())).build()); + } +} diff --git a/flowvault/src/test/java/com/skyflow/utils/validations/ValidationsTests.java b/flowvault/src/test/java/com/skyflow/utils/validations/ValidationsTests.java index f900c03a..fb40b63c 100644 --- a/flowvault/src/test/java/com/skyflow/utils/validations/ValidationsTests.java +++ b/flowvault/src/test/java/com/skyflow/utils/validations/ValidationsTests.java @@ -15,6 +15,7 @@ import com.skyflow.config.VaultConfig; import com.skyflow.enums.Env; import com.skyflow.enums.UpdateType; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.utils.Constants; @@ -232,7 +233,7 @@ public void testValidateVaultConfiguration_nullRequest() { Validations.validateVaultConfiguration(null); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.NullVaultConfig.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.NullVaultConfig.getMessage(), e.getMessage()); } } @@ -582,7 +583,7 @@ public void testValidateInsertRequest_emptyTokensMapThrows() { Validations.validateInsertRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.EmptyTokens.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.EmptyTokens.getMessage(), e.getMessage()); } } @@ -597,7 +598,7 @@ public void testValidateInsertRequest_nullKeyInTokensThrows() { Validations.validateInsertRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.EmptyKeyInTokens.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.EmptyKeyInTokens.getMessage(), e.getMessage()); } } @@ -612,7 +613,7 @@ public void testValidateInsertRequest_blankKeyInTokensThrows() { Validations.validateInsertRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.EmptyKeyInTokens.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.EmptyKeyInTokens.getMessage(), e.getMessage()); } } @@ -1610,7 +1611,7 @@ public void testValidateDeleteRequest_nullRequestThrows() { Validations.validateDeleteRequest(null); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.DeleteRequestNull.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.DeleteRequestNull.getMessage(), e.getMessage()); } } @@ -1621,7 +1622,7 @@ public void testValidateDeleteRequest_nullTableThrows() { Validations.validateDeleteRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.TableKeyError.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.TableKeyError.getMessage(), e.getMessage()); } } @@ -1632,7 +1633,7 @@ public void testValidateDeleteRequest_blankTableThrows() { Validations.validateDeleteRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.EmptyTable.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.EmptyTable.getMessage(), e.getMessage()); } } @@ -1671,7 +1672,7 @@ public void testValidateDeleteRequest_blankIdInIdsThrows() { Validations.validateDeleteRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.EmptyIdInIds.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.EmptyIdInIds.getMessage(), e.getMessage()); } } @@ -1722,7 +1723,7 @@ public void testValidateUpdateRequest_nullRequestThrows() { Validations.validateUpdateRequest(null); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.UpdateRequestNull.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.UpdateRequestNull.getMessage(), e.getMessage()); } } @@ -1734,7 +1735,7 @@ public void testValidateUpdateRequest_nullTableNameThrows() { Validations.validateUpdateRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.TableKeyError.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.TableKeyError.getMessage(), e.getMessage()); } } @@ -1746,7 +1747,7 @@ public void testValidateUpdateRequest_blankTableNameThrows() { Validations.validateUpdateRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.EmptyTable.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.EmptyTable.getMessage(), e.getMessage()); } } @@ -1845,7 +1846,7 @@ public void testValidateUpdateRequest_emptyTokensMapThrows() { Validations.validateUpdateRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.EmptyTokens.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.EmptyTokens.getMessage(), e.getMessage()); } } @@ -1859,7 +1860,7 @@ public void testValidateUpdateRequest_blankKeyInTokensThrows() { Validations.validateUpdateRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.EmptyKeyInTokens.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.EmptyKeyInTokens.getMessage(), e.getMessage()); } } @@ -1917,7 +1918,7 @@ public void testValidateGetRequest_nullRequestThrows() { Validations.validateGetRequest(null); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.GetRequestNull.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.GetRequestNull.getMessage(), e.getMessage()); } } @@ -1957,7 +1958,7 @@ public void testValidateGetRequest_nullTableThrows() { Validations.validateGetRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.TableKeyError.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.TableKeyError.getMessage(), e.getMessage()); } } @@ -1968,7 +1969,7 @@ public void testValidateGetRequest_blankTableThrows() { Validations.validateGetRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.EmptyTable.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.EmptyTable.getMessage(), e.getMessage()); } } @@ -2010,7 +2011,7 @@ public void testValidateGetRequest_blankIdInIdsThrows() { Validations.validateGetRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.EmptyIdInIds.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.EmptyIdInIds.getMessage(), e.getMessage()); } } @@ -2039,7 +2040,7 @@ public void testValidateGetRequest_emptyFieldsThrows() { Validations.validateGetRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.EmptyFields.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.EmptyFields.getMessage(), e.getMessage()); } } @@ -2054,7 +2055,7 @@ public void testValidateGetRequest_blankFieldInFieldsThrows() { Validations.validateGetRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.EmptyFieldInFields.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.EmptyFieldInFields.getMessage(), e.getMessage()); } } @@ -2163,7 +2164,7 @@ public void testValidateGetRequest_multiTableRecordMissingTableThrows() { Validations.validateGetRequest(request); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { - Assert.assertEquals(ErrorMessage.TableKeyError.getMessage(), e.getMessage()); + Assert.assertEquals(BaseErrorMessage.TableKeyError.getMessage(), e.getMessage()); } } } diff --git a/flowvault/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java b/flowvault/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java new file mode 100644 index 00000000..07f6e2f1 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java @@ -0,0 +1,622 @@ +package com.skyflow.vault.controller; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.detect.CheckGuardrailsRequest; +import com.skyflow.detect.CheckGuardrailsResponse; +import com.skyflow.detect.DataSourceType; +import com.skyflow.detect.DeidentificationType; +import com.skyflow.detect.DeidentifyFileRequest; +import com.skyflow.detect.DeidentifyFileResponse; +import com.skyflow.detect.DeidentifyStringRequest; +import com.skyflow.detect.DeidentifyStringResponse; +import com.skyflow.detect.Detect; +import com.skyflow.detect.DetectConfiguration; +import com.skyflow.detect.DetectRunStatus; +import com.skyflow.detect.Entity; +import com.skyflow.detect.EntityType; +import com.skyflow.detect.FileDataFormat; +import com.skyflow.detect.GetRunRequest; +import com.skyflow.detect.GetRunResponse; +import com.skyflow.detect.GuardrailsValidation; +import com.skyflow.detect.PollOptions; +import com.skyflow.detect.RedactionLevel; +import com.skyflow.detect.ReidentifyFileRequest; +import com.skyflow.detect.ReidentifyFileResponse; +import com.skyflow.detect.ReidentifyStringRequest; +import com.skyflow.detect.ReidentifyStringResponse; +import com.skyflow.enums.Env; +import com.skyflow.errors.BaseErrorMessage; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.SkyflowException; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.nio.file.Files; +import java.util.Base64; + +import java.util.Arrays; +import java.util.Collections; + +public class DetectControllerTests { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private static VaultConfig vaultConfig(String vaultId) { + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultId); + config.setClusterId("cluster123"); + config.setEnv(Env.PROD); + return config; + } + + @Test + public void detectAccessorResolvesConfiguredVaults() throws SkyflowException { + Skyflow client = Skyflow.builder() + .addVaultConfig(vaultConfig("vault-a")) + .addVaultConfig(vaultConfig("vault-b")) + .build(); + Assert.assertNotNull(client.detect()); + Assert.assertSame(client.detect(), client.detect("vault-a")); + Assert.assertNotSame(client.detect("vault-a"), client.detect("vault-b")); + try { + client.detect("vault-c"); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(BaseErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + } + } + + @Test + public void detectAccessorFollowsVaultConfigRemoval() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(vaultConfig("vault-a")).build(); + client.removeVaultConfig("vault-a"); + try { + client.detect(); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(BaseErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + } + } + + @Test + public void deidentifyStringValidatesBeforeResolvingCredentials() throws SkyflowException { + DetectController controller = new DetectController(vaultConfig("vault-a"), (Credentials) null); + try { + controller.deidentifyString(DeidentifyStringRequest.builder().text(" ").configurationId("cfg").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(BaseErrorMessage.InvalidTextInDeidentifyString.getMessage(), e.getMessage()); + } + } + + @Test + public void reidentifyStringValidatesBeforeResolvingCredentials() throws SkyflowException { + DetectController controller = new DetectController(vaultConfig("vault-a"), (Credentials) null); + try { + controller.reidentifyString(ReidentifyStringRequest.builder().text(" ").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(BaseErrorMessage.InvalidTextInReidentifyString.getMessage(), e.getMessage()); + } + } + + @Test + public void missingCredentialsSurfaceAsSkyflowExceptionWithoutNetworkCall() throws SkyflowException { + DetectController controller = new DetectController(vaultConfig("vault-a"), (Credentials) null); + try { + controller.reidentifyString(ReidentifyStringRequest.builder().text("[NAME_1]").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(BaseErrorMessage.EmptyCredentials.getMessage(), e.getMessage()); + } + } + + // ─── reidentifyFile (flowvault only) ────────────────────────────────────── + + /** API-key credentials resolve without a token exchange, so the only network call is the Detect request. */ + private static Credentials apiKeyCredentials() { + Credentials credentials = new Credentials(); + credentials.setApiKey("sky-abcde-0123456789abcdef0123456789abcdef"); + return credentials; + } + + private static DetectController controllerFor(MockWebServer server) { + VaultConfig config = vaultConfig("vault-1"); + config.setVaultUrl(server.url("").toString()); + DetectController controller = new DetectController(config, apiKeyCredentials()); + controller.setCommonHttpConfig(null, 0, null, null); + return controller; + } + + @Test + public void reidentifyFileValidatesBeforeResolvingCredentials() throws SkyflowException { + DetectController controller = new DetectController(vaultConfig("vault-a"), (Credentials) null); + ReidentifyFileRequest[] bad = { + null, + ReidentifyFileRequest.builder().value("Zm9v").build(), + ReidentifyFileRequest.builder().dataSource(DataSourceType.BASE64).value(" ").build(), + }; + ErrorMessage[] expected = { + ErrorMessage.ReidentifyFileRequestNull, + ErrorMessage.InvalidDataSourceInReidentifyFile, + ErrorMessage.InvalidValueInReidentifyFile, + }; + for (int i = 0; i < bad.length; i++) { + try { + controller.reidentifyFile(bad[i]); + Assert.fail("expected SkyflowException for case " + i); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(expected[i].getMessage(), e.getMessage()); + } + } + } + + @Test + public void reidentifyFileMissingCredentialsSurfaceWithoutNetworkCall() throws SkyflowException { + DetectController controller = new DetectController(vaultConfig("vault-a"), (Credentials) null); + try { + controller.reidentifyFile(ReidentifyFileRequest.builder() + .dataSource(DataSourceType.BASE64).value("Zm9v").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(BaseErrorMessage.EmptyCredentials.getMessage(), e.getMessage()); + } + } + + @Test + public void reidentifyFileSendsScopedRequestAndMapsResponse() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(200).setHeader("Content-Type", "application/json") + .setBody("{\"status\":\"SUCCESS\",\"outputType\":\"BASE64\"," + + "\"output\":[{\"processedFile\":\"SGVsbG8=\",\"processedFileType\":\"REIDENTIFIED_FILE\"," + + "\"processedFileExtension\":\"txt\"}]," + + "\"metrics\":{\"size\":0.5,\"wordCount\":3,\"characterCount\":12}}")); + + ReidentifyFileResponse response = controllerFor(server).reidentifyFile(ReidentifyFileRequest.builder() + .dataSource(DataSourceType.BASE64) + .value("W05BTUVfMV0=") + .dataFormat(FileDataFormat.TXT) + .redactionLevel(Arrays.asList( + RedactionLevel.builder().tokenGroupName("names").redactionPattern("mask").build())) + .build()); + + Assert.assertEquals(DetectRunStatus.SUCCESS, response.getStatus()); + Assert.assertEquals(DataSourceType.BASE64, response.getOutputType()); + Assert.assertEquals(1, response.getOutput().size()); + Assert.assertEquals("SGVsbG8=", response.getOutput().get(0).getProcessedFile()); + Assert.assertEquals("REIDENTIFIED_FILE", response.getOutput().get(0).getProcessedFileType()); + Assert.assertEquals(FileDataFormat.TXT, response.getOutput().get(0).getProcessedFileExtension()); + Assert.assertEquals(Integer.valueOf(3), response.getMetrics().getWordCount()); + + RecordedRequest recorded = server.takeRequest(); + Assert.assertEquals("POST", recorded.getMethod()); + Assert.assertEquals("/v2/detect/reidentify/file", recorded.getPath()); + Assert.assertEquals("Bearer sky-abcde-0123456789abcdef0123456789abcdef", recorded.getHeader("Authorization")); + Assert.assertNotNull(recorded.getHeader("sky-metadata")); + String body = recorded.getBody().readUtf8(); + Assert.assertTrue(body, body.contains("\"dataSource\":\"BASE64\"")); + Assert.assertTrue(body, body.contains("\"value\":\"W05BTUVfMV0=\"")); + Assert.assertTrue(body, body.contains("\"vaultId\":\"vault-1\"")); + Assert.assertTrue(body, body.contains("\"dataFormat\":\"txt\"")); + Assert.assertTrue(body, body.contains("\"tokenGroupName\":\"names\"")); + Assert.assertTrue(body, body.contains("\"redactionPattern\":\"mask\"")); + } finally { + server.shutdown(); + } + } + + @Test + public void reidentifyFileToleratesSparseAndUnknownResponseValues() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"status\":\"SOMETHING_NEW\"}")); + ReidentifyFileResponse response = controllerFor(server).reidentifyFile(ReidentifyFileRequest.builder() + .dataSource(DataSourceType.SKYFLOW_ID).value("rec-1").build()); + Assert.assertEquals(DetectRunStatus.UNKNOWN, response.getStatus()); + Assert.assertNull(response.getOutputType()); + Assert.assertTrue(response.getOutput().isEmpty()); + Assert.assertNull(response.getMetrics()); + String body = server.takeRequest().getBody().readUtf8(); + Assert.assertFalse(body, body.contains("dataFormat")); + Assert.assertFalse(body, body.contains("redactionLevel")); + } finally { + server.shutdown(); + } + } + + @Test + public void reidentifyFileMapsApiErrors() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(400).setHeader("x-request-id", "req-9") + .setBody("{\"error\":{\"grpc_code\":3,\"http_code\":400,\"message\":\"unsupported format\"," + + "\"http_status\":\"Bad Request\",\"details\":[]}}")); + try { + controllerFor(server).reidentifyFile(ReidentifyFileRequest.builder() + .dataSource(DataSourceType.PRESIGNED_URL).value("https://example.com/f").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(400, e.getHttpCode()); + Assert.assertEquals("unsupported format", e.getMessage()); + Assert.assertEquals("req-9", e.getRequestId()); + Assert.assertEquals(Integer.valueOf(3), e.getGrpcCode()); + } + } finally { + server.shutdown(); + } + } + + // ─── inherited Detect V2 methods, exercised through the flowvault controller ── + // These prove the flowvault wiring (vault URL from VaultConfig, API-key credentials, + // flowvault SDK metrics header) end to end, not just the shared base logic. + + private static final String API_KEY = "sky-abcde-0123456789abcdef0123456789abcdef"; + + private static void assertCommonHeaders(RecordedRequest recorded, String method, String path) { + Assert.assertEquals(method, recorded.getMethod()); + Assert.assertEquals(path, recorded.getPath()); + Assert.assertEquals("Bearer " + API_KEY, recorded.getHeader("Authorization")); + String metrics = recorded.getHeader("sky-metadata"); + Assert.assertNotNull(metrics); + Assert.assertTrue(metrics, metrics.contains("skyflow-flowvault-java@")); + } + + @Test + public void deidentifyStringRoundTripWithStoredConfiguration() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(200).setHeader("Content-Type", "application/json") + .setBody("{\"processedText\":\"My name is [NAME_1].\"," + + "\"entities\":[{\"token\":\"NAME_1\",\"value\":\"John Doe\",\"entityType\":\"NAME\"," + + "\"location\":{\"startIndex\":11,\"endIndex\":19,\"startIndexProcessed\":11,\"endIndexProcessed\":19}," + + "\"entityScores\":{\"NAME\":0.98}}]," + + "\"metrics\":{\"wordCount\":4,\"characterCount\":20}}")); + + DeidentifyStringResponse response = controllerFor(server).deidentifyString(DeidentifyStringRequest.builder() + .text("My name is John Doe.").configurationId("cfg-123").build()); + + Assert.assertEquals("My name is [NAME_1].", response.getProcessedText()); + Assert.assertEquals(1, response.getEntities().size()); + Assert.assertEquals("NAME_1", response.getEntities().get(0).getToken()); + Assert.assertEquals("John Doe", response.getEntities().get(0).getValue()); + Assert.assertEquals("NAME", response.getEntities().get(0).getEntityType()); + Assert.assertEquals(Integer.valueOf(11), response.getEntities().get(0).getLocation().getStartIndex()); + Assert.assertEquals(Integer.valueOf(19), response.getEntities().get(0).getLocation().getEndIndexProcessed()); + Assert.assertEquals(0.98, response.getEntities().get(0).getEntityScores().get("NAME"), 1e-9); + Assert.assertEquals(Integer.valueOf(4), response.getMetrics().getWordCount()); + + RecordedRequest recorded = server.takeRequest(); + assertCommonHeaders(recorded, "POST", "/v2/detect/deidentify/string"); + String body = recorded.getBody().readUtf8(); + Assert.assertTrue(body, body.contains("\"text\":\"My name is John Doe.\"")); + Assert.assertTrue(body, body.contains("\"configurationId\":\"cfg-123\"")); + Assert.assertFalse(body, body.contains("\"configuration\":")); + } finally { + server.shutdown(); + } + } + + @Test + public void deidentifyStringRoundTripWithInlineConfigurationScopedToVault() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"processedText\":\"[NAME_1]\"}")); + + DeidentifyStringResponse response = controllerFor(server).deidentifyString(DeidentifyStringRequest.builder() + .text("John") + .configuration(DetectConfiguration.builder() + .detect(Detect.builder().entities(Collections.singletonList( + Entity.builder().entityType(EntityType.NAME) + .deidentificationType(DeidentificationType.VAULT_TOKEN) + .destination("names").build())).build()) + .build()) + .build()); + + Assert.assertEquals("[NAME_1]", response.getProcessedText()); + Assert.assertTrue(response.getEntities().isEmpty()); + Assert.assertNull(response.getMetrics()); + + String body = server.takeRequest().getBody().readUtf8(); + Assert.assertTrue(body, body.contains("\"configuration\":{")); + Assert.assertTrue(body, body.contains("\"vaultId\":\"vault-1\"")); // injected by the controller + Assert.assertTrue(body, body.contains("\"entityType\":\"NAME\"")); + Assert.assertTrue(body, body.contains("\"deidentificationType\":\"VAULT_TOKEN\"")); + Assert.assertTrue(body, body.contains("\"destination\":\"names\"")); + Assert.assertFalse(body, body.contains("configurationId")); + } finally { + server.shutdown(); + } + } + + @Test + public void reidentifyStringRoundTrip() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(200) + .setBody("{\"processedText\":\"My name is J**n D*e.\",\"metrics\":{\"wordCount\":5}}")); + + ReidentifyStringResponse response = controllerFor(server).reidentifyString(ReidentifyStringRequest.builder() + .text("My name is [NAME_1].") + .redactionLevel(Collections.singletonList( + RedactionLevel.builder().tokenGroupName("names").redactionPattern("mask").build())) + .build()); + + Assert.assertEquals("My name is J**n D*e.", response.getProcessedText()); + Assert.assertEquals(Integer.valueOf(5), response.getMetrics().getWordCount()); + + RecordedRequest recorded = server.takeRequest(); + assertCommonHeaders(recorded, "POST", "/v2/detect/reidentify/string"); + String body = recorded.getBody().readUtf8(); + Assert.assertTrue(body, body.contains("\"text\":\"My name is [NAME_1].\"")); + Assert.assertTrue(body, body.contains("\"vaultId\":\"vault-1\"")); + Assert.assertTrue(body, body.contains("\"tokenGroupName\":\"names\"")); + Assert.assertTrue(body, body.contains("\"redactionPattern\":\"mask\"")); + } finally { + server.shutdown(); + } + } + + @Test + public void getRunRoundTripQueriesByIdScopedToVault() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(200) + .setBody("{\"status\":\"SUCCESS\",\"outputType\":\"BASE64\"," + + "\"output\":[{\"processedFile\":\"UERG\",\"processedFileType\":\"REDACTED_FILE\",\"processedFileExtension\":\"pdf\"}]," + + "\"metrics\":{\"pages\":3}}")); + server.enqueue(new MockResponse().setResponseCode(200) + .setBody("{\"status\":\"FAILED\",\"message\":\"unsupported file\"}")); + + DetectController controller = controllerFor(server); + GetRunResponse done = controller.getRun(GetRunRequest.builder().runId(" run-42 ").build()); + Assert.assertEquals("run-42", done.getRunId()); + Assert.assertEquals(DetectRunStatus.SUCCESS, done.getStatus()); + Assert.assertEquals(DataSourceType.BASE64, done.getOutputType()); + Assert.assertEquals("UERG", done.getOutput().get(0).getProcessedFile()); + Assert.assertEquals("REDACTED_FILE", done.getOutput().get(0).getProcessedFileType()); + Assert.assertEquals(FileDataFormat.PDF, done.getOutput().get(0).getProcessedFileExtension()); + Assert.assertEquals(Integer.valueOf(3), done.getMetrics().getPages()); + Assert.assertNull(done.getMessage()); + + RecordedRequest recorded = server.takeRequest(); + assertCommonHeaders(recorded, "GET", "/v2/detect/runs/run-42?vaultId=vault-1"); + Assert.assertEquals(0L, recorded.getBodySize()); + + GetRunResponse failed = controller.getRun(GetRunRequest.builder().runId("run-43").build()); + Assert.assertEquals(DetectRunStatus.FAILED, failed.getStatus()); + Assert.assertEquals("unsupported file", failed.getMessage()); + Assert.assertTrue(failed.getOutput().isEmpty()); + Assert.assertNull(failed.getOutputType()); + Assert.assertEquals("/v2/detect/runs/run-43?vaultId=vault-1", server.takeRequest().getPath()); + } finally { + server.shutdown(); + } + } + + @Test + public void checkGuardrailsRoundTrip() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(200) + .setBody("{\"text\":\"tell me about politics\",\"validation\":\"FAILED\",\"toxic\":false,\"deniedTopic\":true}")); + server.enqueue(new MockResponse().setResponseCode(200) + .setBody("{\"text\":\"hello\",\"validation\":\"PASSED\"}")); + + DetectController controller = controllerFor(server); + CheckGuardrailsResponse blocked = controller.checkGuardrails(CheckGuardrailsRequest.builder() + .text("tell me about politics").checkToxicity(true).denyTopics(Arrays.asList("politics", "religion")).build()); + Assert.assertEquals("tell me about politics", blocked.getText()); + Assert.assertEquals(GuardrailsValidation.FAILED, blocked.getValidation()); + Assert.assertEquals(Boolean.FALSE, blocked.getToxic()); + Assert.assertEquals(Boolean.TRUE, blocked.getDeniedTopic()); + + RecordedRequest recorded = server.takeRequest(); + assertCommonHeaders(recorded, "POST", "/v2/detect/guardrails"); + String body = recorded.getBody().readUtf8(); + Assert.assertTrue(body, body.contains("\"text\":\"tell me about politics\"")); + Assert.assertTrue(body, body.contains("\"vaultId\":\"vault-1\"")); + Assert.assertTrue(body, body.contains("\"checkToxicity\":true")); + Assert.assertTrue(body, body.contains("\"denyTopics\":[\"politics\",\"religion\"]")); + + CheckGuardrailsResponse passed = controller.checkGuardrails(CheckGuardrailsRequest.builder().text("hello").build()); + Assert.assertEquals(GuardrailsValidation.PASSED, passed.getValidation()); + Assert.assertNull(passed.getToxic()); + Assert.assertNull(passed.getDeniedTopic()); + String minimal = server.takeRequest().getBody().readUtf8(); + Assert.assertFalse(minimal, minimal.contains("checkToxicity")); + Assert.assertFalse(minimal, minimal.contains("denyTopics")); + } finally { + server.shutdown(); + } + } + + @Test + public void apiErrorsOnInheritedMethodsMapToSkyflowException() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(404).setHeader("x-request-id", "req-404") + .setBody("{\"error\":{\"grpc_code\":5,\"http_code\":404,\"message\":\"run not found\",\"http_status\":\"Not Found\",\"details\":[]}}")); + try { + controllerFor(server).getRun(GetRunRequest.builder().runId("missing").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(404, e.getHttpCode()); + Assert.assertEquals("run not found", e.getMessage()); + Assert.assertEquals("req-404", e.getRequestId()); + Assert.assertEquals(Integer.valueOf(5), e.getGrpcCode()); + } + } finally { + server.shutdown(); + } + } + + // ─── deidentifyFile (flowvault only) ────────────────────────────────────── + + @Test + public void deidentifyFileValidatesBeforeResolvingCredentials() throws SkyflowException { + DetectController controller = new DetectController(vaultConfig("vault-a"), (Credentials) null); + try { + controller.deidentifyFile(DeidentifyFileRequest.builder().value("Zm9v").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(BaseErrorMessage.InvalidDataSourceInDeidentifyFile.getMessage(), e.getMessage()); + } + } + + @Test + public void deidentifyFileSubmitOnlyReturnsRunIdWithoutPolling() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"runId\":\"run-42\"}")); + + DeidentifyFileResponse response = controllerFor(server).deidentifyFile(DeidentifyFileRequest.builder() + .dataSource(DataSourceType.SKYFLOW_ID).value("rec-1").dataFormat(FileDataFormat.PDF) + .configurationId("cfg-1").build()); + + Assert.assertEquals("run-42", response.getRunId()); + Assert.assertNull(response.getStatus()); + Assert.assertTrue(response.getOutput().isEmpty()); + + RecordedRequest recorded = server.takeRequest(); + assertCommonHeaders(recorded, "POST", "/v2/detect/deidentify/file"); + String body = recorded.getBody().readUtf8(); + Assert.assertTrue(body, body.contains("\"dataSource\":\"SKYFLOW_ID\"")); + Assert.assertTrue(body, body.contains("\"value\":\"rec-1\"")); + Assert.assertTrue(body, body.contains("\"dataFormat\":\"pdf\"")); + Assert.assertTrue(body, body.contains("\"configurationId\":\"cfg-1\"")); + Assert.assertEquals("only the submit call was made", 1, server.getRequestCount()); + } finally { + server.shutdown(); + } + } + + @Test + public void deidentifyFilePollsToSuccessAndWritesOutputsForLocalFile() throws Exception { + File input = folder.newFile("invoice.txt"); + Files.write(input.toPath(), "Pay John Doe".getBytes("UTF-8")); + File outDir = folder.newFolder("out"); + String redacted = Base64.getEncoder().encodeToString("Pay [NAME_1]".getBytes("UTF-8")); + + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"runId\":\"run-7\"}")); + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"status\":\"QUEUED\"}")); + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"status\":\"SUCCESS\",\"outputType\":\"BASE64\"," + + "\"output\":[{\"processedFile\":\"" + redacted + "\",\"processedFileType\":\"REDACTED_FILE\",\"processedFileExtension\":\"txt\"}," + + "{\"processedFile\":\"W10=\",\"processedFileType\":\"ENTITIES\",\"processedFileExtension\":\"json\"}]," + + "\"metrics\":{\"wordCount\":3}}")); + + DeidentifyFileResponse response = controllerFor(server).deidentifyFile(DeidentifyFileRequest.builder() + .file(input).configurationId("cfg-1") + .pollOptions(PollOptions.builder().waitTime(1).build()) // one real 1 s sleep at most + .outputDirectory(outDir.getPath()) + .build()); + + Assert.assertEquals("run-7", response.getRunId()); + Assert.assertEquals(DetectRunStatus.SUCCESS, response.getStatus()); + Assert.assertEquals(DataSourceType.BASE64, response.getOutputType()); + Assert.assertEquals(2, response.getOutput().size()); + Assert.assertEquals(Integer.valueOf(3), response.getMetrics().getWordCount()); + Assert.assertEquals("Pay [NAME_1]", new String(Files.readAllBytes(new File(outDir, "processed-invoice.txt").toPath()), "UTF-8")); + Assert.assertEquals("[]", new String(Files.readAllBytes(new File(outDir, "processed-entities-invoice.json").toPath()), "UTF-8")); + + RecordedRequest submit = server.takeRequest(); + assertCommonHeaders(submit, "POST", "/v2/detect/deidentify/file"); + String body = submit.getBody().readUtf8(); + Assert.assertTrue(body, body.contains("\"dataSource\":\"BASE64\"")); + Assert.assertTrue(body, body.contains("\"value\":\"" + Base64.getEncoder().encodeToString("Pay John Doe".getBytes("UTF-8")) + "\"")); + Assert.assertTrue(body, body.contains("\"dataFormat\":\"txt\"")); + Assert.assertEquals("/v2/detect/runs/run-7?vaultId=vault-1", server.takeRequest().getPath()); + Assert.assertEquals("/v2/detect/runs/run-7?vaultId=vault-1", server.takeRequest().getPath()); + } finally { + server.shutdown(); + } + } + + @Test + public void deidentifyFileReturnsRunIdWithStatusWhenBudgetRunsOut() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"runId\":\"run-8\"}")); + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"status\":\"IN_PROGRESS\"}")); + + DeidentifyFileResponse response = controllerFor(server).deidentifyFile(DeidentifyFileRequest.builder() + .dataSource(DataSourceType.BASE64).value("Zm9v") + .pollOptions(PollOptions.builder().maxAttempts(1).build()).build()); + + Assert.assertEquals("run-8", response.getRunId()); + Assert.assertEquals(DetectRunStatus.IN_PROGRESS, response.getStatus()); + Assert.assertTrue(response.getOutput().isEmpty()); + Assert.assertEquals(2, server.getRequestCount()); + } finally { + server.shutdown(); + } + } + + @Test + public void deidentifyFileSurfacesFailedRunMessage() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"runId\":\"run-9\"}")); + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"status\":\"FAILED\",\"message\":\"unsupported file\"}")); + + DeidentifyFileResponse response = controllerFor(server).deidentifyFile(DeidentifyFileRequest.builder() + .dataSource(DataSourceType.BASE64).value("Zm9v").pollOptions(PollOptions.builder().build()).build()); + + Assert.assertEquals(DetectRunStatus.FAILED, response.getStatus()); + Assert.assertEquals("unsupported file", response.getMessage()); + Assert.assertEquals(2, server.getRequestCount()); + } finally { + server.shutdown(); + } + } + + @Test + public void deidentifyFileSubmitErrorMapsToSkyflowException() throws Exception { + MockWebServer server = new MockWebServer(); + server.start(); + try { + server.enqueue(new MockResponse().setResponseCode(400).setHeader("x-request-id", "req-1") + .setBody("{\"error\":{\"grpc_code\":3,\"http_code\":400,\"message\":\"configuration not found\",\"http_status\":\"Bad Request\",\"details\":[]}}")); + try { + controllerFor(server).deidentifyFile(DeidentifyFileRequest.builder() + .dataSource(DataSourceType.BASE64).value("Zm9v").configurationId("missing").build()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertEquals(400, e.getHttpCode()); + Assert.assertEquals("configuration not found", e.getMessage()); + Assert.assertEquals("req-1", e.getRequestId()); + } + } finally { + server.shutdown(); + } + } +} diff --git a/skyvault/src/main/java/com/skyflow/Skyflow.java b/skyvault/src/main/java/com/skyflow/Skyflow.java index 940f5008..53692860 100644 --- a/skyvault/src/main/java/com/skyflow/Skyflow.java +++ b/skyvault/src/main/java/com/skyflow/Skyflow.java @@ -5,10 +5,13 @@ import com.skyflow.config.VaultConfig; import com.skyflow.enums.LogLevel; import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.BaseErrorMessage; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.Constants; +import com.skyflow.utils.SdkVersion; import com.skyflow.utils.Utils; import com.skyflow.utils.logger.LogUtil; import com.skyflow.utils.validations.Validations; @@ -32,6 +35,8 @@ protected Skyflow self() { } public static SkyflowClientBuilder builder() { + // Keep common's BaseErrorMessage prefix in step with this SDK's own ErrorMessage prefix. + SdkVersion.setSdkPrefix(Constants.SDK_PREFIX); return new SkyflowClientBuilder(); } @@ -99,27 +104,27 @@ public Skyflow updateLogLevel(LogLevel logLevel) { } public VaultController vault() throws SkyflowException { - return resolveOrThrow(this.builder.vaultClientsMap, null, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + return resolveOrThrow(this.builder.vaultClientsMap, null, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, BaseErrorMessage.VaultIdNotInConfigList); } public VaultController vault(String vaultId) throws SkyflowException { - return resolveOrThrow(this.builder.vaultClientsMap, vaultId, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + return resolveOrThrow(this.builder.vaultClientsMap, vaultId, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, BaseErrorMessage.VaultIdNotInConfigList); } public ConnectionController connection() throws SkyflowException { - return resolveOrThrow(this.builder.connectionsMap, null, ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST, ErrorMessage.ConnectionIdNotInConfigList); + return resolveOrThrow(this.builder.connectionsMap, null, ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST, BaseErrorMessage.ConnectionIdNotInConfigList); } public ConnectionController connection(String connectionId) throws SkyflowException { - return resolveOrThrow(this.builder.connectionsMap, connectionId, ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST, ErrorMessage.ConnectionIdNotInConfigList); + return resolveOrThrow(this.builder.connectionsMap, connectionId, ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST, BaseErrorMessage.ConnectionIdNotInConfigList); } public DetectController detect() throws SkyflowException { - return resolveOrThrow(this.builder.detectClientsMap, null, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + return resolveOrThrow(this.builder.detectClientsMap, null, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, BaseErrorMessage.VaultIdNotInConfigList); } public DetectController detect(String vaultId) throws SkyflowException { - return resolveOrThrow(this.builder.detectClientsMap, vaultId, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + return resolveOrThrow(this.builder.detectClientsMap, vaultId, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, BaseErrorMessage.VaultIdNotInConfigList); } public static final class SkyflowClientBuilder extends BaseSkyflowClientBuilder { From 1fa3589db8f4a26359dc9cf23b2add9218b12289 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 8 Oct 2026 18:42:49 +0530 Subject: [PATCH 2/5] SK-3175 detect v2 add tests --- .../internal/DeidentifyFileFlowTests.java | 36 +++- .../internal/FileOutputWriterTests.java | 155 ++++++++++++++++++ 2 files changed, 187 insertions(+), 4 deletions(-) diff --git a/common/src/test/java/com/skyflow/detect/internal/DeidentifyFileFlowTests.java b/common/src/test/java/com/skyflow/detect/internal/DeidentifyFileFlowTests.java index 9afa58bc..a55d5078 100644 --- a/common/src/test/java/com/skyflow/detect/internal/DeidentifyFileFlowTests.java +++ b/common/src/test/java/com/skyflow/detect/internal/DeidentifyFileFlowTests.java @@ -17,6 +17,7 @@ import java.io.File; import java.nio.file.Files; +import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.concurrent.atomic.AtomicInteger; @@ -29,9 +30,16 @@ public class DeidentifyFileFlowTests { private static final RunPoller.Sleeper NO_SLEEP = millis -> { }; private static GetRunResponse success() { - return new GetRunResponse("run-1", DetectRunStatus.SUCCESS, DataSourceType.BASE64, - Collections.singletonList(new FileOutput(Base64.getEncoder().encodeToString("redacted".getBytes()), - "REDACTED_FILE", FileDataFormat.TXT)), + return success(DataSourceType.BASE64); + } + + private static GetRunResponse success(DataSourceType outputType) { + return new GetRunResponse("run-1", DetectRunStatus.SUCCESS, outputType, + Arrays.asList( + new FileOutput(Base64.getEncoder().encodeToString("redacted".getBytes()), + "REDACTED_FILE", FileDataFormat.TXT), + new FileOutput(Base64.getEncoder().encodeToString("[]".getBytes()), + "ENTITIES", FileDataFormat.JSON)), new Metrics(null, 1, 8, null, null, null), null); } @@ -66,7 +74,7 @@ public void polledRawRequestMergesRunButWritesNothing() throws Exception { .pollOptions(PollOptions.builder().build()).outputDirectory(folder.getRoot().getPath()).build(), "run-1", id -> success(), NO_SLEEP); Assert.assertEquals(DetectRunStatus.SUCCESS, response.getStatus()); - Assert.assertEquals(1, response.getOutput().size()); + Assert.assertEquals(2, response.getOutput().size()); Assert.assertEquals(Integer.valueOf(8), response.getMetrics().getCharacterCount()); Assert.assertEquals(0, folder.getRoot().list().length); } @@ -86,6 +94,26 @@ public void polledFileRequestWritesOutputsOnSuccess() throws Exception { File written = new File(outDir, "processed-notes.txt"); Assert.assertTrue(written.isFile()); Assert.assertEquals("redacted", new String(Files.readAllBytes(written.toPath()))); + File entities = new File(outDir, "processed-entities-notes.json"); + Assert.assertTrue(entities.isFile()); + Assert.assertEquals("[]", new String(Files.readAllBytes(entities.toPath()))); + Assert.assertEquals(2, outDir.list().length); + } + + @Test + public void polledFileRequestWritesNothingForPresignedUrlOutputs() throws Exception { + File input = folder.newFile("notes.txt"); + File outDir = folder.newFolder("out"); + + DeidentifyFileResponse response = DeidentifyFileFlow.complete( + DeidentifyFileRequest.builder().file(input).pollOptions(PollOptions.builder().build()) + .outputDirectory(outDir.getPath()).build(), + "run-1", id -> success(DataSourceType.PRESIGNED_URL), NO_SLEEP); + + Assert.assertEquals(DetectRunStatus.SUCCESS, response.getStatus()); + Assert.assertEquals(DataSourceType.PRESIGNED_URL, response.getOutputType()); + Assert.assertEquals(2, response.getOutput().size()); + Assert.assertEquals(0, outDir.list().length); } @Test diff --git a/common/src/test/java/com/skyflow/detect/internal/FileOutputWriterTests.java b/common/src/test/java/com/skyflow/detect/internal/FileOutputWriterTests.java index 4672d7a0..70297c59 100644 --- a/common/src/test/java/com/skyflow/detect/internal/FileOutputWriterTests.java +++ b/common/src/test/java/com/skyflow/detect/internal/FileOutputWriterTests.java @@ -108,6 +108,161 @@ public void invalidBase64IsReportedAsWriteFailure() { } } + // ---- naming branches ------------------------------------------------------------------- + + @Test + public void nullOrEmptyTypeFallsBackToPlainName() throws Exception { + List written = FileOutputWriter.write(Arrays.asList( + new FileOutput(b64("a"), null, FileDataFormat.PDF), + new FileOutput(b64("b"), "", FileDataFormat.TXT)), + DataSourceType.BASE64, "invoice.pdf", folder.getRoot().getPath()); + Assert.assertEquals("processed-invoice.pdf", written.get(0).getName()); + Assert.assertEquals("processed-invoice.txt", written.get(1).getName()); + } + + @Test + public void nonEntityOutputWithoutExtensionGetsBin() throws Exception { + List written = FileOutputWriter.write( + Collections.singletonList(new FileOutput(b64("x"), "REDACTED_TRANSCRIPTION", null)), + DataSourceType.BASE64, "call.mp3", folder.getRoot().getPath()); + Assert.assertEquals("processed-call.bin", written.get(0).getName()); + } + + @Test + public void entityOutputHonoursAnExplicitExtension() throws Exception { + List written = FileOutputWriter.write( + Collections.singletonList(new FileOutput(b64("x"), "ENTITIES", FileDataFormat.TXT)), + DataSourceType.BASE64, "notes.txt", folder.getRoot().getPath()); + Assert.assertEquals("processed-entities-notes.txt", written.get(0).getName()); + } + + @Test + public void entityTypeMatchIsCaseInsensitive() throws Exception { + List written = FileOutputWriter.write( + Collections.singletonList(new FileOutput(b64("x"), "entities", null)), + DataSourceType.BASE64, "notes.txt", folder.getRoot().getPath()); + Assert.assertEquals("processed-entities-notes.json", written.get(0).getName()); + } + + @Test + public void duplicateEntityOutputsGetTheTypeInsertedOnCollision() throws Exception { + List written = FileOutputWriter.write(Arrays.asList( + new FileOutput(b64("a"), "ENTITIES", FileDataFormat.JSON), + new FileOutput(b64("b"), "ENTITIES", FileDataFormat.JSON)), + DataSourceType.BASE64, "scan.png", folder.getRoot().getPath()); + Assert.assertEquals("processed-entities-scan.json", written.get(0).getName()); + Assert.assertEquals("processed-entities-scan.entities.json", written.get(1).getName()); + } + + @Test + public void collisionWithoutATypeUsesARunningNumber() throws Exception { + List written = FileOutputWriter.write(Arrays.asList( + new FileOutput(b64("a"), null, FileDataFormat.TXT), + new FileOutput(b64("b"), null, FileDataFormat.TXT), + new FileOutput(b64("c"), null, FileDataFormat.TXT)), + DataSourceType.BASE64, "notes.txt", folder.getRoot().getPath()); + Assert.assertEquals("processed-notes.txt", written.get(0).getName()); + Assert.assertEquals("processed-notes.1.txt", written.get(1).getName()); + Assert.assertEquals("processed-notes.2.txt", written.get(2).getName()); + } + + @Test + public void inputNamesWithoutAnExtensionOrWithLeadingDotAreKept() throws Exception { + List plain = FileOutputWriter.write( + Collections.singletonList(new FileOutput(b64("x"), "ENTITIES", null)), + DataSourceType.BASE64, "README", folder.getRoot().getPath()); + Assert.assertEquals("processed-entities-README.json", plain.get(0).getName()); + + List dotted = FileOutputWriter.write( + Collections.singletonList(new FileOutput(b64("x"), "REDACTED_TEXT", FileDataFormat.TXT)), + DataSourceType.BASE64, ".notes", folder.getRoot().getPath()); + Assert.assertEquals("processed-.notes.txt", dotted.get(0).getName()); + } + + @Test + public void missingInputNameFallsBackToOutput() throws Exception { + List written = FileOutputWriter.write(Arrays.asList( + new FileOutput(b64("a"), "REDACTED_FILE", FileDataFormat.PDF), + new FileOutput(b64("b"), "ENTITIES", null)), + DataSourceType.BASE64, null, folder.getRoot().getPath()); + Assert.assertEquals("processed-output.pdf", written.get(0).getName()); + Assert.assertEquals("processed-entities-output.json", written.get(1).getName()); + } + + // ---- directory and output-type branches --------------------------------------------------- + + @Test + public void nullOutputTypeWritesNothing() throws Exception { + List written = FileOutputWriter.write( + Collections.singletonList(new FileOutput(b64("x"), "REDACTED_FILE", FileDataFormat.PDF)), + null, "invoice.pdf", folder.getRoot().getPath()); + Assert.assertTrue(written.isEmpty()); + Assert.assertEquals(0, folder.getRoot().list().length); + } + + @Test + public void nullElementsInTheOutputListAreSkipped() throws Exception { + List written = FileOutputWriter.write(Arrays.asList( + null, + new FileOutput(b64("x"), "REDACTED_TEXT", FileDataFormat.TXT)), + DataSourceType.BASE64, "notes.txt", folder.getRoot().getPath()); + Assert.assertEquals(1, written.size()); + Assert.assertEquals("processed-notes.txt", written.get(0).getName()); + } + + @Test + public void blankOutputDirectoryWritesToTheWorkingDirectory() throws Exception { + File expected = new File("processed-writer-cwd-test.txt"); + try { + List written = FileOutputWriter.write( + Collections.singletonList(new FileOutput(b64("x"), "REDACTED_TEXT", FileDataFormat.TXT)), + DataSourceType.BASE64, "writer-cwd-test.txt", " "); + Assert.assertEquals(expected, written.get(0)); + Assert.assertNull(written.get(0).getParentFile()); + Assert.assertTrue(expected.isFile()); + } finally { + Files.deleteIfExists(expected.toPath()); + } + } + + @Test + public void outputDirectoryThatIsAFileIsReportedAsWriteFailure() throws Exception { + File notADirectory = folder.newFile("blocker"); + try { + FileOutputWriter.write( + Collections.singletonList(new FileOutput(b64("x"), "REDACTED_TEXT", FileDataFormat.TXT)), + DataSourceType.BASE64, "notes.txt", notADirectory.getPath()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage(), e.getMessage().contains(notADirectory.getPath())); + } + } + + @Test + public void existingFilesAreOverwritten() throws Exception { + File target = new File(folder.getRoot(), "processed-notes.txt"); + Files.write(target.toPath(), "old".getBytes()); + FileOutputWriter.write( + Collections.singletonList(new FileOutput(b64("new"), "REDACTED_TEXT", FileDataFormat.TXT)), + DataSourceType.BASE64, "notes.txt", folder.getRoot().getPath()); + Assert.assertEquals("new", new String(Files.readAllBytes(target.toPath()))); + } + + @Test + public void aLaterFailureLeavesEarlierFilesOnDisk() throws Exception { + try { + FileOutputWriter.write(Arrays.asList( + new FileOutput(b64("ok"), "REDACTED_FILE", FileDataFormat.PDF), + new FileOutput("not base64!", "ENTITIES", null)), + DataSourceType.BASE64, "invoice.pdf", folder.getRoot().getPath()); + Assert.fail("expected SkyflowException"); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage(), e.getMessage().contains("processed-entities-invoice.json")); + } + Assert.assertTrue(new File(folder.getRoot(), "processed-invoice.pdf").isFile()); + Assert.assertFalse(new File(folder.getRoot(), "processed-entities-invoice.json").exists()); + } + @Test public void nameHelpers() { Assert.assertEquals("notes", FileOutputWriter.stripExtension("notes.txt")); From 1377c1f3fd57900501454663b92f16958aaaf6f8 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 8 Oct 2026 19:22:38 +0530 Subject: [PATCH 3/5] SK-3175 fix ci check --- flowvault/README.md | 4 ++-- flowvault/pom.xml | 29 +++++++++++------------------ skyvault/pom.xml | 31 ++++++++++++------------------- 3 files changed, 25 insertions(+), 39 deletions(-) diff --git a/flowvault/README.md b/flowvault/README.md index a668231c..f51dd137 100644 --- a/flowvault/README.md +++ b/flowvault/README.md @@ -1742,7 +1742,7 @@ Outputs are written only when polling reached `SUCCESS`, the request was built f ## Reidentify string -`ReidentifyStringRequest` takes the tokenised text and an optional list of `RedactionLevel` rules. For flowvault vaults each rule targets a `tokenGroupName` and applies a named `redactionPattern`. Without rules every token is replaced by its original value. +`ReidentifyStringRequest` takes the tokenized text and an optional list of `RedactionLevel` rules. For flowvault vaults each rule targets a `tokenGroupName` and applies a named `redactionPattern`. Without rules every token is replaced by its original value. ```java ReidentifyStringResponse response = skyflowClient.detect().reidentifyString( @@ -1766,7 +1766,7 @@ A `RedactionLevel` is expected to name one source (`tokenGroupName` or `entityNa `reidentifyFile` is synchronous: the re-identified file comes back in the response, not through a run id. `ReidentifyFileRequest` takes a `dataSource` (`BASE64`, `SKYFLOW_ID` or `PRESIGNED_URL`), the matching `value`, an optional `dataFormat` and the same optional `RedactionLevel` rules as `reidentifyString`. Re-identification supports text-based formats such as TXT, CSV, JSON, JSONL, XML, DOCX, XLSX and PPTX. ```java -byte[] content = Files.readAllBytes(Paths.get("tokenised.txt")); +byte[] content = Files.readAllBytes(Paths.get("tokenized.txt")); ReidentifyFileResponse response = skyflowClient.detect().reidentifyFile( ReidentifyFileRequest.builder() diff --git a/flowvault/pom.xml b/flowvault/pom.xml index 76a711d9..2221a64a 100644 --- a/flowvault/pom.xml +++ b/flowvault/pom.xml @@ -82,25 +82,18 @@ - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.6.0 - + shade-for-japicmp package diff --git a/skyvault/pom.xml b/skyvault/pom.xml index ce14cc82..23cb42d5 100644 --- a/skyvault/pom.xml +++ b/skyvault/pom.xml @@ -57,26 +57,19 @@ - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.6.0 - + shade-for-japicmp package From 495a6dcc7233782e30344cf9177c9063b92de79e Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 8 Oct 2026 19:29:34 +0530 Subject: [PATCH 4/5] SK-3175 fix ci check --- .../controller/DetectControllerTests.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/flowvault/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java b/flowvault/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java index 07f6e2f1..f21fe9f3 100644 --- a/flowvault/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java +++ b/flowvault/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java @@ -112,17 +112,28 @@ public void reidentifyStringValidatesBeforeResolvingCredentials() throws Skyflow } } + /** + * Null credentials would make the SDK fall back to SKYFLOW_CREDENTIALS from the environment or a + * .env file, which CI provides, so the outcome would depend on the machine. An explicitly empty + * {@link Credentials} is rejected by validation the same way everywhere, before any network call. + */ @Test public void missingCredentialsSurfaceAsSkyflowExceptionWithoutNetworkCall() throws SkyflowException { - DetectController controller = new DetectController(vaultConfig("vault-a"), (Credentials) null); + DetectController controller = new DetectController(vaultConfig("vault-a"), emptyCredentials()); try { controller.reidentifyString(ReidentifyStringRequest.builder().text("[NAME_1]").build()); Assert.fail("expected SkyflowException"); } catch (SkyflowException e) { - Assert.assertEquals(BaseErrorMessage.EmptyCredentials.getMessage(), e.getMessage()); + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(BaseErrorMessage.NoTokenGenerationMeansPassed.getMessage(), e.getMessage()); } } + /** Credentials with no path, string, token or API key set. */ + private static Credentials emptyCredentials() { + return new Credentials(); + } + // ─── reidentifyFile (flowvault only) ────────────────────────────────────── /** API-key credentials resolve without a token exchange, so the only network call is the Detect request. */ @@ -166,13 +177,14 @@ public void reidentifyFileValidatesBeforeResolvingCredentials() throws SkyflowEx @Test public void reidentifyFileMissingCredentialsSurfaceWithoutNetworkCall() throws SkyflowException { - DetectController controller = new DetectController(vaultConfig("vault-a"), (Credentials) null); + DetectController controller = new DetectController(vaultConfig("vault-a"), emptyCredentials()); try { controller.reidentifyFile(ReidentifyFileRequest.builder() .dataSource(DataSourceType.BASE64).value("Zm9v").build()); Assert.fail("expected SkyflowException"); } catch (SkyflowException e) { - Assert.assertEquals(BaseErrorMessage.EmptyCredentials.getMessage(), e.getMessage()); + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(BaseErrorMessage.NoTokenGenerationMeansPassed.getMessage(), e.getMessage()); } } From 932676d8d0014911044b964e14766805345275d6 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 8 Oct 2026 14:02:01 +0000 Subject: [PATCH 5/5] [AUTOMATED] Private Release 1.2.0-dev-5aa7241b --- flowvault/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flowvault/pom.xml b/flowvault/pom.xml index 2221a64a..ea28c99b 100644 --- a/flowvault/pom.xml +++ b/flowvault/pom.xml @@ -12,7 +12,7 @@ skyflow-flowvault-java - 1.2.0 + 1.2.0-dev.5aa7241b jar ${project.groupId}:${project.artifactId} Skyflow FlowVault SDK for the Java programming language