-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a5a4a08
commit 96faa6f
Showing
17 changed files
with
28,997 additions
and
163 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,15 @@ | ||
[package] | ||
name = "dropshot" | ||
description = "expose REST APIs from a Rust program" | ||
version = "0.2.0" | ||
version = "0.3.0" | ||
authors = ["David Pacheco <[email protected]>"] | ||
edition = "2018" | ||
license = "Apache-2.0" | ||
repository = "https://github.com/oxidecomputer/dropshot/" | ||
|
||
[dependencies] | ||
async-trait = "0.1.24" | ||
base64 = "0.12.3" | ||
bytes = "0.5.4" | ||
futures = "0.3.1" | ||
hostname = "0.3.0" | ||
|
@@ -53,5 +54,12 @@ version = "0.7.0" | |
features = [ "uuid" ] | ||
|
||
[dev-dependencies] | ||
libc = "0.2.71" | ||
difference = "2.0.0" | ||
lazy_static = "1.4.0" | ||
libc = "0.2.71" | ||
serde_with = "1.4.0" | ||
subprocess = "0.2.4" | ||
|
||
[dev-dependencies.schemars] | ||
version = "0.7.0" | ||
features = [ "chrono", "uuid" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
// Copyright 2020 Oxide Computer Company | ||
/*! | ||
* Example showing a relatively simple use of the pagination API | ||
* | ||
* When you run this program, it will start an HTTP server on an available local | ||
* port. See the log entry to see what port it ran on. Then use curl to use | ||
* it, like this: | ||
* | ||
* ```ignore | ||
* $ curl localhost:50568/projects | ||
* ``` | ||
* | ||
* (Replace 50568 with whatever port your server is listening on.) | ||
* | ||
* Try passing different values of the `limit` query parameter. Try passing the | ||
* next page token from the response as a query parameter, too. | ||
*/ | ||
|
||
use dropshot::endpoint; | ||
use dropshot::ApiDescription; | ||
use dropshot::ConfigDropshot; | ||
use dropshot::ConfigLogging; | ||
use dropshot::ConfigLoggingLevel; | ||
use dropshot::EmptyScanParams; | ||
use dropshot::HttpError; | ||
use dropshot::HttpResponseOkObject; | ||
use dropshot::HttpServer; | ||
use dropshot::PaginationParams; | ||
use dropshot::Query; | ||
use dropshot::RequestContext; | ||
use dropshot::ResultsPage; | ||
use dropshot::WhichPage; | ||
use schemars::JsonSchema; | ||
use serde::Deserialize; | ||
use serde::Serialize; | ||
use std::collections::BTreeMap; | ||
use std::net::Ipv4Addr; | ||
use std::net::SocketAddr; | ||
use std::ops::Bound; | ||
use std::sync::Arc; | ||
|
||
/** | ||
* Object returned by our paginated endpoint | ||
* | ||
* Like anything returned by Dropshot, we must implement `JsonSchema` and | ||
* `Serialize`. We also implement `Clone` to simplify the example. | ||
*/ | ||
#[derive(Clone, JsonSchema, Serialize)] | ||
struct Project { | ||
name: String, | ||
// lots more fields | ||
} | ||
|
||
/** | ||
* Parameters describing the client's position in a scan through all projects | ||
* | ||
* This implementation only needs the name of the last project seen, as we only | ||
* support listing projects in ascending order by name. | ||
* | ||
* This must be `Serialize` so that Dropshot can turn it into a page token to | ||
* include with each page of results, and it must be `Deserialize` to get it | ||
* back in a querystring. | ||
*/ | ||
#[derive(Deserialize, JsonSchema, Serialize)] | ||
struct ProjectPage { | ||
name: String, | ||
} | ||
|
||
/** | ||
* API endpoint for listing projects | ||
* | ||
* This implementation stores all the projects in a BTreeMap, which makes it | ||
* very easy to fetch a particular range of items based on the key. | ||
*/ | ||
#[endpoint { | ||
method = GET, | ||
path = "/projects" | ||
}] | ||
async fn example_list_projects( | ||
rqctx: Arc<RequestContext>, | ||
query: Query<PaginationParams<EmptyScanParams, ProjectPage>>, | ||
) -> Result<HttpResponseOkObject<ResultsPage<Project>>, HttpError> { | ||
let pag_params = query.into_inner(); | ||
let limit = rqctx.page_limit(&pag_params)?.get(); | ||
let tree = rqctx_to_tree(rqctx); | ||
let projects = match &pag_params.page { | ||
WhichPage::First(..) => { | ||
/* Return a list of the first "limit" projects. */ | ||
tree.iter() | ||
.take(limit) | ||
.map(|(_, project)| project.clone()) | ||
.collect() | ||
} | ||
WhichPage::Next(ProjectPage { | ||
name: last_seen, | ||
}) => { | ||
/* Return a list of the first "limit" projects after this name. */ | ||
tree.range((Bound::Excluded(last_seen.clone()), Bound::Unbounded)) | ||
.take(limit) | ||
.map(|(_, project)| project.clone()) | ||
.collect() | ||
} | ||
}; | ||
|
||
Ok(HttpResponseOkObject(ResultsPage::new( | ||
projects, | ||
&EmptyScanParams {}, | ||
|p: &Project, _| ProjectPage { | ||
name: p.name.clone(), | ||
}, | ||
)?)) | ||
} | ||
|
||
fn rqctx_to_tree(rqctx: Arc<RequestContext>) -> Arc<BTreeMap<String, Project>> { | ||
let c = Arc::clone(&rqctx.server.private); | ||
c.downcast::<BTreeMap<String, Project>>().unwrap() | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), String> { | ||
let port = std::env::args() | ||
.nth(1) | ||
.map(|p| p.parse::<u16>()) | ||
.transpose() | ||
.map_err(|e| format!("failed to parse \"port\" argument: {}", e))? | ||
.unwrap_or(0); | ||
|
||
/* | ||
* Create 1000 projects up front. | ||
*/ | ||
let mut tree = BTreeMap::new(); | ||
for n in 1..1000 { | ||
let name = format!("project{:03}", n); | ||
let project = Project { | ||
name: name.clone(), | ||
}; | ||
tree.insert(name, project); | ||
} | ||
|
||
/* | ||
* Run the Dropshot server. | ||
*/ | ||
let ctx = Arc::new(tree); | ||
let config_dropshot = ConfigDropshot { | ||
bind_address: SocketAddr::from((Ipv4Addr::LOCALHOST, port)), | ||
}; | ||
let config_logging = ConfigLogging::StderrTerminal { | ||
level: ConfigLoggingLevel::Debug, | ||
}; | ||
let log = config_logging | ||
.to_logger("example-pagination-basic") | ||
.map_err(|error| format!("failed to create logger: {}", error))?; | ||
let mut api = ApiDescription::new(); | ||
api.register(example_list_projects).unwrap(); | ||
let mut server = HttpServer::new(&config_dropshot, api, ctx, &log) | ||
.map_err(|error| format!("failed to create server: {}", error))?; | ||
let server_task = server.run(); | ||
server.wait_for_shutdown(server_task).await | ||
} |
Oops, something went wrong.