1use crate::{BodyData, Error, Position, parsing::ParseContext};
2use ::markdown::mdast::{self, Node};
3use ::reqmd_http as http;
4
5#[derive(Debug, Clone, PartialEq, Eq, Default)]
13#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
14#[cfg_attr(feature = "serde", serde(default))]
15pub struct HttpData {
16 pub title: Option<String>,
20
21 pub description: Option<String>,
24
25 pub method: http::Method,
28
29 pub path: http::Path,
31
32 pub query: http::QueryString,
35
36 pub headers: http::Headers,
38
39 pub body: BodyData,
42
43 pub position: Position,
45}
46
47impl HttpData {
48 pub(crate) fn try_collect(ctx: &ParseContext<'_>) -> Result<Vec<Self>, Error> {
49 let mut data_set = Vec::new();
50 let mut iter = ctx.root.children.iter().peekable();
51 let mut prior_heading = None;
52
53 while let Some(child) = iter.next() {
54 match child {
55 Node::Code(block) if is_http_code(block) => {
56 let mut data = parser::parse(&block.value)?;
57 data.position = Position::try_from(block)?;
58
59 if let Some(Node::Code(block)) = iter.peek()
60 && !is_http_code(block)
61 {
62 iter.next(); data.body = BodyData::from(block.clone());
64 data.position.extend(&Position::try_from(block)?);
65 }
66
67 if let Some(heading) = prior_heading.take() {
68 let position = Position::try_from(heading)?;
69 let title = position
70 .find_substring(ctx.input)?
71 .trim_start_matches('#')
72 .trim();
73 data.title = Some(title.into());
74
75 if let Some(range) = position.range_between(&data.position) {
76 let desc = ctx.input[range].trim();
77 if !desc.is_empty() {
78 data.description = Some(desc.into());
79 }
80 }
81
82 data.position.extend(&position);
83 }
84
85 data_set.push(data);
86 }
87 Node::Heading(heading) => {
88 prior_heading = Some(heading);
89 }
90 _ => continue,
91 }
92 }
93
94 Ok(data_set)
95 }
96}
97
98fn is_http_code(code: &mdast::Code) -> bool {
99 code.lang.as_deref() == Some("http")
100}
101
102mod parser;
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::Point;
108 use crate::support::fixtures::post_widget_parse_context as ctx;
109
110 #[rstest::rstest]
111 fn collect_from_mdast_works(ctx: ParseContext<'static>) {
112 let data = HttpData::try_collect(&ctx).unwrap();
113 assert_eq!(data.len(), 1);
114 let http = data.first().unwrap();
115 dbg!(&http);
116 assert_eq!(http.method, http::Method::Post);
117 assert_eq!(http.path, http::Path::from("/widgets"));
118 assert_eq!(http.query.first("foo"), Some("bar"));
119 assert_eq!(http.query.first("rofl"), Some("copter"));
120 assert_eq!(http.headers.first("authorization"), Some("Bearer abcd1234"));
121 assert_eq!(http.body.lang.as_deref(), Some("json"));
122 assert_eq!(http.body.meta.as_deref(), Some("http-body"));
123 assert_eq!(http.title.as_deref(), Some("Post Widgets"));
124 assert_eq!(
125 http.description.as_deref(),
126 Some("I've often wondered what this text is called")
127 );
128 assert_eq!(
129 http.body.position.as_ref(),
130 Some(&Position {
131 start: Point {
132 line: 19,
133 column: 1,
134 offset: 232
135 },
136 end: Point {
137 line: 24,
138 column: 4,
139 offset: 305
140 },
141 })
142 );
143 assert_eq!(
144 http.body.content.text(),
145 Some("{\n \"name\": \"XFox\",\n \"desc\": \"Wonderful widget!\"\n}")
146 );
147 assert_eq!(
148 http.position,
149 Position {
150 start: Point {
151 line: 8,
152 column: 1,
153 offset: 80
154 },
155 end: Point {
156 line: 24,
157 column: 4,
158 offset: 305
159 },
160 }
161 );
162 }
163}