Skip to content

Commit ba7da8b

Browse files
authored
fix: value constraints (#96)
1 parent 0d1d4d9 commit ba7da8b

23 files changed

Lines changed: 1267 additions & 6 deletions

Cargo.lock

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oapi-codegen/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ owo-colors = "4.3.0"
3232
prettyplease = "0.3.0"
3333
proc-macro2 = "1.0.107"
3434
quote = "1.0.47"
35+
regex = { version = "1.13.1", default-features = false, features = ["std", "perf", "unicode"] }
3536
serde = { version = "1.0.229", features = ["derive"] }
3637
serde_json = "1.0.151"
3738
serde_yaml = "0.9.34"

crates/oapi-codegen/src/console.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,31 @@ fn hints_for(err: &Error) -> Vec<String> {
259259
"Inside a schema, only a same-document ref resolves. This covers a property, `items`, `additionalProperties`, `allOf`, and a union member.".to_owned(),
260260
];
261261
}
262+
Error::UnsupportedSchema { reason, .. } if reason.contains("does not reach the type") => {
263+
if reason.contains("Properties") {
264+
return vec![
265+
"`minProperties` and `maxProperties` read a free-form map. A schema that names its properties fixes the count already.".to_owned(),
266+
];
267+
}
268+
if reason.contains("uniqueItems") {
269+
return vec![
270+
"`uniqueItems` reads a list of numbers, strings, or booleans. A list of models cannot always compare.".to_owned(),
271+
];
272+
}
273+
return vec![
274+
"A `format` can name a type that is no longer a string, such as a date or a UUID. Drop the `format` to keep the string and the rule.".to_owned(),
275+
"An `x-rust-type` does the same. The rules of that type are its own.".to_owned(),
276+
];
277+
}
278+
Error::UnsupportedSchema { reason, .. } if reason.contains("is not above zero") => {
279+
return vec!["JSON Schema asks for a `multipleOf` above zero. A step of zero divides by zero.".to_owned()];
280+
}
281+
Error::UnsupportedSchema { reason, .. } if reason.contains("does not fit `i32`") => {
282+
return vec![
283+
"A bound must fit the type the `format` chooses. `int32` holds -2147483648 to 2147483647. Drop the `format` to get `i64`."
284+
.to_owned(),
285+
];
286+
}
262287
Error::UnsupportedSchema { reason, .. } if reason.contains("`enum`") => {
263288
return vec![
264289
"An `enum` names each value once. Remove the repeat.".to_owned(),
@@ -526,6 +551,42 @@ mod tests {
526551
);
527552
}
528553

554+
/// Each constraint fault must reach its own hint. The arms read the reason
555+
/// text, so a broad one can take a message meant for a later arm and send the
556+
/// reader to the wrong fix.
557+
#[test]
558+
fn each_constraint_fault_reaches_its_own_hint() {
559+
let cases = [
560+
("the `multipleOf` value `0` is not above zero", "divides by zero"),
561+
("the `multipleOf` value `5000000000` does not fit `i32`", "-2147483648"),
562+
("the `minimum` value `-5000000000` does not fit `i32`", "-2147483648"),
563+
(
564+
"the `pattern` rule does not reach the type this field holds",
565+
"Drop the `format`",
566+
),
567+
(
568+
"the `minProperties` rule does not reach the type this field holds",
569+
"free-form map",
570+
),
571+
(
572+
"the `uniqueItems` rule does not reach the type this field holds",
573+
"numbers, strings, or booleans",
574+
),
575+
("the `enum` gives `1` more than once", "Remove the repeat"),
576+
];
577+
for (reason, wanted) in cases {
578+
let err = Error::UnsupportedSchema {
579+
path: "field".to_owned(),
580+
reason: reason.to_owned(),
581+
};
582+
let hints = hints_for(&err);
583+
assert!(
584+
hints.iter().any(|hint| return hint.contains(wanted)),
585+
"`{reason}` should reach a hint holding `{wanted}`, got: {hints:?}",
586+
);
587+
}
588+
}
589+
529590
#[test]
530591
fn undeclared_path_parameter_hint_guides_the_fix() {
531592
let err = Error::UndeclaredPathParameter {

crates/oapi-codegen/src/deps.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ pub fn required_dependencies(code: &str) -> Vec<Dependency> {
163163
if has("uuid::") {
164164
deps.push(with_features("uuid", true, vec!["serde"]));
165165
}
166+
if has("regex::") {
167+
deps.push(with_features("regex", false, vec!["std", "perf", "unicode"]));
168+
}
166169
if has("http::") {
167170
deps.push(plain("http"));
168171
}

0 commit comments

Comments
 (0)