diff --git a/schemars/tests/docs.rs b/schemars/tests/docs.rs index 788140d8..994490da 100644 --- a/schemars/tests/docs.rs +++ b/schemars/tests/docs.rs @@ -54,6 +54,59 @@ enum MyEnum { }, } +#[allow(dead_code)] +#[derive(JsonSchema)] +/** + * + * # This is the struct's title + * + * This is the struct's description. + * + * This is example: + * ```json + * { + * "value": 0, + * "type": "msg" + * } + * ``` + */ +struct MyStructWithInlineCode { + /// # An integer + my_int: i32, +} + +#[allow(dead_code)] +#[derive(JsonSchema)] + +/// This is example: +/// ```json +/// { +/// "value": 0, +/// "type": "msg" +/// } +/// ``` +struct MyStructWithInlineCodeNormal { + /// # An integer + my_int: i32, +} + +#[allow(dead_code)] +#[derive(JsonSchema)] + +/// This is example: +/// +/// | A | B | +/// |---|---| +/// | 1 | 2 | +/// | 3 | 4 | +/// | 5 | 6 | +/// this is last line +/// +struct MyStructWithInlineCodeTable { + /// # An integer + my_int: i32, +} + #[test] fn doc_comments_struct() -> TestResult { test_default_generated_schema::("doc_comments_struct") @@ -70,6 +123,23 @@ fn doc_comments_enum() -> TestResult { test_default_generated_schema::("doc_comments_enum") } +#[test] +fn doc_comments_with_inline_code() -> TestResult { + test_default_generated_schema::("doc_comments_with_inline_code") +} + +#[test] +fn doc_comments_with_inline_code_normal() -> TestResult { + test_default_generated_schema::( + "doc_comments_with_inline_code_normal", + ) +} + +#[test] +fn doc_comments_with_inline_table() -> TestResult { + test_default_generated_schema::("doc_comments_with_inline_table") +} + /// # OverrideDocs struct /// This description should be overridden #[allow(dead_code)] diff --git a/schemars/tests/expected/doc_comments_with_inline_code.json b/schemars/tests/expected/doc_comments_with_inline_code.json new file mode 100644 index 00000000..5cc4f40a --- /dev/null +++ b/schemars/tests/expected/doc_comments_with_inline_code.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "This is the struct's title", + "description": "This is the struct's description.\n\nThis is example:\n ```json\n {\n \"value\": 0,\n \"type\": \"msg\"\n }\n```", + "type": "object", + "required": [ + "my_int" + ], + "properties": { + "my_int": { + "title": "An integer", + "type": "integer", + "format": "int32" + } + } +} \ No newline at end of file diff --git a/schemars/tests/expected/doc_comments_with_inline_code_normal.json b/schemars/tests/expected/doc_comments_with_inline_code_normal.json new file mode 100644 index 00000000..e18ad33b --- /dev/null +++ b/schemars/tests/expected/doc_comments_with_inline_code_normal.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MyStructWithInlineCodeNormal", + "description": "This is example:\n ```json\n {\n \"value\": 0,\n \"type\": \"msg\"\n }\n```", + "type": "object", + "required": [ + "my_int" + ], + "properties": { + "my_int": { + "title": "An integer", + "type": "integer", + "format": "int32" + } + } +} \ No newline at end of file diff --git a/schemars/tests/expected/doc_comments_with_inline_table.json b/schemars/tests/expected/doc_comments_with_inline_table.json new file mode 100644 index 00000000..aaf0b15b --- /dev/null +++ b/schemars/tests/expected/doc_comments_with_inline_table.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MyStructWithInlineCodeTable", + "description": "This is example:\n\n| A | B |\n|---|---|\n| 1 | 2 |\n| 3 | 4 |\n| 5 | 6 | this is last line", + "type": "object", + "required": [ + "my_int" + ], + "properties": { + "my_int": { + "title": "An integer", + "type": "integer", + "format": "int32" + } + } +} \ No newline at end of file diff --git a/schemars/tests/validate_inner.rs b/schemars/tests/validate_inner.rs index 535410f1..64166719 100644 --- a/schemars/tests/validate_inner.rs +++ b/schemars/tests/validate_inner.rs @@ -12,7 +12,7 @@ pub struct Struct<'a> { #[schemars(inner(length(min = 5, max = 100)))] array_str_length: [&'a str; 2], #[schemars(inner(contains(pattern = "substring...")))] - slice_str_contains: &'a[&'a str], + slice_str_contains: &'a [&'a str], #[schemars(inner(regex = "STARTS_WITH_HELLO"))] vec_str_regex: Vec, #[schemars(inner(length(min = 1, max = 100)))] diff --git a/schemars_derive/src/attr/doc.rs b/schemars_derive/src/attr/doc.rs index 0827dc01..207d4f3a 100644 --- a/schemars_derive/src/attr/doc.rs +++ b/schemars_derive/src/attr/doc.rs @@ -25,7 +25,13 @@ fn merge_description_lines(doc: &str) -> Option { let desc = doc .trim() .split("\n\n") - .filter_map(|line| none_if_empty(line.trim().replace('\n', " "))) + .map(|s| s.to_string()) + .collect::>(); + + let desc = desc + .iter() + .map(|paragrah| merge_without_codeblock(paragrah)) + .filter_map(|line| none_if_empty(line.to_string())) .collect::>() .join("\n\n"); none_if_empty(desc) @@ -47,30 +53,22 @@ fn get_doc(attrs: &[Attribute]) -> Option { None }) .collect::>(); - - let mut lines = attrs + let lines = attrs .iter() .flat_map(|a| a.split('\n')) - .map(str::trim) .skip_while(|s| s.is_empty()) + .map(|l| l.to_string()) .collect::>(); - if let Some(&"") = lines.last() { - lines.pop(); - } - + let mut res = strip_without_codeblock(&lines, |l| l.trim().to_string()); // Added for backward-compatibility, but perhaps we shouldn't do this // https://github.com/rust-lang/rust/issues/32088 - if lines.iter().all(|l| l.starts_with('*')) { - for line in lines.iter_mut() { - *line = line[1..].trim() - } - while let Some(&"") = lines.first() { - lines.remove(0); - } + if res.iter().all(|l| l.trim().starts_with('*')) { + res = res.iter().map(|l| l[1..].to_string()).collect::>(); + res = strip_without_codeblock(&res, |l| l.trim().to_string()); }; - none_if_empty(lines.join("\n")) + none_if_empty(res.join("\n")) } fn none_if_empty(s: String) -> Option { @@ -80,3 +78,50 @@ fn none_if_empty(s: String) -> Option { Some(s) } } + +fn strip_without_codeblock(lines: &Vec, func: fn(&str) -> String) -> Vec { + let mut res = vec![]; + let mut in_codeblock = false; + for line in lines { + if line.trim().starts_with("```") { + in_codeblock = !in_codeblock; + } + let l = if in_codeblock { + line.to_string() + } else { + func(line) + }; + res.push(l); + } + while let Some("") = res.first().map(|s| s.as_str()) { + res.remove(0); + } + while let Some("") = res.last().map(|s| s.as_str()) { + res.pop(); + } + res +} + +fn merge_without_codeblock(content: &str) -> String { + let lines = content.lines(); + let mut res = String::new(); + let mut in_codeblock = false; + for line in lines { + let flag = line.trim().starts_with("```"); + if flag { + in_codeblock = !in_codeblock; + } + // other possible Markdown prefix characters + let maybe_markdown = ["#", "-", ">", "|", "*", "["] + .iter() + .any(|p| line.trim().starts_with(p)) + || line.trim().chars().next().map(char::is_numeric) == Some(true); + let prefix = if in_codeblock || flag || maybe_markdown { + "\n" + } else { + " " + }; + res += &(format!("{}{}", prefix, line)); + } + res.trim().to_string() +}