Use content type negotiation (the Accept header) to serve different formats from the _search endpoint

This commit is contained in:
Magnus Hoff 2017-10-25 11:39:19 +02:00
parent 313fc16add
commit 2cdbd7c7f5
4 changed files with 63 additions and 15 deletions

View file

@ -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,

View file

@ -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())),
}
}))
}
}

View file

@ -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(),

View file

@ -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.
}
}