Skip to content

Commit c28e4aa

Browse files
authored
Fix default/examples lint rules on non-anonymous schemas (#476)
See: sourcemeta/jsonschema#390 Signed-off-by: Juan Cruz Viotti <[email protected]>
1 parent ec936ca commit c28e4aa

File tree

4 files changed

+270
-6
lines changed

4 files changed

+270
-6
lines changed

src/linter/valid_default.cc

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ ValidDefault::ValidDefault(Compiler compiler)
1515
auto ValidDefault::condition(
1616
const sourcemeta::core::JSON &schema, const sourcemeta::core::JSON &root,
1717
const sourcemeta::core::Vocabularies &vocabularies,
18-
const sourcemeta::core::SchemaFrame &,
18+
const sourcemeta::core::SchemaFrame &frame,
1919
const sourcemeta::core::SchemaFrame::Location &location,
2020
const sourcemeta::core::SchemaWalker &walker,
2121
const sourcemeta::core::SchemaResolver &resolver) const
@@ -36,11 +36,24 @@ auto ValidDefault::condition(
3636
return false;
3737
}
3838

39+
const auto &root_base_dialect{frame.traverse(location.root.value_or(""))
40+
.value_or(location)
41+
.get()
42+
.base_dialect};
43+
std::optional<std::string> default_id{location.base};
44+
if (sourcemeta::core::identify(root, root_base_dialect).has_value()) {
45+
// We want to only set a default identifier if the root schema does not
46+
// have an explicit identifier. Otherwise, we can get into corner case
47+
// when wrapping the schema
48+
default_id = std::nullopt;
49+
}
50+
3951
const auto subschema{sourcemeta::core::wrap(root, location.pointer, resolver,
4052
location.dialect)};
4153
const auto schema_template{compile(subschema, walker, resolver,
4254
this->compiler_, Mode::FastValidation,
43-
location.dialect, location.base)};
55+
location.dialect, default_id)};
56+
4457
const auto &instance{schema.at("default")};
4558
Evaluator evaluator;
4659
const std::string ref{"$ref"};

src/linter/valid_examples.cc

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ ValidExamples::ValidExamples(Compiler compiler)
2525
auto ValidExamples::condition(
2626
const sourcemeta::core::JSON &schema, const sourcemeta::core::JSON &root,
2727
const sourcemeta::core::Vocabularies &vocabularies,
28-
const sourcemeta::core::SchemaFrame &,
28+
const sourcemeta::core::SchemaFrame &frame,
2929
const sourcemeta::core::SchemaFrame::Location &location,
3030
const sourcemeta::core::SchemaWalker &walker,
3131
const sourcemeta::core::SchemaResolver &resolver) const
@@ -44,11 +44,23 @@ auto ValidExamples::condition(
4444
return false;
4545
}
4646

47+
const auto &root_base_dialect{frame.traverse(location.root.value_or(""))
48+
.value_or(location)
49+
.get()
50+
.base_dialect};
51+
std::optional<std::string> default_id{location.base};
52+
if (sourcemeta::core::identify(root, root_base_dialect).has_value()) {
53+
// We want to only set a default identifier if the root schema does not
54+
// have an explicit identifier. Otherwise, we can get into corner case
55+
// when wrapping the schema
56+
default_id = std::nullopt;
57+
}
58+
4759
const auto subschema{sourcemeta::core::wrap(root, location.pointer, resolver,
4860
location.dialect)};
4961
const auto schema_template{compile(subschema, walker, resolver,
5062
this->compiler_, Mode::FastValidation,
51-
location.dialect, location.base)};
63+
location.dialect, default_id)};
5264

5365
Evaluator evaluator;
5466
std::size_t cursor{0};

test/linter/linter_valid_default_test.cc

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ static auto transformer_callback_error(const sourcemeta::core::Pointer &,
1212
throw std::runtime_error("The transform callback must not be called");
1313
}
1414

15-
TEST(Linter, valid_default_error_message) {
15+
TEST(Linter, valid_default_error_message_without_id_nested) {
1616
sourcemeta::core::SchemaTransformer bundle;
1717
bundle.add<sourcemeta::blaze::ValidDefault>(
1818
sourcemeta::blaze::default_schema_compiler);
@@ -54,6 +54,124 @@ TEST(Linter, valid_default_error_message) {
5454
)TXT");
5555
}
5656

57+
TEST(Linter, valid_default_error_message_without_id_flat) {
58+
sourcemeta::core::SchemaTransformer bundle;
59+
bundle.add<sourcemeta::blaze::ValidDefault>(
60+
sourcemeta::blaze::default_schema_compiler);
61+
62+
const auto schema{sourcemeta::core::parse_json(R"JSON({
63+
"$schema": "https://json-schema.org/draft/2020-12/schema",
64+
"default": 1,
65+
"type": "string"
66+
})JSON")};
67+
68+
std::vector<std::tuple<sourcemeta::core::Pointer, std::string, std::string,
69+
std::string>>
70+
entries;
71+
const bool result =
72+
bundle.check(schema, sourcemeta::core::schema_official_walker,
73+
sourcemeta::core::schema_official_resolver,
74+
[&entries](const auto &pointer, const auto &name,
75+
const auto &message, const auto &description) {
76+
entries.emplace_back(pointer, name, message, description);
77+
});
78+
79+
EXPECT_FALSE(result);
80+
EXPECT_EQ(entries.size(), 1);
81+
82+
EXPECT_EQ(std::get<0>(entries.at(0)), sourcemeta::core::Pointer({}));
83+
EXPECT_EQ(std::get<1>(entries.at(0)), "blaze/valid_default");
84+
EXPECT_EQ(std::get<2>(entries.at(0)),
85+
"Only set a `default` value that validates against the schema");
86+
EXPECT_EQ(
87+
std::get<3>(entries.at(0)),
88+
R"TXT(The value was expected to be of type string but it was of type integer
89+
at instance location ""
90+
at evaluate path "/type"
91+
)TXT");
92+
}
93+
94+
TEST(Linter, valid_default_error_message_with_id_nested) {
95+
sourcemeta::core::SchemaTransformer bundle;
96+
bundle.add<sourcemeta::blaze::ValidDefault>(
97+
sourcemeta::blaze::default_schema_compiler);
98+
99+
const auto schema{sourcemeta::core::parse_json(R"JSON({
100+
"$schema": "https://json-schema.org/draft/2020-12/schema",
101+
"$id": "https://example.com",
102+
"properties": {
103+
"foo": {
104+
"default": 1,
105+
"type": "string"
106+
}
107+
}
108+
})JSON")};
109+
110+
std::vector<std::tuple<sourcemeta::core::Pointer, std::string, std::string,
111+
std::string>>
112+
entries;
113+
const bool result =
114+
bundle.check(schema, sourcemeta::core::schema_official_walker,
115+
sourcemeta::core::schema_official_resolver,
116+
[&entries](const auto &pointer, const auto &name,
117+
const auto &message, const auto &description) {
118+
entries.emplace_back(pointer, name, message, description);
119+
});
120+
121+
EXPECT_FALSE(result);
122+
EXPECT_EQ(entries.size(), 1);
123+
124+
EXPECT_EQ(std::get<0>(entries.at(0)),
125+
sourcemeta::core::Pointer({"properties", "foo"}));
126+
EXPECT_EQ(std::get<1>(entries.at(0)), "blaze/valid_default");
127+
EXPECT_EQ(std::get<2>(entries.at(0)),
128+
"Only set a `default` value that validates against the schema");
129+
EXPECT_EQ(
130+
std::get<3>(entries.at(0)),
131+
R"TXT(The value was expected to be of type string but it was of type integer
132+
at instance location ""
133+
at evaluate path "/type"
134+
)TXT");
135+
}
136+
137+
TEST(Linter, valid_default_error_message_with_id_flat) {
138+
sourcemeta::core::SchemaTransformer bundle;
139+
bundle.add<sourcemeta::blaze::ValidDefault>(
140+
sourcemeta::blaze::default_schema_compiler);
141+
142+
const auto schema{sourcemeta::core::parse_json(R"JSON({
143+
"$schema": "https://json-schema.org/draft/2020-12/schema",
144+
"$id": "https://example.com",
145+
"default": 1,
146+
"type": "string"
147+
})JSON")};
148+
149+
std::vector<std::tuple<sourcemeta::core::Pointer, std::string, std::string,
150+
std::string>>
151+
entries;
152+
const bool result =
153+
bundle.check(schema, sourcemeta::core::schema_official_walker,
154+
sourcemeta::core::schema_official_resolver,
155+
[&entries](const auto &pointer, const auto &name,
156+
const auto &message, const auto &description) {
157+
entries.emplace_back(pointer, name, message, description);
158+
});
159+
160+
EXPECT_FALSE(result);
161+
EXPECT_EQ(entries.size(), 1);
162+
163+
EXPECT_EQ(std::get<0>(entries.at(0)), sourcemeta::core::Pointer({}));
164+
EXPECT_EQ(std::get<1>(entries.at(0)), "blaze/valid_default");
165+
EXPECT_EQ(std::get<2>(entries.at(0)),
166+
"Only set a `default` value that validates against the schema");
167+
EXPECT_EQ(
168+
std::get<3>(entries.at(0)),
169+
R"TXT(The value was expected to be of type string but it was of type integer
170+
at instance location ""
171+
at evaluate path "/type"
172+
)TXT");
173+
}
174+
57175
TEST(Linter, valid_default_1) {
58176
sourcemeta::core::SchemaTransformer bundle;
59177
bundle.add<sourcemeta::blaze::ValidDefault>(

test/linter/linter_valid_examples_test.cc

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ static auto transformer_callback_error(const sourcemeta::core::Pointer &,
1212
throw std::runtime_error("The transform callback must not be called");
1313
}
1414

15-
TEST(Linter, valid_examples_error_message) {
15+
TEST(Linter, valid_examples_error_message_without_id_nested) {
1616
sourcemeta::core::SchemaTransformer bundle;
1717
bundle.add<sourcemeta::blaze::ValidExamples>(
1818
sourcemeta::blaze::default_schema_compiler);
@@ -55,6 +55,127 @@ TEST(Linter, valid_examples_error_message) {
5555
)TXT");
5656
}
5757

58+
TEST(Linter, valid_examples_error_message_without_id_flat) {
59+
sourcemeta::core::SchemaTransformer bundle;
60+
bundle.add<sourcemeta::blaze::ValidExamples>(
61+
sourcemeta::blaze::default_schema_compiler);
62+
63+
const auto schema{sourcemeta::core::parse_json(R"JSON({
64+
"$schema": "https://json-schema.org/draft/2020-12/schema",
65+
"examples": [ 1 ],
66+
"type": "string"
67+
})JSON")};
68+
69+
std::vector<std::tuple<sourcemeta::core::Pointer, std::string, std::string,
70+
std::string>>
71+
entries;
72+
const bool result =
73+
bundle.check(schema, sourcemeta::core::schema_official_walker,
74+
sourcemeta::core::schema_official_resolver,
75+
[&entries](const auto &pointer, const auto &name,
76+
const auto &message, const auto &description) {
77+
entries.emplace_back(pointer, name, message, description);
78+
});
79+
80+
EXPECT_FALSE(result);
81+
EXPECT_EQ(entries.size(), 1);
82+
83+
EXPECT_EQ(std::get<0>(entries.at(0)), sourcemeta::core::Pointer({}));
84+
EXPECT_EQ(std::get<1>(entries.at(0)), "blaze/valid_examples");
85+
EXPECT_EQ(std::get<2>(entries.at(0)),
86+
"Only include instances in the `examples` array that validate "
87+
"against the schema");
88+
EXPECT_EQ(std::get<3>(entries.at(0)),
89+
R"TXT(Invalid example instance at index 0
90+
The value was expected to be of type string but it was of type integer
91+
at instance location ""
92+
at evaluate path "/type"
93+
)TXT");
94+
}
95+
96+
TEST(Linter, valid_examples_error_message_with_id_nested) {
97+
sourcemeta::core::SchemaTransformer bundle;
98+
bundle.add<sourcemeta::blaze::ValidExamples>(
99+
sourcemeta::blaze::default_schema_compiler);
100+
101+
const auto schema{sourcemeta::core::parse_json(R"JSON({
102+
"$schema": "https://json-schema.org/draft/2020-12/schema",
103+
"$id": "https://example.com",
104+
"properties": {
105+
"foo": {
106+
"examples": [ 1 ],
107+
"type": "string"
108+
}
109+
}
110+
})JSON")};
111+
112+
std::vector<std::tuple<sourcemeta::core::Pointer, std::string, std::string,
113+
std::string>>
114+
entries;
115+
const bool result =
116+
bundle.check(schema, sourcemeta::core::schema_official_walker,
117+
sourcemeta::core::schema_official_resolver,
118+
[&entries](const auto &pointer, const auto &name,
119+
const auto &message, const auto &description) {
120+
entries.emplace_back(pointer, name, message, description);
121+
});
122+
123+
EXPECT_FALSE(result);
124+
EXPECT_EQ(entries.size(), 1);
125+
126+
EXPECT_EQ(std::get<0>(entries.at(0)),
127+
sourcemeta::core::Pointer({"properties", "foo"}));
128+
EXPECT_EQ(std::get<1>(entries.at(0)), "blaze/valid_examples");
129+
EXPECT_EQ(std::get<2>(entries.at(0)),
130+
"Only include instances in the `examples` array that validate "
131+
"against the schema");
132+
EXPECT_EQ(std::get<3>(entries.at(0)),
133+
R"TXT(Invalid example instance at index 0
134+
The value was expected to be of type string but it was of type integer
135+
at instance location ""
136+
at evaluate path "/type"
137+
)TXT");
138+
}
139+
140+
TEST(Linter, valid_examples_error_message_with_id_flat) {
141+
sourcemeta::core::SchemaTransformer bundle;
142+
bundle.add<sourcemeta::blaze::ValidExamples>(
143+
sourcemeta::blaze::default_schema_compiler);
144+
145+
const auto schema{sourcemeta::core::parse_json(R"JSON({
146+
"$schema": "https://json-schema.org/draft/2020-12/schema",
147+
"$id": "https://example.com",
148+
"examples": [ 1 ],
149+
"type": "string"
150+
})JSON")};
151+
152+
std::vector<std::tuple<sourcemeta::core::Pointer, std::string, std::string,
153+
std::string>>
154+
entries;
155+
const bool result =
156+
bundle.check(schema, sourcemeta::core::schema_official_walker,
157+
sourcemeta::core::schema_official_resolver,
158+
[&entries](const auto &pointer, const auto &name,
159+
const auto &message, const auto &description) {
160+
entries.emplace_back(pointer, name, message, description);
161+
});
162+
163+
EXPECT_FALSE(result);
164+
EXPECT_EQ(entries.size(), 1);
165+
166+
EXPECT_EQ(std::get<0>(entries.at(0)), sourcemeta::core::Pointer({}));
167+
EXPECT_EQ(std::get<1>(entries.at(0)), "blaze/valid_examples");
168+
EXPECT_EQ(std::get<2>(entries.at(0)),
169+
"Only include instances in the `examples` array that validate "
170+
"against the schema");
171+
EXPECT_EQ(std::get<3>(entries.at(0)),
172+
R"TXT(Invalid example instance at index 0
173+
The value was expected to be of type string but it was of type integer
174+
at instance location ""
175+
at evaluate path "/type"
176+
)TXT");
177+
}
178+
58179
TEST(Linter, valid_examples_1) {
59180
sourcemeta::core::SchemaTransformer bundle;
60181
bundle.add<sourcemeta::blaze::ValidExamples>(

0 commit comments

Comments
 (0)