Skip to main content

typespec/http/
response.rs

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License.
3
4//! HTTP responses.
5
6use crate::{
7    error::{ErrorKind, ResultExt as _},
8    http::{headers::Headers, StatusCode},
9    Bytes,
10};
11#[cfg(any(feature = "json", feature = "xml"))]
12use serde::de::DeserializeOwned;
13use std::{fmt, ops::Deref};
14
15/// A raw HTTP response with status, headers, and body.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct RawResponse {
18    status: StatusCode,
19    headers: Headers,
20    body: ResponseBody,
21}
22
23impl RawResponse {
24    /// Create an HTTP response from raw bytes.
25    pub fn from_bytes(status: StatusCode, headers: Headers, body: impl Into<Bytes>) -> Self {
26        Self {
27            status,
28            headers,
29            body: ResponseBody::from_bytes(body),
30        }
31    }
32
33    /// Get the status code from the response.
34    pub fn status(&self) -> StatusCode {
35        self.status
36    }
37
38    /// Get the headers from the response.
39    pub fn headers(&self) -> &Headers {
40        &self.headers
41    }
42
43    /// Get the [`ResponseBody`].
44    pub fn body(&self) -> &ResponseBody {
45        &self.body
46    }
47
48    /// Get the [`ResponseBody`].
49    pub fn into_body(self) -> ResponseBody {
50        self.body
51    }
52
53    /// Deconstruct the HTTP response into its components.
54    pub fn deconstruct(self) -> (StatusCode, Headers, ResponseBody) {
55        (self.status, self.headers, self.body)
56    }
57}
58
59/// A response body.
60///
61/// This body has already been collected.
62#[derive(Clone, PartialEq, Eq)]
63pub struct ResponseBody(Bytes);
64
65impl ResponseBody {
66    /// Create a new [`ResponseBody`] from a byte slice.
67    pub fn from_bytes(bytes: impl Into<Bytes>) -> Self {
68        Self(bytes.into())
69    }
70
71    /// Collect the stream into a [`String`].
72    pub fn into_string(self) -> crate::Result<String> {
73        std::str::from_utf8(&self.0)
74            .with_context(
75                ErrorKind::DataConversion,
76                "response body was not utf-8 like expected",
77            )
78            .map(ToOwned::to_owned)
79    }
80
81    /// Deserialize the JSON stream into type `T`.
82    #[cfg(feature = "json")]
83    pub fn json<T>(&self) -> crate::Result<T>
84    where
85        T: DeserializeOwned,
86    {
87        crate::json::from_json(&self.0)
88    }
89
90    /// Deserialize the XML stream into type `T`.
91    #[cfg(feature = "xml")]
92    pub fn xml<T>(&self) -> crate::Result<T>
93    where
94        T: DeserializeOwned,
95    {
96        crate::xml::from_xml(&self.0)
97    }
98}
99
100impl AsRef<[u8]> for ResponseBody {
101    #[inline]
102    fn as_ref(&self) -> &[u8] {
103        &self.0
104    }
105}
106
107impl Deref for ResponseBody {
108    type Target = [u8];
109    #[inline]
110    fn deref(&self) -> &Self::Target {
111        &self.0
112    }
113}
114
115impl fmt::Debug for ResponseBody {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        f.write_str("ResponseBody")
118    }
119}
120
121impl From<ResponseBody> for Bytes {
122    fn from(body: ResponseBody) -> Self {
123        body.0
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use serde::Deserialize;
131
132    #[derive(Debug, Deserialize)]
133    struct Employee {
134        id: u32,
135        name: String,
136    }
137
138    #[test]
139    fn as_ref() {
140        let resp =
141            RawResponse::from_bytes(StatusCode::Ok, Headers::new(), r#"{"id":12,"name":"Bob"}"#);
142        assert_eq!(resp.body().as_ref(), br#"{"id":12,"name":"Bob"}"#);
143    }
144
145    #[test]
146    fn deref() {
147        let resp =
148            RawResponse::from_bytes(StatusCode::Ok, Headers::new(), r#"{"id":12,"name":"Bob"}"#);
149        assert_eq!(&*resp.into_body(), br#"{"id":12,"name":"Bob"}"#);
150    }
151
152    #[cfg(feature = "json")]
153    #[test]
154    fn deserializes_json() {
155        let resp =
156            RawResponse::from_bytes(StatusCode::Ok, Headers::new(), r#"{"id":12,"name":"Bob"}"#);
157        let e: Employee = resp.into_body().json().expect("deserialize Employee");
158        assert_eq!(e.id, 12);
159        assert_eq!(e.name, "Bob");
160    }
161
162    #[cfg(feature = "xml")]
163    #[test]
164    fn deserializes_xml() {
165        let resp = RawResponse::from_bytes(
166            StatusCode::Ok,
167            Headers::new(),
168            r#"<Employee><id>34</id><name>Maria</name></Employee>"#,
169        );
170        let e: Employee = resp.into_body().xml().expect("deserialize Employee");
171        assert_eq!(e.id, 34);
172        assert_eq!(e.name, "Maria");
173    }
174}