Use content type negotiation (the Accept header) to serve different formats from the _search endpoint
This commit is contained in:
parent
313fc16add
commit
2cdbd7c7f5
4 changed files with 63 additions and 15 deletions
|
@ -33,7 +33,7 @@ pub struct ArticleRevisionStub {
|
|||
pub author: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Queryable)]
|
||||
#[derive(Debug, Queryable, Serialize)]
|
||||
pub struct SearchResult {
|
||||
pub title: String,
|
||||
pub snippet: String,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use futures::{self, Future};
|
||||
use hyper;
|
||||
use hyper::header::ContentType;
|
||||
use hyper::header::{Accept, ContentType};
|
||||
use hyper::server::*;
|
||||
use serde_json;
|
||||
use serde_urlencoded;
|
||||
|
||||
use assets::StyleCss;
|
||||
|
@ -76,6 +77,7 @@ impl SearchLookup {
|
|||
|
||||
pub struct SearchResource {
|
||||
state: State,
|
||||
response_type: ResponseType,
|
||||
|
||||
query: Option<String>,
|
||||
limit: i32,
|
||||
|
@ -83,9 +85,15 @@ pub struct SearchResource {
|
|||
snippet_size: i32,
|
||||
}
|
||||
|
||||
// This is a complete hack, searching for a reasonable design:
|
||||
pub enum ResponseType {
|
||||
Html,
|
||||
Json,
|
||||
}
|
||||
|
||||
impl SearchResource {
|
||||
pub fn new(state: State, query: Option<String>, limit: i32, offset: i32, snippet_size: i32) -> Self {
|
||||
Self { state, query, limit, offset, snippet_size }
|
||||
Self { state, response_type: ResponseType::Html, query, limit, offset, snippet_size }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,10 +103,28 @@ impl Resource for SearchResource {
|
|||
vec![Options, Head, Get]
|
||||
}
|
||||
|
||||
// This is a complete hack, searching for a reasonable design:
|
||||
fn hacky_inject_accept_header(&mut self, accept: Accept) {
|
||||
use hyper::header::QualityItem;
|
||||
use hyper::mime;
|
||||
|
||||
self.response_type = match accept.first() {
|
||||
Some(&QualityItem { item: ref mime, .. })
|
||||
if mime.type_() == mime::APPLICATION && mime.subtype() == mime::JSON
|
||||
=> ResponseType::Json,
|
||||
_ => ResponseType::Html,
|
||||
};
|
||||
}
|
||||
|
||||
fn head(&self) -> ResponseFuture {
|
||||
let content_type = match &self.response_type {
|
||||
&ResponseType::Json => ContentType(APPLICATION_JSON.clone()),
|
||||
&ResponseType::Html => ContentType(TEXT_HTML.clone()),
|
||||
};
|
||||
|
||||
Box::new(futures::finished(Response::new()
|
||||
.with_status(hyper::StatusCode::Ok)
|
||||
.with_header(ContentType(TEXT_HTML.clone()))
|
||||
.with_header(content_type)
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -110,6 +136,12 @@ impl Resource for SearchResource {
|
|||
hits: Vec<models::SearchResult>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JsonResponse<'a> {
|
||||
query: &'a str,
|
||||
hits: &'a [models::SearchResult],
|
||||
}
|
||||
|
||||
impl models::SearchResult {
|
||||
fn link(&self) -> String {
|
||||
if self.slug == "" {
|
||||
|
@ -128,16 +160,24 @@ impl Resource for SearchResource {
|
|||
|
||||
Box::new(data.join(head)
|
||||
.and_then(move |(data, head)| {
|
||||
Ok(head
|
||||
.with_body(Layout {
|
||||
base: None, // Hmm, should perhaps accept `base` as argument
|
||||
title: "Search",
|
||||
body: &Template {
|
||||
match &self.response_type {
|
||||
&ResponseType::Json => Ok(head
|
||||
.with_body(serde_json::to_string(&JsonResponse {
|
||||
query: self.query.as_ref().map(|x| &**x).unwrap_or(""),
|
||||
hits: data,
|
||||
},
|
||||
style_css_checksum: StyleCss::checksum(),
|
||||
}.to_string()))
|
||||
hits: &data,
|
||||
}).expect("Should never fail"))
|
||||
),
|
||||
&ResponseType::Html => Ok(head
|
||||
.with_body(Layout {
|
||||
base: None, // Hmm, should perhaps accept `base` as argument
|
||||
title: "Search",
|
||||
body: &Template {
|
||||
query: self.query.as_ref().map(|x| &**x).unwrap_or(""),
|
||||
hits: data,
|
||||
},
|
||||
style_css_checksum: StyleCss::checksum(),
|
||||
}.to_string())),
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
use std::fmt;
|
||||
|
||||
use futures::{self, Future};
|
||||
use hyper::header::ContentType;
|
||||
use hyper::header::{Accept, ContentType};
|
||||
use hyper::mime;
|
||||
use hyper::server::*;
|
||||
use hyper;
|
||||
|
@ -99,13 +99,16 @@ impl Service for Site {
|
|||
false => None,
|
||||
};
|
||||
|
||||
let accept_header = headers.get().map(|x: &Accept| x.clone()).unwrap_or(Accept(vec![]));
|
||||
|
||||
let base = root_base_from_request_uri(uri.path());
|
||||
let base2 = base.clone(); // Bah, stupid clone
|
||||
|
||||
Box::new(self.root.lookup(uri.path(), uri.query())
|
||||
.and_then(move |resource| match resource {
|
||||
Some(resource) => {
|
||||
Some(mut resource) => {
|
||||
use hyper::Method::*;
|
||||
resource.hacky_inject_accept_header(accept_header);
|
||||
match method {
|
||||
Options => Box::new(futures::finished(resource.options())),
|
||||
Head => resource.head(),
|
||||
|
|
|
@ -54,4 +54,9 @@ pub trait Resource {
|
|||
.with_header(header::ContentType(TEXT_PLAIN.clone()))
|
||||
.with_body("Method not allowed\n")
|
||||
}
|
||||
|
||||
fn hacky_inject_accept_header(&mut self, _: header::Accept) {
|
||||
// This function is a complete hack, searching for the appropriate
|
||||
// architecture.
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue