From 4d8452fb487b2919ec365c1d70ee7a8d590ead2c Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Tue, 13 Jan 2026 10:12:02 -0800 Subject: [PATCH 1/3] OpenAPI operation summaries should not end in a period --- Cargo.toml | 2 +- README.md | 5 ++++- src/lib.rs | 28 ++++++++++++++++++++++++++++ src/tests/errors.json | 1 + src/tests/errors.out | 5 ++++- 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 267128a..678d4ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "openapi-lint" description = "Validate an OpenAPI schema against some rules" -version = "0.4.0" +version = "0.5.0" edition = "2021" license = "Apache-2.0" repository = "https://github.com/oxidecomputer/openapi-lint/" diff --git a/README.md b/README.md index 0f8750f..f18a6bf 100644 --- a/README.md +++ b/README.md @@ -200,4 +200,7 @@ easy to accidentally allow internally-relevant documentation leak out as externally-visible in the OpenAPI document. It's not possible to simply infer this from text alone, but we do look for shibboleths such as a Rust path delimeter (`::`) and bracketed expressions with no subsequent parentheses -(`[title](http://link.dest)` being reasonable). \ No newline at end of file +(`[title](http://link.dest)` being reasonable). + +Additionally, operation summaries (the OpenAPI `summary` field) should be +short phrases and **should not end with a period**. diff --git a/src/lib.rs b/src/lib.rs index 3fb5409..aa8537e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,9 @@ impl Validator { let responses = spec .operations() .flat_map(|(_, _, op)| self.validate_operation_response(spec, op)); + let op_summaries = spec + .operations() + .filter_map(|path_method_op| self.validate_operation_summary(path_method_op)); let op_docs = if external { spec.operations() .flat_map(|(_, _, op)| op.description.as_ref().and_then(|s| check_doc_string(s))) @@ -77,6 +80,7 @@ impl Validator { } else { Vec::new() }; + let named_schemas = spec.components.iter().flat_map(|components| { components .schemas @@ -90,6 +94,7 @@ impl Validator { .chain(parameters) .chain(responses) .chain(named_schemas) + .chain(op_summaries) .chain(op_docs) .collect() } @@ -261,6 +266,29 @@ impl Validator { } } + fn validate_operation_summary( + &self, + path_method_op: (&str, &str, &Operation), + ) -> Option { + let (path, method, op) = path_method_op; + + const INFO: &str = "For more info, see \ + https://github.com/oxidecomputer/openapi-lint#rust-documentation"; + + if let Some(summary) = &op.summary { + // summaries should be short phrases and should not end with a period + if summary.trim_end().ends_with('.') { + let operation_id = op.operation_id.as_deref().unwrap_or(""); + return Some(format!( + "The operation for {} {} (operation_id: \"{}\") has a summary that ends with a period; summaries should not end with a period.\n{}", + path, method, operation_id, INFO, + )); + } + } + + None + } + fn validate_operation_parameters(&self, spec: &OpenAPI, op: &Operation) -> Vec { const INFO: &str = "For more info, see \ https://github.com/oxidecomputer/openapi-lint#naming"; diff --git a/src/tests/errors.json b/src/tests/errors.json index 64a1294..0d13661 100644 --- a/src/tests/errors.json +++ b/src/tests/errors.json @@ -48,6 +48,7 @@ }, "/hardware/racks": { "get": { + "summary": "List racks in the system.", "description": "List racks in the system.", "operationId": "hardware_racks_get", "parameters": [ diff --git a/src/tests/errors.out b/src/tests/errors.out index d2cf460..6f885f4 100644 --- a/src/tests/errors.out +++ b/src/tests/errors.out @@ -5681,4 +5681,7 @@ The return type for unit_return was a trivial null. For more info, see https://github.com/oxidecomputer/openapi-lint#trivial-null-response The type "fake_id_sort_mode" has a name that is not PascalCase; to rename it add #[serde(rename = "FakeIdSortMode")] -For more info, see https://github.com/oxidecomputer/openapi-lint#naming \ No newline at end of file +For more info, see https://github.com/oxidecomputer/openapi-lint#naming + +The operation for /hardware/racks get (operation_id: "hardware_racks_get") has a summary that ends with a period; summaries should not end with a period. +For more info, see https://github.com/oxidecomputer/openapi-lint#rust-documentation \ No newline at end of file From a9bad35ce9fa16a95810c4339cf8bbe7a828f8cb Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Tue, 13 Jan 2026 12:21:01 -0800 Subject: [PATCH 2/3] external API only --- src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index aa8537e..a67c7b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,9 +70,13 @@ impl Validator { let responses = spec .operations() .flat_map(|(_, _, op)| self.validate_operation_response(spec, op)); - let op_summaries = spec - .operations() - .filter_map(|path_method_op| self.validate_operation_summary(path_method_op)); + let op_summaries = if external { + spec.operations() + .filter_map(|path_method_op| self.validate_operation_summary(path_method_op)) + .collect() + } else { + Vec::new() + }; let op_docs = if external { spec.operations() .flat_map(|(_, _, op)| op.description.as_ref().and_then(|s| check_doc_string(s))) From e6e27172880d3cf6938f920515860f6c55e294c8 Mon Sep 17 00:00:00 2001 From: "Adam H. Leventhal" Date: Tue, 13 Jan 2026 14:04:14 -0800 Subject: [PATCH 3/3] verbosity level up --- src/lib.rs | 4 +++- src/tests/errors.out | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a67c7b2..e453e36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -284,7 +284,9 @@ impl Validator { if summary.trim_end().ends_with('.') { let operation_id = op.operation_id.as_deref().unwrap_or(""); return Some(format!( - "The operation for {} {} (operation_id: \"{}\") has a summary that ends with a period; summaries should not end with a period.\n{}", + "The operation for {} {} (operation_id: {}) has a summary \ + (first line of doc comment) that ends with a period; \ + summaries should not end with a period.\n{}", path, method, operation_id, INFO, )); } diff --git a/src/tests/errors.out b/src/tests/errors.out index 6f885f4..4e1338e 100644 --- a/src/tests/errors.out +++ b/src/tests/errors.out @@ -5683,5 +5683,5 @@ For more info, see https://github.com/oxidecomputer/openapi-lint#trivial-null-re The type "fake_id_sort_mode" has a name that is not PascalCase; to rename it add #[serde(rename = "FakeIdSortMode")] For more info, see https://github.com/oxidecomputer/openapi-lint#naming -The operation for /hardware/racks get (operation_id: "hardware_racks_get") has a summary that ends with a period; summaries should not end with a period. +The operation for /hardware/racks get (operation_id: hardware_racks_get) has a summary (first line of doc comment) that ends with a period; summaries should not end with a period. For more info, see https://github.com/oxidecomputer/openapi-lint#rust-documentation \ No newline at end of file