#![allow(dead_code)]
use std::sync::Arc;
use lino_objects_codec::LinoValue;
use lino_rest_api::app::{LinoApp, RawRequest};
use lino_rest_api::client::LinoClient;
use lino_rest_api::codec::decode_from;
use lino_rest_api::headers::Headers;
use lino_rest_api::media_type::{JSON_CONTENT_TYPE, LINO_CONTENT_TYPE};
use lino_rest_api::middleware::ResponseParts;
use lino_rest_api::serve::BoundServer;
use lino_rest_api::value::{as_array, get, to_query_text};
#[derive(Debug, Clone)]
pub struct RawResponse {
pub status: u16,
pub headers: Headers,
pub text: String,
}
impl RawResponse {
pub fn header(&self, name: &str) -> Option<&str> {
self.headers.get(name)
}
pub fn require(&self, name: &str) -> &str {
self.header(name)
.unwrap_or_else(|| panic!("response carries no {name} header"))
}
pub fn content_type(&self) -> String {
self.header("content-type")
.unwrap_or("")
.split(';')
.next()
.unwrap_or("")
.trim()
.to_string()
}
pub fn vary_fields(&self) -> Vec<String> {
self.header("vary")
.unwrap_or("")
.split(',')
.map(|field| field.trim().to_lowercase())
.filter(|field| !field.is_empty())
.collect()
}
pub fn value(&self) -> LinoValue {
let media_type = self.content_type();
let media_type = if media_type.is_empty() {
LINO_CONTENT_TYPE.to_string()
} else {
media_type
};
decode_from(&self.text, &media_type)
.unwrap_or_else(|error| panic!("body is not a well-formed {media_type}: {error}"))
}
pub fn lino(&self) -> LinoValue {
decode_from(&self.text, LINO_CONTENT_TYPE)
.unwrap_or_else(|error| panic!("body is not well-formed Links Notation: {error}"))
}
pub fn json(&self) -> LinoValue {
decode_from(&self.text, JSON_CONTENT_TYPE)
.unwrap_or_else(|error| panic!("body is not well-formed JSON: {error}"))
}
}
impl From<ResponseParts> for RawResponse {
fn from(parts: ResponseParts) -> Self {
Self {
status: parts.status,
headers: parts.headers,
text: parts.body,
}
}
}
pub struct InProcess {
pub app: LinoApp,
}
impl InProcess {
pub fn new(app: LinoApp) -> Self {
Self { app }
}
pub async fn send(&self, request: RawRequest) -> RawResponse {
self.app.respond(request).await.into()
}
pub async fn raw(
&self,
method: &str,
path: &str,
headers: &[(&str, &str)],
body: Option<&str>,
) -> RawResponse {
let mut request = RawRequest::new(method, path);
if let Some((path, query)) = path.split_once('?') {
request.path = path.to_string();
request.query_string = query.to_string();
}
for (name, value) in headers {
request.headers.insert(*name, *value);
}
if let Some(body) = body {
request.body = body.as_bytes().to_vec();
}
self.send(request).await
}
}
pub struct Served {
pub base: String,
pub http: reqwest::Client,
pub client: LinoClient,
}
impl Served {
pub async fn start(app: LinoApp) -> Self {
let server = BoundServer::bind(app, "127.0.0.1:0")
.await
.expect("an ephemeral port is available");
let base = server.base_url();
tokio::spawn(async move {
let _ = server.serve().await;
});
Self {
client: LinoClient::new(base.clone()),
base,
http: reqwest::Client::new(),
}
}
pub async fn raw(
&self,
method: &str,
path: &str,
headers: &[(&str, &str)],
body: Option<&str>,
) -> RawResponse {
let method = reqwest::Method::from_bytes(method.as_bytes()).expect("a known method");
let mut builder = self.http.request(method, format!("{}{path}", self.base));
for (name, value) in headers {
builder = builder.header(*name, *value);
}
if let Some(body) = body {
builder = builder.body(body.to_string());
}
let response = builder.send().await.expect("the server answers");
let status = response.status().as_u16();
let mut collected = Headers::new();
for (name, value) in response.headers() {
collected.insert(name.as_str(), value.to_str().unwrap_or_default());
}
RawResponse {
status,
headers: collected,
text: response.text().await.expect("a readable body"),
}
}
}
pub fn shared<S>(store: S) -> Arc<S> {
Arc::new(store)
}
pub fn member<'a>(value: &'a LinoValue, key: &str) -> &'a LinoValue {
get(value, key).unwrap_or_else(|| panic!("value carries no {key} member: {value:?}"))
}
pub fn at<'a>(value: &'a LinoValue, path: &[&str]) -> &'a LinoValue {
path.iter().fold(value, |current, key| member(current, key))
}
pub fn text(value: &LinoValue, key: &str) -> String {
to_query_text(member(value, key))
}
pub fn items(envelope: &LinoValue) -> &Vec<LinoValue> {
as_array(member(envelope, "items")).expect("items is a list")
}