Skip to content

Commit bab95f9

Browse files
committed
wip
1 parent 971684e commit bab95f9

File tree

10 files changed

+134
-515
lines changed

10 files changed

+134
-515
lines changed

AGENTS.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,26 @@
1414
- Never commit unverified mass changes—compile or test first.
1515
- Do not use Perl or sed for multi-line structural edits; rely on Python 3.2-friendly heredocs.
1616

17+
## Markdown-Driven-Development (MDD)
18+
We practice **Markdown-Driven-Development** where documentation precedes implementation:
19+
20+
1. **Create GitHub issue** with clear problem statement and goals
21+
2. **Update user documentation** (README.md) with new behavior/semantics
22+
3. **Update agentic documentation** (AGENTS.md) with implementation guidance
23+
4. **Update specialist documentation** (**/*.md, e.g., ARCHITECTURE.md) as needed
24+
5. **Create implementation plan** (PLAN_${issue_id}.md) documenting exact changes
25+
6. **Implement code changes** to match documented behavior
26+
7. **Update tests** to validate the documented behavior
27+
8. **Verify all documentation** remains accurate after implementation
28+
29+
This ensures:
30+
- Users understand behavior changes before code is written
31+
- Developers have clear implementation guidance
32+
- Documentation stays synchronized with code
33+
- Breaking changes are clearly communicated
34+
35+
When making changes, always update documentation files before modifying code.
36+
1737

1838
## Testing & Logging Discipline
1939

@@ -498,6 +518,16 @@ IMPORTANT: Never disable tests written for logic that we are yet to write we do
498518
* Virtual threads for concurrent processing
499519
* **Use try-with-resources for all AutoCloseable resources** (HttpClient, streams, etc.)
500520

521+
## RFC 8927 Compliance Guidelines
522+
523+
* **Do not introduce AJV/JSON Schema compatibility semantics**
524+
* **{} must always compile as an empty object schema** (no properties allowed per RFC 8927)
525+
* **If tests or legacy code expect {} to mean "accept anything", update them to expect failure**
526+
* **The validator emits an INFO-level log when {} is compiled** to help catch migration issues
527+
* **Empty schema {} is equivalent to**: `{ "properties": {}, "optionalProperties": {}, "additionalProperties": false }`
528+
529+
When implementing JTD validation logic, ensure strict RFC 8927 compliance rather than maintaining compatibility with other JSON schema specifications.
530+
501531
## Package Structure
502532

503533
* Use default (package-private) access as the standard. Do not use 'private' or 'public' by default.

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,16 @@ This repo contains an incubating JTD validator that has the core JSON API as its
295295

296296
A complete JSON Type Definition validator is included (module: json-java21-jtd).
297297

298+
### Empty Schema `{}` Semantics
299+
300+
In RFC 8927 (JSON Typedef), the empty schema `{}` means:
301+
- An object with **no properties allowed**.
302+
- Equivalent to `{ "properties": {}, "optionalProperties": {}, "additionalProperties": false }`.
303+
304+
⚠️ Note: Some JSON Schema / AJV implementations treat `{}` as "accept anything".
305+
This library is RFC 8927–strict and will reject documents with any properties.
306+
A log message at INFO level is emitted when `{}` is compiled to highlight this difference.
307+
298308
```java
299309
import json.java21.jtd.Jtd;
300310
import jdk.sandbox.java.util.json.*;

json-java21-jtd/ARCHITECTURE.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,17 @@ $(command -v mvnd || command -v mvn || command -v ./mvnw) test -pl json-java21-j
288288
- **Definitions**: Validate all definitions exist at compile time
289289
- **Type Checking**: Strict RFC 8927 compliance for all primitive types
290290

291+
## Empty Schema Semantics
292+
293+
**RFC 8927 Strict Compliance**: The empty schema `{}` has specific semantics that differ from other JSON schema specifications:
294+
295+
- **RFC 8927 Meaning**: `{}` means an object with no properties allowed
296+
- **Equivalent to**: `{ "properties": {}, "optionalProperties": {}, "additionalProperties": false }`
297+
- **Valid Input**: Only `{}` (empty object)
298+
- **Invalid Input**: Any object with properties
299+
300+
**Important Note**: Some JSON Schema and AJV implementations treat `{}` as "accept anything". This JTD validator is RFC 8927-strict and will reject documents with additional properties. An INFO-level log message is emitted when `{}` is compiled to highlight this semantic difference.
301+
291302
## RFC 8927 Compliance
292303

293304
This implementation strictly follows RFC 8927:

json-java21-jtd/src/main/java/json/java21/jtd/Jtd.java

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ public class Jtd {
1818
/// Top-level definitions map for ref resolution
1919
private final Map<String, JtdSchema> definitions = new java.util.HashMap<>();
2020

21-
/// Raw definition values for context-aware ref resolution
22-
private final Map<String, JsonValue> rawDefinitions = new java.util.HashMap<>();
21+
// Removed: RFC 8927 strict mode - no context-aware compilation needed
2322

2423
/// Stack frame for iterative validation with path and offset tracking
2524
record Frame(JtdSchema schema, JsonValue instance, String ptr, Crumbs crumbs, String discriminatorKey) {
@@ -285,11 +284,6 @@ void pushChildFrames(Frame frame, java.util.Deque<Frame> stack) {
285284

286285
/// Compiles a JsonValue into a JtdSchema based on RFC 8927 rules
287286
JtdSchema compileSchema(JsonValue schema) {
288-
return compileSchema(schema, false); // Default: not from ref resolution
289-
}
290-
291-
/// Compiles a JsonValue into a JtdSchema with context-aware handling of {}
292-
JtdSchema compileSchema(JsonValue schema, boolean fromRef) {
293287
if (!(schema instanceof JsonObject obj)) {
294288
throw new IllegalArgumentException("Schema must be an object");
295289
}
@@ -308,19 +302,18 @@ JtdSchema compileSchema(JsonValue schema, boolean fromRef) {
308302
for (String key : defsObj.members().keySet()) {
309303
if (definitions.get(key) == null) {
310304
JsonValue rawDef = defsObj.members().get(key);
311-
rawDefinitions.put(key, rawDef); // Store raw definition for context-aware ref resolution
312-
// Compile definitions with fromRef=true for compatibility mode
313-
JtdSchema compiled = compileSchema(rawDef, true);
305+
// Compile definitions normally (RFC 8927 strict)
306+
JtdSchema compiled = compileSchema(rawDef);
314307
definitions.put(key, compiled);
315308
}
316309
}
317310
}
318311

319-
return compileObjectSchema(obj, fromRef);
312+
return compileObjectSchema(obj);
320313
}
321314

322-
/// Compiles an object schema according to RFC 8927 with context-aware handling
323-
JtdSchema compileObjectSchema(JsonObject obj, boolean fromRef) {
315+
/// Compiles an object schema according to RFC 8927 with strict semantics
316+
JtdSchema compileObjectSchema(JsonObject obj) {
324317
// Check for mutually-exclusive schema forms
325318
List<String> forms = new ArrayList<>();
326319
Map<String, JsonValue> members = obj.members();
@@ -347,18 +340,15 @@ JtdSchema compileObjectSchema(JsonObject obj, boolean fromRef) {
347340
// Parse the specific schema form
348341
JtdSchema schema;
349342

350-
// Context-aware handling of {} - RFC vs compatibility mode
343+
// RFC 8927 strict: {} always means "no properties allowed"
351344
if (forms.isEmpty() && obj.members().isEmpty()) {
352-
if (fromRef) {
353-
// Compatibility mode: {} from ref resolution behaves as EmptySchema (accept anything)
354-
schema = new JtdSchema.EmptySchema();
355-
} else {
356-
// RFC mode: {} at root or direct context behaves as PropertiesSchema (no properties allowed)
357-
schema = new JtdSchema.PropertiesSchema(Map.of(), Map.of(), false);
358-
}
345+
LOG.info(() -> "Empty schema {} encountered. "
346+
+ "Note: In some JSON validation specs this means 'accept anything', "
347+
+ "but per RFC 8927 it means an object with no properties allowed.");
348+
return new JtdSchema.PropertiesSchema(Map.of(), Map.of(), false);
359349
} else if (forms.isEmpty()) {
360350
// Empty schema with no explicit form - default to EmptySchema for backwards compatibility
361-
schema = new JtdSchema.EmptySchema();
351+
return new JtdSchema.EmptySchema();
362352
} else {
363353
String form = forms.getFirst();
364354
schema = switch (form) {
@@ -503,10 +493,7 @@ JtdSchema compileDiscriminatorSchema(JsonObject obj) {
503493
return new JtdSchema.DiscriminatorSchema(discStr.value(), mapping);
504494
}
505495

506-
/// Gets raw definition value for context-aware ref resolution
507-
JsonValue getRawDefinition(String ref) {
508-
return rawDefinitions.get(ref);
509-
}
496+
// Removed: RFC 8927 strict mode - no context-aware ref resolution needed
510497

511498
/// Extracts and stores top-level definitions for ref resolution
512499
private Map<String, JtdSchema> parsePropertySchemas(JsonObject propsObj) {

json-java21-jtd/src/test/java/json/java21/jtd/DocumentationAJvTests.java

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -233,42 +233,45 @@ public void testSelfReferencingSchema() throws Exception {
233233
LOG.fine(() -> "Self-referencing schema test - schema: " + schema + ", tree: " + tree);
234234
}
235235

236-
/// Empty form: any data
236+
/// Empty form: RFC 8927 strict - {} means "no properties allowed"
237237
@Test
238-
public void testEmptyForm() throws Exception {
238+
public void testEmptyFormRfcStrict() throws Exception {
239239
JsonValue schema = Json.parse("{}");
240240

241-
// Test various data types
242-
JsonValue stringData = Json.parse("\"hello\"");
243-
JsonValue numberData = Json.parse("42");
244-
JsonValue objectData = Json.parse("{\"key\": \"value\"}");
245-
JsonValue arrayData = Json.parse("[1, 2, 3]");
246-
JsonValue nullData = Json.parse("null");
247-
JsonValue boolData = Json.parse("true");
248-
249-
assertThat(schema).isNotNull();
250-
assertThat(stringData).isNotNull();
251-
assertThat(numberData).isNotNull();
252-
assertThat(objectData).isNotNull();
253-
assertThat(arrayData).isNotNull();
254-
assertThat(nullData).isNotNull();
255-
assertThat(boolData).isNotNull();
256-
LOG.fine(() -> "Empty form test - schema: " + schema + ", accepts any data");
241+
// Test valid empty object
242+
JsonValue emptyObject = Json.parse("{}");
243+
Jtd validator = new Jtd();
244+
Jtd.Result validResult = validator.validate(schema, emptyObject);
245+
assertThat(validResult.isValid())
246+
.as("Empty schema {} should accept empty object per RFC 8927")
247+
.isTrue();
248+
249+
// Test invalid object with properties
250+
JsonValue objectWithProps = Json.parse("{\"key\": \"value\"}");
251+
Jtd.Result invalidResult = validator.validate(schema, objectWithProps);
252+
assertThat(invalidResult.isValid())
253+
.as("Empty schema {} should reject object with properties per RFC 8927")
254+
.isFalse();
255+
assertThat(invalidResult.errors())
256+
.as("Should have validation error for additional property")
257+
.isNotEmpty();
258+
259+
LOG.fine(() -> "Empty form RFC strict test - schema: " + schema + ", valid: empty object, invalid: object with properties");
257260
}
258261

259-
/// Counter-test: Empty form validation should pass for any data (no invalid data)
260-
/// Same schema as testEmptyForm but tests that no data is invalid
262+
/// Counter-test: Empty form validation should reject objects with properties per RFC 8927
263+
/// Same schema as testEmptyFormRfcStrict but tests invalid data
261264
@Test
262-
public void testEmptyFormInvalid() throws Exception {
265+
public void testEmptyFormRejectsProperties() throws Exception {
263266
JsonValue schema = Json.parse("{}");
264267

265-
// Test that empty schema accepts any data - should pass for "invalid" data
266-
JsonValue anyData = Json.parse("{\"anything\": \"goes\"}");
268+
// Test that empty schema rejects object with properties per RFC 8927
269+
JsonValue dataWithProps = Json.parse("{\"anything\": \"goes\"}");
267270
Jtd validator = new Jtd();
268-
Jtd.Result result = validator.validate(schema, anyData);
269-
assertThat(result.isValid()).isTrue();
270-
assertThat(result.errors()).isEmpty();
271-
LOG.fine(() -> "Empty form invalid test - schema: " + schema + ", any data should pass: " + anyData);
271+
Jtd.Result result = validator.validate(schema, dataWithProps);
272+
assertThat(result.isValid()).isFalse();
273+
assertThat(result.errors()).isNotEmpty();
274+
LOG.fine(() -> "Empty form rejects properties test - schema: " + schema + ", data with properties should fail: " + dataWithProps);
272275
}
273276

274277
/// Type form: numeric types

0 commit comments

Comments
 (0)