Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ permissions:

env:
# The conformance suite release this repo is vendored from and must report against.
BQ_CONFORMANCE_VERSION: "1.9.0"
BQ_CONFORMANCE_VERSION: "1.10.0"
# Integrity pin for fetch.sh (sha256 of conformance/fetch.sh at the pinned tag).
BQ_FETCH_SH_SHA256: 8bb6ba567b288fb0ac71a70fd17041b2503df119e01998d76fdad372570bf849

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ permissions:
contents: write

env:
BQ_CONFORMANCE_VERSION: "1.9.0"
BQ_CONFORMANCE_VERSION: "1.10.0"

jobs:
# Secret-free: runs the tests, produces results.json and validates it. The PyPI package it
Expand Down
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,38 @@ The envelope wire format is versioned separately by `meta.schema_version`

## [Unreleased]

## [1.16.0] - 2026-11-11

Brings the opt-in payload validator (ADR-0024, `com.babelqueue.schema`) to parity with the other SDKs, as locked by the
`payload_schema_keywords` section of conformance 1.10.0. The envelope stays at `schema_version: 1`; nothing changes on
the wire, and the validator remains zero-dependency (GR-7).

### Added
- Union `type`: `"type": ["string", "null"]` is valid when the value matches ANY listed type, and the keywords of the
matching type (`minLength`, `minimum`, `properties`, ...) still apply. `integer` accepts every whole-valued number
(`1.0`, `1e2`, `BigDecimal("1.00")`).
- `maxLength` (counted in Unicode code points, like `minLength`).
- Registration-time warnings for keywords outside the supported subset: `PayloadValidator.warnings(schema)` returns one
`SchemaWarning(pointer, keyword)` per occurrence (RFC 6901 pointer of the schema object carrying the keyword), and
`MapProvider` reports them when the schemas are registered, each as a `RegisteredSchemaWarning(urn, warning)` so the
operator can tell which URN's schema carries it, through a `Consumer<RegisteredSchemaWarning>` hook
(`new MapProvider(schemas, onWarning)`; `System.Logger` at `WARNING` by default) and keeps them in
`MapProvider.warnings()`. Annotation keywords (`$schema`, `$id`, `title`, `description`, `examples`, `default`,
`$comment`, `x-*`) never warn.

### Fixed
- `const` / `enum` compare numbers by mathematical value, recursively inside arrays and objects: `1`, `1.0` and `1e0` are
equal and `2.5` equals `2.50` whatever the `Number` subtype (`Long`, `Integer`, `Double`, `BigInteger`, `BigDecimal`, ...).
A boolean or a string is still never equal to a number. Previously a `Long` const never matched a `Double` value.
- A fractional `maxLength` is truncated toward zero (`2.9` is `2`, `-0.5` is `0`) before the comparison, like `minLength` here and the
Go, PHP and Python validators; it was compared as a raw double, which only differed for a negative fractional bound.
A non-finite `minLength` / `maxLength` (`NaN`, `±Infinity`; only reachable from a hand-built schema) is ignored, and a
`Long` bound above the `int` range no longer wraps (`minLength` used `intValue()`).

### Changed
- Vendored conformance suite bumped to 1.10.0 (new `payload_schema_keywords` section, run by `PayloadSchemaConformanceTest`
and recorded in `results.json`).

## [1.15.0] - 2026-11-01

Implements ADR-0038 (OpenTelemetry semantic-conventions alignment) and ADR-0039 (standard metrics and the
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.babelqueue</groupId>
<artifactId>babelqueue-core</artifactId>
<version>1.15.0</version>
<version>1.16.0</version>
<packaging>jar</packaging>

<name>BabelQueue Core</name>
Expand Down
49 changes: 49 additions & 0 deletions src/main/java/com/babelqueue/schema/MapProvider.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,70 @@
package com.babelqueue.schema;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

/**
* In-memory {@link SchemaProvider}, for tests and for embedding schemas in code. Construct it
* with each URN's already-decoded JSON Schema (a {@code Map}).
*
* <p>Construction is where a schema is first processed: every keyword outside the supported
* subset (see {@link PayloadValidator#warnings}) produces one {@link RegisteredSchemaWarning}
* (the {@link SchemaWarning} plus the URN it was found under), handed to the hook
* ({@link System.Logger} at {@code WARNING} by default, like the envelope codec) and
* kept in {@link #warnings()}. Warnings never change a validation verdict.
*/
public final class MapProvider implements SchemaProvider {

private static final System.Logger LOGGER = System.getLogger(MapProvider.class.getName());

private final Map<String, Map<String, Object>> schemas;
private final List<RegisteredSchemaWarning> warnings;

/**
* Warnings go to {@link System.Logger} at {@code WARNING}.
*
* @param schemas urn -&gt; decoded JSON Schema
*/
public MapProvider(Map<String, Map<String, Object>> schemas) {
this(schemas, warning -> LOGGER.log(System.Logger.Level.WARNING, warning.message()));
}

/**
* @param schemas urn -&gt; decoded JSON Schema
* @param onWarning receives each registration warning (with its URN) instead of the logger
*/
public MapProvider(
Map<String, Map<String, Object>> schemas, Consumer<RegisteredSchemaWarning> onWarning) {
Objects.requireNonNull(onWarning, "onWarning");
this.schemas = new HashMap<>(schemas);
List<RegisteredSchemaWarning> collected = new ArrayList<>();
// Iterate the supplied map, not the copy: the order is that of the caller's map.
for (Map.Entry<String, Map<String, Object>> entry : schemas.entrySet()) {
if (entry.getValue() == null) {
continue;
}
for (SchemaWarning warning : PayloadValidator.warnings(entry.getValue())) {
RegisteredSchemaWarning registered = new RegisteredSchemaWarning(entry.getKey(), warning);
collected.add(registered);
onWarning.accept(registered);
}
}
this.warnings = Collections.unmodifiableList(collected);
}

/**
* The warnings produced when the schemas were registered, in the iteration order of the
* map passed to the constructor (document order within one schema).
*
* @return an unmodifiable list, empty when every keyword is supported
*/
public List<RegisteredSchemaWarning> warnings() {
return warnings;
}

@Override
Expand Down
Loading
Loading