1#[cfg(feature = "rspc")]
2use rspc::Type;
3
4use serde::{Deserialize, Serialize};
5
6use std::borrow::Cow;
7
8use crate::error::{ErrorWithPartial, ParseError};
9
10#[allow(dead_code)]
11#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
12pub enum WithDefault<T> {
13 Some(T),
14 Default(T),
15}
16
17impl<T: Clone> Clone for WithDefault<T> {
18 fn clone(&self) -> Self {
19 match self {
20 WithDefault::Some(value) => WithDefault::Some(value.clone()),
21 WithDefault::Default(default_val) => WithDefault::Default(default_val.clone()),
22 }
23 }
24}
25
26impl<T: std::fmt::Debug> std::fmt::Debug for WithDefault<T> {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 match self {
29 WithDefault::Some(value) => f.debug_tuple("Some").field(value).finish(),
30 WithDefault::Default(value) => f.debug_tuple("Default").field(value).finish(),
31 }
32 }
33}
34
35impl<T> WithDefault<T> {
36 pub fn new_with_default(value: Option<T>, default: T) -> Self {
37 match value {
38 Some(value) => WithDefault::Some(value),
39 None => WithDefault::Default(default),
40 }
41 }
42}
43
44impl<T> From<T> for WithDefault<T> {
45 fn from(value: T) -> Self {
46 WithDefault::Some(value)
47 }
48}
49
50impl<T> From<Option<T>> for WithDefault<T>
51where
52 WithDefault<T>: Default,
53{
54 fn from(value: Option<T>) -> Self {
55 match value {
56 Some(t) => WithDefault::Some(t),
57 _ => WithDefault::default(),
58 }
59 }
60}
61
62impl<T> From<WithDefault<T>> for Option<T> {
63 fn from(value: WithDefault<T>) -> Option<T> {
64 match value {
65 WithDefault::Some(val) => Some(val),
66 _ => None,
67 }
68 }
69}
70
71impl<T> Default for WithDefault<T>
72where
73 T: Default,
74{
75 fn default() -> Self {
76 WithDefault::Default(T::default())
77 }
78}
79
80impl<T: ToOwned<Owned = T>> WithDefault<T> {
81 #[allow(dead_code)]
82 pub fn get_cloned_or_computed(&self) -> T {
83 match self {
84 WithDefault::Some(val) => val.to_owned(),
85 WithDefault::Default(val) => val.to_owned(),
86 }
87 }
88}
89
90impl<T: Clone> WithDefault<T> {
91 #[allow(dead_code)]
92 pub fn get_ref_or_default(&self) -> Cow<T> {
93 match self {
94 WithDefault::Some(val) => Cow::Borrowed(val),
95 WithDefault::Default(val) => Cow::Borrowed(val),
96 }
97 }
98}
99
100impl<T> WithDefault<T> {
101 #[allow(dead_code)]
102 pub fn is_default(&self) -> bool {
103 !matches!(self, WithDefault::Some(_))
104 }
105
106 #[allow(dead_code)]
107 pub fn unwrap_or_default(self) -> T {
108 match self {
109 WithDefault::Some(value) => value,
110 WithDefault::Default(default) => default,
111 }
112 }
113}
114
115impl<T: Clone> WithDefault<T> {
116 pub fn get_or_default(&self) -> T {
117 match self {
118 WithDefault::Some(value) => value.clone(),
119 WithDefault::Default(default) => default.clone(),
120 }
121 }
122}
123
124impl<T: std::cmp::PartialOrd> PartialOrd for WithDefault<T> {
125 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
126 let first_ref = match self {
127 WithDefault::Default(default) => default,
128 WithDefault::Some(value) => value,
129 };
130
131 let second_ref = match other {
132 WithDefault::Default(default) => default,
133 WithDefault::Some(value) => value,
134 };
135
136 first_ref.partial_cmp(second_ref)
137 }
138}
139
140impl<T: std::cmp::PartialEq> PartialEq for WithDefault<T> {
141 fn eq(&self, other: &Self) -> bool {
142 match (self, other) {
143 (WithDefault::Some(value), WithDefault::Some(other_value)) => value.eq(other_value),
144 (WithDefault::Default(value), WithDefault::Default(other_value)) => {
145 value.eq(other_value)
146 }
147 _ => false,
148 }
149 }
150}
151
152#[allow(clippy::upper_case_acronyms)]
153#[derive(Debug, Clone, PartialEq, Eq, Default)]
154#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
155#[cfg_attr(feature = "rspc", derive(Type))]
156pub enum HttpMethod {
157 #[default]
158 GET,
159 POST,
160 PUT,
161 PATCH,
162 DELETE,
163 HEAD,
164 TRACE,
165 OPTIONS,
166 CONNECT,
167 CUSTOM(String),
168}
169
170impl ToString for HttpMethod {
171 fn to_string(&self) -> String {
172 let result = match self {
173 HttpMethod::GET => "GET",
174 HttpMethod::POST => "POST",
175 HttpMethod::PUT => "PUT",
176 HttpMethod::PATCH => "PATCH",
177 HttpMethod::DELETE => "DELETE",
178 HttpMethod::HEAD => "HEAD",
179 HttpMethod::TRACE => "TRACE",
180 HttpMethod::OPTIONS => "OPTIONS",
181 HttpMethod::CONNECT => "CONNECT",
182 HttpMethod::CUSTOM(string) => string,
183 };
184 result.to_string()
185 }
186}
187
188impl HttpMethod {
189 pub fn new(s: &str) -> Self {
190 match s {
191 "GET" => HttpMethod::GET,
192 "PUT" => HttpMethod::PUT,
193 "POST" => HttpMethod::POST,
194 "PATCH" => HttpMethod::PATCH,
195 "DELETE" => HttpMethod::DELETE,
196 "HEAD" => HttpMethod::HEAD,
197 "OPTIONS" => HttpMethod::OPTIONS,
198 "CONNECT " => HttpMethod::CONNECT,
199 "TRACE" => HttpMethod::TRACE,
200 custom => HttpMethod::CUSTOM(custom.to_string()),
201 }
202 }
203}
204
205#[derive(PartialEq, Debug, Clone)]
206#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
207#[cfg_attr(feature = "rspc", derive(Type))]
208pub enum RequestTarget {
209 RelativeOrigin { uri: String },
210 Absolute { uri: String },
211 Asterisk,
212 InvalidTarget(String),
213 Missing,
214}
215
216#[derive(Debug, Clone, PartialEq, Eq)]
217#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
218#[cfg_attr(feature = "rspc", derive(Type))]
219pub enum SettingsEntry {
220 NoRedirect,
221 NoLog,
222 NoCookieJar,
223 NameEntry(String),
224}
225
226#[derive(Debug, Clone, PartialEq, Eq)]
227#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
228#[cfg_attr(feature = "rspc", derive(Type))]
229pub struct RequestSettings {
230 pub no_redirect: Option<bool>,
231 pub no_log: Option<bool>,
232 pub no_cookie_jar: Option<bool>,
233}
234
235impl Default for RequestSettings {
236 fn default() -> Self {
237 RequestSettings {
238 no_redirect: Some(false),
239 no_log: Some(false),
240 no_cookie_jar: Some(false),
241 }
242 }
243}
244
245impl RequestSettings {
246 pub fn set_entry(&mut self, entry: &SettingsEntry) {
247 match entry {
248 SettingsEntry::NoLog => self.no_log = Some(true),
249 SettingsEntry::NoRedirect => self.no_redirect = Some(true),
250 SettingsEntry::NoCookieJar => self.no_cookie_jar = Some(true),
251 SettingsEntry::NameEntry(_name) => (),
253 }
254 }
255
256 #[allow(dead_code)]
257 pub fn serialized(&self) -> String {
259 let mut result = String::new();
260 if let Some(true) = self.no_redirect {
261 result.push_str("# @no-redirect\n");
262 }
263 if let Some(true) = self.no_log {
264 result.push_str("# @no-log\n");
265 }
266 if let Some(true) = self.no_cookie_jar {
267 result.push_str("# @no-cookie-jar\n");
268 }
269 result
270 }
271}
272
273#[derive(Debug, Clone, PartialEq, Eq)]
274#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
275#[cfg_attr(feature = "rspc", derive(Type))]
276pub struct DispositionField {
277 pub name: String,
278 pub filename: Option<String>,
279 pub filename_star: Option<String>,
280}
281
282impl DispositionField {
283 pub fn new<S>(name: S) -> Self
284 where
285 S: Into<String>,
286 {
287 DispositionField {
288 name: name.into(),
289 filename: None,
290 filename_star: None,
291 }
292 }
293 pub fn new_with_filename<S, T>(name: S, filename: Option<T>) -> Self
294 where
295 S: Into<String>,
296 T: Into<String>,
297 {
298 DispositionField {
299 name: name.into(),
300 filename: filename.map(|t| t.into()),
301 filename_star: None,
302 }
303 }
304}
305
306#[derive(PartialEq, Debug, Clone)]
307#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
308#[cfg_attr(feature = "rspc", derive(Type))]
309pub struct Multipart {
310 pub data: DataSource<String>,
311 pub disposition: DispositionField,
312 pub headers: Vec<Header>,
313}
314
315#[derive(Debug, Clone, PartialEq, Eq)]
316#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
317#[cfg_attr(feature = "rspc", derive(Type))]
318pub enum DataSource<T> {
319 Raw(T),
320 FromFilepath(String),
321}
322
323impl ToString for DataSource<String> {
324 fn to_string(&self) -> String {
325 match self {
326 Self::Raw(str) => str.to_string(),
327 Self::FromFilepath(path) => format!("< {}", path),
328 }
329 }
330}
331
332#[derive(Debug, Clone, PartialEq, Eq)]
333#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
334#[cfg_attr(feature = "rspc", derive(Type))]
335pub struct UrlEncodedParam {
336 pub key: String,
337 pub value: String,
338}
339
340impl UrlEncodedParam {
341 pub fn new<S, T>(key: S, value: T) -> Self
342 where
343 S: Into<String>,
344 T: Into<String>,
345 {
346 UrlEncodedParam {
347 key: key.into(),
348 value: value.into(),
349 }
350 }
351}
352
353#[derive(Debug, Clone, PartialEq)]
354#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
355#[cfg_attr(feature = "rspc", derive(Type))]
356pub enum RequestBody {
357 None,
358
359 Multipart {
360 boundary: String,
361 parts: Vec<Multipart>,
362 },
363
364 UrlEncoded {
365 url_encoded_params: Vec<UrlEncodedParam>,
366 },
367
368 Raw {
369 data: DataSource<String>,
370 },
371}
372
373impl RequestBody {
374 #[allow(dead_code)]
375 pub fn is_present(&self) -> bool {
377 if let RequestBody::None = self {
378 return false;
379 }
380 true
381 }
382}
383
384impl ToString for RequestBody {
385 fn to_string(&self) -> String {
386 match self {
387 RequestBody::None => "".to_string(),
388 RequestBody::UrlEncoded { url_encoded_params } => {
389 let mut serializer = url::form_urlencoded::Serializer::new(String::new());
390 url_encoded_params.iter().for_each(|param| {
391 serializer.append_pair(¶m.key, ¶m.value);
392 });
393 serializer.finish()
394 }
395 RequestBody::Multipart { boundary, parts } => {
396 let mut multipart_res = String::new();
397
398 for part in parts.iter() {
400 multipart_res.push_str(&format!("--{}\n", boundary));
401 multipart_res.push_str(&format!(
402 "Content-Disposition: form-data; name=\"{}\"",
403 part.disposition.name
404 ));
405
406 if let Some(ref filename) = part.disposition.filename {
407 multipart_res.push_str(&format!("; filename=\"{}\"", filename));
408 }
409
410 if let Some(ref filename_star) = part.disposition.filename_star {
411 multipart_res.push_str(&format!("; filename*=\"{}\"", filename_star));
412 }
413 multipart_res.push('\n');
414 for header in part.headers.iter() {
415 multipart_res.push_str(&format!("{}: {}", header.key, header.value));
416 multipart_res.push('\n');
417 }
418 multipart_res.push('\n');
419 let content = match part.data {
420 DataSource::Raw(ref str) => str.to_string(),
421 DataSource::FromFilepath(ref path) => format!("< {}", path),
422 };
423 multipart_res.push_str(&content);
424 multipart_res.push('\n');
425 }
426 multipart_res.push_str(&format!("--{}--", boundary));
427 multipart_res
428 }
429 RequestBody::Raw { data } => data.to_string(),
430 }
431 }
432}
433
434impl RequestTarget {
435 pub fn is_missing(&self) -> bool {
436 matches!(self, RequestTarget::Missing)
437 }
438
439 pub fn parse(value: &str) -> Result<RequestTarget, ParseError> {
440 if value.is_empty() {
441 return Ok(RequestTarget::Missing);
442 }
443
444 if value == "*" {
445 return Ok(RequestTarget::Asterisk);
446 }
447 match value.parse::<http::Uri>() {
448 Ok(uri) => {
449 if let Some(_authority) = uri.authority() {
451 Ok(RequestTarget::Absolute {
452 uri: value.to_string(),
453 })
454 } else {
455 Ok(RequestTarget::RelativeOrigin {
456 uri: value.to_string(),
457 })
458 }
459 }
460 Err(_err) => {
465 let fixed_value = format!("http://{}", value);
466 match fixed_value.parse::<http::Uri>() {
467 Ok(_uri) => Ok(RequestTarget::Absolute {
468 uri: value.to_string(),
469 }),
470 _ => Err(ParseError::InvalidRequestUrl(value.to_string())),
471 }
472 }
473 }
474 }
475
476 #[allow(dead_code)]
477 pub fn has_scheme(&self) -> bool {
479 match self {
480 RequestTarget::Asterisk => false,
481 RequestTarget::Absolute { uri, .. } | RequestTarget::RelativeOrigin { uri, .. } => uri
482 .parse::<http::Uri>()
483 .map_or(false, |uri| uri.scheme().is_some()),
484 RequestTarget::InvalidTarget(_) => false,
485 RequestTarget::Missing => false,
486 }
487 }
488}
489
490#[derive(PartialEq, Debug, Clone)]
491#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
492#[cfg_attr(feature = "rspc", derive(Type))]
493pub struct Header {
494 pub key: String,
495 pub value: String,
496}
497
498impl Header {
499 #[allow(dead_code)]
500 pub fn new<S: Into<String>, T: Into<String>>(key: S, value: T) -> Self {
502 Header {
503 key: key.into(),
504 value: value.into(),
505 }
506 }
507}
508
509impl ToString for Header {
510 fn to_string(&self) -> String {
511 format!("{}: {}", self.key, self.value)
512 }
513}
514
515#[derive(PartialEq, Debug, Clone)]
516#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
517#[cfg_attr(feature = "rspc", derive(Type))]
518pub enum HttpRestFileExtension {
519 Http,
520 Rest,
521}
522
523impl HttpRestFileExtension {
524 pub fn get_extension(&self) -> String {
525 match self {
526 HttpRestFileExtension::Http => ".http".to_string(),
527 HttpRestFileExtension::Rest => ".rest".to_string(),
528 }
529 }
530
531 pub fn get_default_extension() -> String {
532 HttpRestFileExtension::Http.get_extension()
533 }
534}
535
536impl std::fmt::Display for HttpRestFileExtension {
537 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
538 match self {
539 HttpRestFileExtension::Http => f.write_str("http"),
540 HttpRestFileExtension::Rest => f.write_str("rest"),
541 }
542 }
543}
544
545impl HttpRestFileExtension {
546 pub fn from_path(path: &std::path::Path) -> Option<Self> {
547 match path.extension().and_then(|os_str| os_str.to_str()) {
548 Some("http") => Some(HttpRestFileExtension::Http),
549 Some("rest") => Some(HttpRestFileExtension::Rest),
550 _ => None,
551 }
552 }
553}
554
555#[derive(PartialEq, Debug)]
556pub struct HttpRestFile {
557 pub requests: Vec<Request>,
558 pub errs: Vec<ErrorWithPartial>,
559 pub path: Box<std::path::PathBuf>,
560 pub extension: Option<HttpRestFileExtension>,
561}
562
563#[derive(PartialEq, Debug, Clone, Eq)]
564#[cfg_attr(feature = "rspc", derive(Type))]
565#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
566pub enum PreRequestScript {
567 FromFilepath(String),
568 Script(String),
569}
570
571impl ToString for PreRequestScript {
572 fn to_string(&self) -> String {
573 match self {
574 PreRequestScript::FromFilepath(path) => format!("< {}", path),
575 PreRequestScript::Script(script) => {
576 format!("< {{%{}%}}", script)
577 }
578 }
579 }
580}
581
582#[derive(PartialEq, Debug, Clone, Eq)]
583#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
584#[cfg_attr(feature = "rspc", derive(Type))]
585pub enum ResponseHandler {
586 FromFilepath(String),
587 Script(String),
588}
589
590#[derive(PartialEq, Debug, Clone, Eq)]
591#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
592#[cfg_attr(feature = "rspc", derive(Type))]
593pub enum SaveResponse {
595 NewFileIfExists(std::path::PathBuf),
598 RewriteFile(std::path::PathBuf),
600}
601
602#[derive(PartialEq, Debug)]
603#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
604pub struct Request {
605 pub name: Option<String>,
606 pub comments: Vec<Comment>,
607 pub request_line: RequestLine,
608 pub headers: Vec<Header>,
609 pub body: RequestBody,
610 pub settings: RequestSettings,
611 pub pre_request_script: Option<PreRequestScript>,
612 pub response_handler: Option<ResponseHandler>,
613 pub save_response: Option<SaveResponse>,
614}
615
616impl Default for Request {
617 fn default() -> Self {
618 Request {
619 name: None,
620 comments: vec![],
621 request_line: RequestLine::default(),
622 headers: vec![],
623 body: RequestBody::None,
624 settings: RequestSettings::default(),
625 pre_request_script: None,
626 response_handler: None,
627 save_response: None,
628 }
629 }
630}
631
632#[derive(PartialEq, Debug, Clone)]
633#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
634#[cfg_attr(feature = "rspc", derive(Type))]
635pub enum CommentKind {
636 DoubleSlash,
638 RequestSeparator,
640 SingleTag,
642}
643
644impl CommentKind {
645 pub fn string_repr(&self) -> &str {
646 match self {
647 Self::DoubleSlash => "//",
648 Self::RequestSeparator => "###",
649 Self::SingleTag => "#",
650 }
651 }
652}
653
654impl std::str::FromStr for CommentKind {
655 type Err = ParseError;
656 fn from_str(s: &str) -> Result<Self, Self::Err> {
657 match s {
658 "//" => Ok(Self::DoubleSlash),
659 "###" => Ok(Self::RequestSeparator),
660 "#" => Ok(Self::SingleTag),
661 _ => Err(ParseError::InvalidCommentStart(s.to_string())),
662 }
663 }
664}
665
666#[derive(PartialEq, Debug, Clone)]
667#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
668#[cfg_attr(feature = "rspc", derive(Type))]
669pub struct Comment {
670 pub value: String,
671 pub kind: CommentKind,
672}
673
674impl ToString for Comment {
675 fn to_string(&self) -> String {
676 match self.kind {
677 CommentKind::SingleTag => format!("# {}", self.value),
678 CommentKind::DoubleSlash => format!("// {}", self.value),
679 CommentKind::RequestSeparator => format!("### {}", self.value),
680 }
681 }
682}
683
684#[derive(PartialEq, Debug, Clone, Eq)]
685#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
686#[cfg_attr(feature = "rspc", derive(Type))]
687pub struct HttpVersion {
688 pub major: u32,
689 pub minor: u32,
690}
691
692impl Default for HttpVersion {
693 fn default() -> Self {
694 HttpVersion { major: 1, minor: 1 }
695 }
696}
697
698impl std::str::FromStr for HttpVersion {
699 type Err = ParseError;
700 fn from_str(s: &str) -> Result<Self, Self::Err> {
701 if !s.starts_with("HTTP/") {
702 return Err(ParseError::InvalidHttpVersion(s.to_string()));
703 }
704 let s = if s.contains(' ') {
707 s.split(' ').next().unwrap_or("")
708 } else {
709 s
710 };
711 let rest = &s[5..].to_string();
712 let mut split = rest.split('.');
713 let major = split.next().map(|v| v.parse::<u32>());
714 let minor = split.next().map(|v| v.parse::<u32>()).unwrap_or(Ok(0));
716 match (major, minor) {
717 (Some(Ok(major)), Ok(minor)) => Ok(HttpVersion { major, minor }),
718 _ => Err(ParseError::InvalidHttpVersion(s.to_string())),
719 }
720 }
721}
722
723impl std::fmt::Display for HttpVersion {
724 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
725 write!(f, "HTTP/{}.{}", self.major, self.minor)
726 }
727}
728
729#[derive(PartialEq, Debug, Clone)]
730#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
731pub struct RequestLine {
732 pub method: WithDefault<HttpMethod>,
733 pub target: RequestTarget,
734 pub http_version: WithDefault<HttpVersion>,
735}
736
737impl From<&str> for RequestTarget {
738 fn from(value: &str) -> RequestTarget {
739 match RequestTarget::parse(value) {
740 Ok(t) => t,
741 Err(_err) => RequestTarget::InvalidTarget(value.to_string()),
742 }
743 }
746}
747
748impl Default for RequestLine {
749 fn default() -> RequestLine {
750 RequestLine {
751 method: WithDefault::Default(HttpMethod::GET),
752 target: RequestTarget::from(""),
753 http_version: WithDefault::Default(HttpVersion::default()),
754 }
755 }
756}
757
758impl ToString for RequestTarget {
759 fn to_string(&self) -> String {
760 match self {
761 RequestTarget::Asterisk => "*",
762 RequestTarget::Absolute { uri, .. } => uri,
763 RequestTarget::RelativeOrigin { uri, .. } => uri,
764 RequestTarget::InvalidTarget(target) => target,
765 RequestTarget::Missing => "",
766 }
767 .to_string()
768 }
769}
770
771impl Request {
772 #[allow(dead_code)]
773 pub fn get_comment_text(&self) -> Option<String> {
774 if self.comments.is_empty() {
775 return None;
776 }
777 Some(
778 self.comments
779 .iter()
780 .map(|b| b.value.clone())
781 .collect::<Vec<String>>()
782 .join("\n"),
783 )
784 }
785
786 pub fn get_url(&self) -> String {
787 self.request_line.target.to_string()
788 }
789}
790
791#[derive(PartialEq, Debug)]
792pub struct FileParseResult {
793 pub requests: Vec<Request>,
794 pub errs: Vec<ErrorWithPartial>,
795}
796
797#[derive(PartialEq, Debug, Clone)]
798#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
799pub struct PartialRequest {
800 pub name: Option<String>,
801 pub comments: Vec<Comment>,
802 pub settings: RequestSettings,
803 pub request_line: Option<RequestLine>,
804 pub headers: Option<Vec<Header>>,
805 pub body: Option<RequestBody>,
806 pub pre_request_script: Option<PreRequestScript>,
807 pub response_handler: Option<ResponseHandler>,
808 pub save_response: Option<SaveResponse>,
809}
810
811impl From<PartialRequest> for Request {
812 fn from(partial: PartialRequest) -> Self {
813 Request {
814 name: partial.name,
815 comments: partial.comments,
816 request_line: partial.request_line.unwrap_or(RequestLine {
817 method: WithDefault::<HttpMethod>::default(),
818 target: RequestTarget::Missing,
819 http_version: WithDefault::Default(HttpVersion::default()),
820 }),
821 body: partial.body.unwrap_or(RequestBody::None),
822 save_response: partial.save_response,
823 headers: partial.headers.unwrap_or_default(),
824 response_handler: partial.response_handler,
825 settings: partial.settings,
826 pre_request_script: partial.pre_request_script,
827 }
828 }
829}
830
831#[cfg(test)]
832mod tests {
833 use super::*;
834
835 #[test]
836 pub fn test_with_default() {
837 assert_eq!(
838 WithDefault::<HttpVersion>::default(),
839 WithDefault::<HttpVersion>::default()
840 );
841
842 assert_eq!(
843 WithDefault::<HttpMethod>::default(),
844 WithDefault::<HttpMethod>::default()
845 );
846
847 assert_eq!(
848 WithDefault::Some(HttpMethod::CUSTOM("CustomVerb".to_string())),
849 WithDefault::Some(HttpMethod::CUSTOM("CustomVerb".to_string()))
850 );
851
852 assert!(WithDefault::<HttpVersion>::default().is_default());
853 assert_eq!(
854 WithDefault::Some(HttpVersion { major: 1, minor: 1 }).is_default(),
855 false
856 );
857 assert_eq!(WithDefault::Some(1).unwrap_or_default(), 1);
858 assert_eq!(WithDefault::Default(1).unwrap_or_default(), 1);
859 }
860}