request builder improvements

This commit is contained in:
Robert Habermeier 2017-03-13 13:36:03 +01:00
parent 64342d200c
commit f0a587d310
3 changed files with 63 additions and 35 deletions

View File

@ -82,7 +82,7 @@ pub trait Provider: Send + Sync {
} }
}; };
let headers = (0u64..req.max as u64) let headers: Vec<_> = (0u64..req.max as u64)
.map(|x: u64| x.saturating_mul(req.skip + 1)) .map(|x: u64| x.saturating_mul(req.skip + 1))
.take_while(|x| if req.reverse { x < &start_num } else { best_num.saturating_sub(start_num) >= *x }) .take_while(|x| if req.reverse { x < &start_num } else { best_num.saturating_sub(start_num) >= *x })
.map(|x| if req.reverse { start_num - x } else { start_num + x }) .map(|x| if req.reverse { start_num - x } else { start_num + x })

View File

@ -21,7 +21,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use request::{ use request::{
IncompleteRequest, CompleteRequest, Request, IncompleteRequest, CompleteRequest, Request,
OutputKind, Output, NoSuchOutput, Response, OutputKind, Output, NoSuchOutput, Response, ResponseError,
}; };
/// Build chained requests. Push them onto the series with `push`, /// Build chained requests. Push them onto the series with `push`,
@ -34,7 +34,7 @@ pub struct RequestBuilder {
impl RequestBuilder { impl RequestBuilder {
/// Attempt to push a request onto the request chain. Fails if the request /// Attempt to push a request onto the request chain. Fails if the request
/// references a non-existant output of a prior request. /// references a non-existent output of a prior request.
pub fn push(&mut self, request: Request) -> Result<(), NoSuchOutput> { pub fn push(&mut self, request: Request) -> Result<(), NoSuchOutput> {
request.check_outputs(|req, idx, kind| { request.check_outputs(|req, idx, kind| {
match self.output_kinds.get(&(req, idx)) { match self.output_kinds.get(&(req, idx)) {
@ -48,13 +48,17 @@ impl RequestBuilder {
Ok(()) Ok(())
} }
/// Get a reference to the output kinds map.
pub fn output_kinds(&self) -> &HashMap<(usize, usize), OutputKind> {
&self.output_kinds
}
/// Convert this into a "requests" object. /// Convert this into a "requests" object.
pub fn build(self) -> Requests { pub fn build(self) -> Requests {
Requests { Requests {
output_kinds: self.output_kinds,
outputs: HashMap::new(), outputs: HashMap::new(),
requests: self.requests, requests: self.requests,
offset: 0, answered: 0,
} }
} }
} }
@ -62,49 +66,27 @@ impl RequestBuilder {
/// Requests pending responses. /// Requests pending responses.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Requests { pub struct Requests {
output_kinds: HashMap<(usize, usize), OutputKind>,
outputs: HashMap<(usize, usize), Output>, outputs: HashMap<(usize, usize), Output>,
requests: Vec<Request>, requests: Vec<Request>,
offset: usize, // offset for splitting. answered: usize,
} }
impl Requests { impl Requests {
/// For each request, produce responses for each. /// For each request, produce responses for each.
/// The responses vector produced goes up to the point where the responder /// The responses vector produced goes up to the point where the responder
/// first returns `None`, an invalid response, or until all requests have been responded to. /// first returns `None`, an invalid response, or until all requests have been responded to.
pub fn respond_to_all<F>(self, responder: F) -> Vec<Response> pub fn respond_to_all<F>(mut self, responder: F) -> Vec<Response>
where F: Fn(CompleteRequest) -> Option<Response> where F: Fn(CompleteRequest) -> Option<Response>
{ {
let mut responses = Vec::new(); let mut responses = Vec::new();
let mut found_bad = false;
let offset = self.offset;
let output_kinds = self.output_kinds;
let mut outputs = self.outputs;
for (idx, req) in self.requests.into_iter().enumerate().map(|(idx, req)| (idx + offset, req)) {
let complete = req.fill(|req_idx, out_idx| outputs.get(&(req_idx, out_idx)).cloned().ok_or(NoSuchOutput))
.expect("All outputs checked as invariant of `Requests` object; qed");
match responder(complete) { while let Some(response) = self.next_complete().and_then(&responder) {
Some(response) => { match self.supply_response(&response) {
response.fill_outputs(|out_idx, output| { Ok(()) => responses.push(response),
match output_kinds.get(&(idx, out_idx)) { Err(e) => {
None => {}, debug!(target: "pip", "produced bad response to request: {:?}", e);
Some(out) => if out == &output.kind() {
outputs.insert((idx, out_idx), output);
} else {
// output kind doesn't match expected.
found_bad = true;
}
}
});
if found_bad {
return responses; return responses;
} }
responses.push(response);
}
None => return responses,
} }
} }
@ -112,7 +94,44 @@ impl Requests {
} }
/// Get access to the underlying slice of requests. /// Get access to the underlying slice of requests.
// TODO: unimplemented -> Vec<Request>, // do we _have to_ allocate?
pub fn requests(&self) -> &[Request] { &self.requests } pub fn requests(&self) -> &[Request] { &self.requests }
/// Get the number of answered requests.
pub fn num_answered(&self) -> usize { self.answered }
/// Get the next request as a filled request. Returns `None` when all requests answered.
pub fn next_complete(&self) -> Option<CompleteRequest> {
if self.answered == self.requests.len() {
None
} else {
let outputs = &self.outputs;
Some(self.requests[self.answered].clone()
.fill(|req_idx, out_idx| outputs.get(&(req_idx, out_idx)).cloned().ok_or(NoSuchOutput))
.expect("All outputs checked as invariant of `Requests` object; qed"))
}
}
/// Supply a response for the next request.
/// Fails on: wrong request kind, all requests answered already.
pub fn supply_response(&mut self, response: &Response) -> Result<(), ResponseError> {
let idx = self.answered;
// check validity.
if idx == self.requests.len() { return Err(ResponseError::Unexpected) }
if self.requests[idx].kind() != response.kind() { return Err(ResponseError::WrongKind) }
let outputs = &mut self.outputs;
response.fill_outputs(|out_idx, output| {
// we don't need to check output kinds here because all back-references
// are validated in the builder.
// TODO: optimization for only storing outputs we "care about"?
outputs.insert((idx, out_idx), output);
});
self.answered += 1;
Ok(())
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -69,6 +69,15 @@ pub use self::builder::{RequestBuilder, Requests};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NoSuchOutput; pub struct NoSuchOutput;
/// Error on processing a response.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResponseError {
/// Wrong kind of response.
WrongKind,
/// No responses expected.
Unexpected,
}
/// An input to a request. /// An input to a request.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Field<T> { pub enum Field<T> {