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 @@
8UTF-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.
+ *
+ *
{@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.
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> getRunV2(
+ String runId, GetRunV2Request request) {
+ return getRunV2(runId, request, null);
+ }
+
+ /**
+ * Returns the status of a detect run.
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.
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.
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.
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.
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.
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.