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 README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ to use a JSON Schema validator at runtime to enforce remaining constraints.
| Applicator (2020-12) | `propertyNames` | Ignored |
| Applicator (2020-12) | `dependentSchemas` | Pending |
| Applicator (2020-12) | `contains` | Ignored |
| Applicator (2020-12) | `allOf` | Pending |
| Applicator (2020-12) | `allOf` | Yes |
| Applicator (2020-12) | `oneOf` | **PARTIAL GIVEN LANGUAGE LIMITATIONS** |
| Applicator (2020-12) | `not` | **CANNOT SUPPORT** |
| Applicator (2020-12) | `if` | Pending |
Expand Down
19 changes: 19 additions & 0 deletions test/e2e/typescript/2020-12/allof_intersection/expected.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export type AllOfIntersection_1Age = number;

export type AllOfIntersection_1AdditionalProperties = never;

export interface AllOfIntersection_1 {
"age": AllOfIntersection_1Age;
}

export type AllOfIntersection_0Name = string;

export type AllOfIntersection_0AdditionalProperties = never;

export interface AllOfIntersection_0 {
"name": AllOfIntersection_0Name;
}

export type AllOfIntersection =
AllOfIntersection_0 &
AllOfIntersection_1;
3 changes: 3 additions & 0 deletions test/e2e/typescript/2020-12/allof_intersection/options.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"defaultPrefix": "AllOfIntersection"
}
21 changes: 21 additions & 0 deletions test/e2e/typescript/2020-12/allof_intersection/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: These closed allOf branches make the schema unsatisfiable; the fixture never admits an object with both name and age.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/e2e/typescript/2020-12/allof_intersection/schema.json, line 10:

<comment>These closed `allOf` branches make the schema unsatisfiable; the fixture never admits an object with both `name` and `age`.</comment>

<file context>
@@ -0,0 +1,21 @@
+        "name": { "type": "string" }
+      },
+      "required": [ "name" ],
+      "additionalProperties": false
+    },
+    {
</file context>
Fix with Cubic

"$schema": "https://json-schema.org/draft/2020-12/schema",
"allOf": [
{
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": [ "name" ],
"additionalProperties": false
Copy link
Copy Markdown

@augmentcode augmentcode bot Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

additionalProperties: false inside each allOf subschema makes objects containing both name and age invalid under JSON Schema semantics (each branch rejects the other branch’s property), so the “valid” instances in the TypeScript tests wouldn’t validate at runtime.

Severity: medium

Other Locations
  • test/e2e/typescript/2020-12/allof_intersection/schema.json:18
  • test/e2e/typescript/2020-12/allof_refs/schema.json:10
  • test/e2e/typescript/2020-12/allof_refs/schema.json:18

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

},
{
"type": "object",
"properties": {
"age": { "type": "integer" }
},
"required": [ "age" ],
"additionalProperties": false
}
]
}
19 changes: 19 additions & 0 deletions test/e2e/typescript/2020-12/allof_intersection/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { AllOfIntersection } from "./expected";

// Valid: satisfies both branches
const valid: AllOfIntersection = {
name: "Alice",
age: 30
};

// Invalid: missing age from second branch
// @ts-expect-error
const missingAge: AllOfIntersection = {
name: "Bob"
};

// Invalid: missing name from first branch
// @ts-expect-error
const missingName: AllOfIntersection = {
age: 25
};
23 changes: 23 additions & 0 deletions test/e2e/typescript/2020-12/allof_refs/expected.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export type Person_1 = PersonAged;

export type Person_0 = PersonNamed;

export type PersonNamedName = string;

export type PersonNamedAdditionalProperties = never;

export interface PersonNamed {
"name": PersonNamedName;
}

export type PersonAgedAge = number;

export type PersonAgedAdditionalProperties = never;

export interface PersonAged {
"age": PersonAgedAge;
}

export type Person =
Person_0 &
Person_1;
3 changes: 3 additions & 0 deletions test/e2e/typescript/2020-12/allof_refs/options.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"defaultPrefix": "Person"
}
25 changes: 25 additions & 0 deletions test/e2e/typescript/2020-12/allof_refs/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"Named": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": [ "name" ],
"additionalProperties": false
},
"Aged": {
"type": "object",
"properties": {
"age": { "type": "integer" }
},
"required": [ "age" ],
"additionalProperties": false
}
},
"allOf": [
{ "$ref": "#/$defs/Named" },
{ "$ref": "#/$defs/Aged" }
]
}
19 changes: 19 additions & 0 deletions test/e2e/typescript/2020-12/allof_refs/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Person } from "./expected";

// Valid: satisfies both $ref branches
const valid: Person = {
name: "Alice",
age: 30
};

// Invalid: missing age
// @ts-expect-error
const missingAge: Person = {
name: "Bob"
};

// Invalid: missing name
// @ts-expect-error
const missingName: Person = {
age: 25
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type Wrapper_0Value = string;

export type Wrapper_0AdditionalProperties = never;

export interface Wrapper_0 {
"value": Wrapper_0Value;
}

export type Wrapper = Wrapper_0;
3 changes: 3 additions & 0 deletions test/e2e/typescript/2020-12/allof_single_element/options.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"defaultPrefix": "Wrapper"
}
13 changes: 13 additions & 0 deletions test/e2e/typescript/2020-12/allof_single_element/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"allOf": [
{
"type": "object",
"properties": {
"value": { "type": "string" }
},
"required": [ "value" ],
"additionalProperties": false
}
]
}
10 changes: 10 additions & 0 deletions test/e2e/typescript/2020-12/allof_single_element/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Wrapper } from "./expected";

// Valid: single-element allOf acts as the element itself
const valid: Wrapper = {
value: "hello"
};

// Invalid: missing required value
// @ts-expect-error
const missingValue: Wrapper = {};
167 changes: 167 additions & 0 deletions test/ir/ir_2020_12_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1393,3 +1393,170 @@ TEST(IR_2020_12, dynamic_anchor_on_typed_schema) {
EXPECT_EQ(result.size(), 1);
EXPECT_IR_SCALAR(result, 0, String, "");
}

TEST(IR_2020_12, allof_two_objects) {
const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"allOf": [
{
"type": "object",
"properties": { "name": { "type": "string" } },
"required": [ "name" ],
"additionalProperties": false
},
{
"type": "object",
"properties": { "age": { "type": "integer" } },
"required": [ "age" ],
"additionalProperties": false
}
]
})JSON")};

const auto result{
sourcemeta::codegen::compile(schema, sourcemeta::core::schema_walker,
sourcemeta::core::schema_resolver,
sourcemeta::codegen::default_compiler)};

using namespace sourcemeta::codegen;

ASSERT_EQ(result.size(), 7);
EXPECT_IR_INTERSECTION(result, 6, "", 2);
Copy link
Copy Markdown

@augmentcode augmentcode bot Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test hard-codes the intersection node at index 6 (and size 7), which can become brittle if the IR gains additional entries or the sort order changes; using a relative index like other tests would make it less sensitive to unrelated IR changes.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

}

TEST(IR_2020_12, allof_ref_and_object) {
const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"allOf": [
{ "$ref": "#/$defs/Base" },
{
"type": "object",
"properties": { "extra": { "type": "string" } },
"additionalProperties": false
}
],
"$defs": {
"Base": {
"type": "object",
"properties": { "id": { "type": "integer" } },
"required": [ "id" ],
"additionalProperties": false
}
}
})JSON")};

const auto result{
sourcemeta::codegen::compile(schema, sourcemeta::core::schema_walker,
sourcemeta::core::schema_resolver,
sourcemeta::codegen::default_compiler)};

using namespace sourcemeta::codegen;

EXPECT_IR_INTERSECTION(result, result.size() - 1, "", 2);
}

TEST(IR_2020_12, allof_single_element) {
const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"allOf": [
{ "type": "string" }
]
})JSON")};

const auto result{
sourcemeta::codegen::compile(schema, sourcemeta::core::schema_walker,
sourcemeta::core::schema_resolver,
sourcemeta::codegen::default_compiler)};

using namespace sourcemeta::codegen;

ASSERT_EQ(result.size(), 2);
EXPECT_IR_SCALAR(result, 0, String, "/allOf/0");
EXPECT_IR_REFERENCE(result, 1, "", "/allOf/0");
}

TEST(IR_2020_12, allof_three_branches) {
const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"allOf": [
{
"type": "object",
"properties": { "a": { "type": "string" } },
"additionalProperties": false
},
{
"type": "object",
"properties": { "b": { "type": "integer" } },
"additionalProperties": false
},
{
"type": "object",
"properties": { "c": { "type": "number" } },
"additionalProperties": false
}
]
})JSON")};

const auto result{
sourcemeta::codegen::compile(schema, sourcemeta::core::schema_walker,
sourcemeta::core::schema_resolver,
sourcemeta::codegen::default_compiler)};

using namespace sourcemeta::codegen;

EXPECT_IR_INTERSECTION(result, result.size() - 1, "", 3);
}

TEST(IR_2020_12, allof_with_defs) {
const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"Named": {
"type": "object",
"properties": { "name": { "type": "string" } },
"required": [ "name" ],
"additionalProperties": false
},
"Aged": {
"type": "object",
"properties": { "age": { "type": "integer" } },
"required": [ "age" ],
"additionalProperties": false
}
},
"allOf": [
{ "$ref": "#/$defs/Named" },
{ "$ref": "#/$defs/Aged" }
]
})JSON")};

const auto result{
sourcemeta::codegen::compile(schema, sourcemeta::core::schema_walker,
sourcemeta::core::schema_resolver,
sourcemeta::codegen::default_compiler)};

using namespace sourcemeta::codegen;

EXPECT_IR_INTERSECTION(result, result.size() - 1, "", 2);

// Both allOf branches should be references to their respective $defs
bool found_named{false};
bool found_aged{false};
for (const auto &entry : result) {
if (std::holds_alternative<IRReference>(entry)) {
const auto &reference{std::get<IRReference>(entry)};
const auto pointer_string{sourcemeta::core::to_string(reference.pointer)};
const auto target_string{
sourcemeta::core::to_string(reference.target.pointer)};
if (pointer_string == "/allOf/0" && target_string == "/$defs/Named") {
found_named = true;
} else if (pointer_string == "/allOf/1" &&
target_string == "/$defs/Aged") {
found_aged = true;
}
}
}

EXPECT_TRUE(found_named);
EXPECT_TRUE(found_aged);
}
12 changes: 12 additions & 0 deletions test/ir/ir_test_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@
std::get<sourcemeta::codegen::IRArray>(result.at(index)).items->pointer, \
expected_items_pointer)

#define EXPECT_IR_INTERSECTION(result, index, expected_pointer, \
expected_count) \
EXPECT_TRUE(std::holds_alternative<sourcemeta::codegen::IRIntersection>( \
result.at(index))) \
<< "Expected IRIntersection at index " << index; \
EXPECT_AS_STRING( \
std::get<sourcemeta::codegen::IRIntersection>(result.at(index)).pointer, \
expected_pointer); \
EXPECT_EQ(std::get<sourcemeta::codegen::IRIntersection>(result.at(index)) \
.values.size(), \
expected_count)

#define EXPECT_IR_REFERENCE(result, index, expected_pointer, \
expected_target_pointer) \
EXPECT_TRUE(std::holds_alternative<sourcemeta::codegen::IRReference>( \
Expand Down
Loading