Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/custom-schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,31 @@ The URL parsing allows using either the `{study}` or `{value}` placeholders mult

> [!Warning]
> The URL may cause errors in Song if it contains any tokens matching the `{word}` format other than `{study}` and `{value}`

#### Accessing values within arrays

The `jsonPath` can address values inside arrays using standard bracket notation.

To access a value at a specific index, include the index in brackets:

```json
{
"url": "http://example.com/{study}/donor/{value}",
"jsonPath": "donors[0].donorId"
}
```

This extracts `donorId` from the first element of the `donors` array and performs one validation call.

To validate every element in an array, use the `[*]` wildcard:

```json
{
"url": "http://example.com/{study}/donor/{value}",
"jsonPath": "donors[*].donorId"
}
```

This extracts `donorId` from each element of the `donors` array and performs one validation call per value. If any value fails validation, the entire analysis submission is rejected. If the array is empty, no validation calls are made and the submission proceeds.

Only string values are validated. If the property at the resolved path is not a string, it is silently skipped.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import bio.overture.song.server.validation.ValidationResponse;
import com.fasterxml.jackson.databind.JsonNode;
import com.jayway.jsonpath.JsonPath;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
Expand Down Expand Up @@ -127,14 +128,27 @@ public Optional<String> validate(@NonNull JsonNode payload, @NonNull String stud
return Optional.ofNullable(errors);
}

private Optional<String> getValueAtJsonPath(@NonNull JsonNode payload, @NonNull String jsonPath) {
List<String> getValuesAtJsonPath(@NonNull JsonNode payload, @NonNull String jsonPath) {
try {
String value = JsonPath.read(payload.toString(), "$." + jsonPath);
return Optional.of(value);
} catch (Exception e) {
val result = JsonPath.read(payload.toString(), "$." + jsonPath);
if (result instanceof List) {
List<String> values = new ArrayList<>();
for (val element : (List<?>) result) {
if (element instanceof String) {
values.add((String) element);
}
}
return values;
} else if (result instanceof String) {
return List.of((String) result);
} else {
return List.of();
}
} catch (Exception exception) {
log.debug(
String.format("Error reading value for external validation. Reason: %s", e.getMessage()));
return Optional.empty();
String.format(
"Error reading value for external validation. Reason: %s", exception.getMessage()));
return List.of();
}
}

Expand All @@ -151,26 +165,25 @@ private void externalValidations(

for (ExternalValidation externalValidation : externalValidations) {

val value = getValueAtJsonPath(payload, externalValidation.getJsonPath());
if (value.isPresent()) {
// Only validate vs external source if the value is present in the analysis payload
val values = getValuesAtJsonPath(payload, externalValidation.getJsonPath());
for (String value : values) {
val formattedExternalUrl =
buildExternalUrlFromTemplate(externalValidation.getUrl(), studyId, value.get());
buildExternalUrlFromTemplate(externalValidation.getUrl(), studyId, value);
try {
val response = restTemplate.getForEntity(formattedExternalUrl, Void.class);
if (response.getStatusCode().isError()) {
val errorMessage =
String.format(
"Value '%s' from path '%s' is not permitted as it failed to validate with external validation source.",
value.get(), externalValidation.getJsonPath(), formattedExternalUrl);
value, externalValidation.getJsonPath());
log.debug(errorMessage);
throw new ValidationException(errorMessage);
}
} catch (RestClientException e) {
} catch (RestClientException exception) {
val errorMessage =
String.format(
"Value '%s' from path '%s' is not permitted as it failed to validate with external validation source.",
value.get(), externalValidation.getJsonPath());
value, externalValidation.getJsonPath());
log.info(
String.format(
"Error occurred while executing external validation against url '%s'.",
Expand Down