Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ https://github.com/oxidecomputer/dropshot/compare/v0.13.0\...HEAD[Full list of c
+
Defining the old config option will produce an error, guiding you to perform the rename.

* Within `RequestContext`, endpoint-specific metadata has been moved to an `endpoint` field:
** `rqctx.operation_id` is now `rqctx.endpoint.operation_id`.
** `rqctx.path_variables` is now `rqctx.endpoint.variables`.
** `rqctx.body_content_type` is now `rqctx.endpoint.body_content_type`.

== 0.13.0 (released 2024-11-13)

https://github.com/oxidecomputer/dropshot/compare/v0.12.0\...v0.13.0[Full list of commits]
Expand Down
2 changes: 1 addition & 1 deletion dropshot/src/extractor/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ where
let body_content_type =
ApiEndpointBodyContentType::from_mime_type(&mime_type)
.map_err(|e| HttpError::for_bad_request(None, e))?;
let expected_content_type = rqctx.body_content_type.clone();
let expected_content_type = rqctx.endpoint.body_content_type.clone();

use ApiEndpointBodyContentType::*;

Expand Down
3 changes: 2 additions & 1 deletion dropshot/src/extractor/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ where
async fn from_request<Context: ServerContext>(
rqctx: &RequestContext<Context>,
) -> Result<Path<PathType>, HttpError> {
let params: PathType = http_extract_path_params(&rqctx.path_variables)?;
let params: PathType =
http_extract_path_params(&rqctx.endpoint.variables)?;
Ok(Path { inner: params })
}

Expand Down
24 changes: 18 additions & 6 deletions dropshot/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,16 @@ pub type HttpHandlerResult = Result<Response<Body>, HttpError>;

/// Handle for various interfaces useful during request processing.
#[derive(Debug)]
#[non_exhaustive]
pub struct RequestContext<Context: ServerContext> {
/// shared server state
pub server: Arc<DropshotState<Context>>,
/// HTTP request routing variables
pub path_variables: VariableSet,
/// expected request body mime type
pub body_content_type: ApiEndpointBodyContentType,
/// Endpoint-specific information.
pub endpoint: RequestEndpointMetadata,
/// unique id assigned to this request
pub request_id: String,
/// logger for this specific request
pub log: Logger,
/// The operation ID for the endpoint handler method
pub operation_id: String,
/// basic request information (method, URI, etc.)
pub request: RequestInfo,
}
Expand Down Expand Up @@ -208,6 +205,21 @@ impl<Context: ServerContext> RequestContext<Context> {
}
}

/// Endpoint-specific information for a request.
///
/// This is part of [`RequestContext`].
#[derive(Debug)]
pub struct RequestEndpointMetadata {
/// The operation ID for the endpoint handler method.
pub operation_id: String,

/// HTTP request routing variables.
pub variables: VariableSet,

/// The expected request body MIME type.
pub body_content_type: ApiEndpointBodyContentType,
}

/// Helper trait for extracting the underlying Context type from the
/// first argument to an endpoint. This trait exists to help the
/// endpoint macro parse this argument.
Expand Down
1 change: 1 addition & 0 deletions dropshot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,7 @@ pub use handler::HttpResponseTemporaryRedirect;
pub use handler::HttpResponseUpdatedNoContent;
pub use handler::NoHeaders;
pub use handler::RequestContext;
pub use handler::RequestEndpointMetadata;
pub use handler::RequestInfo;
pub use http_util::CONTENT_TYPE_JSON;
pub use http_util::CONTENT_TYPE_MULTIPART_FORM_DATA;
Expand Down
69 changes: 34 additions & 35 deletions dropshot/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::from_map::MapError;
use crate::from_map::MapValue;
use crate::server::ServerContext;
use crate::ApiEndpoint;
use crate::ApiEndpointBodyContentType;
use crate::RequestEndpointMetadata;
use http::Method;
use http::StatusCode;
use percent_encoding::percent_decode_str;
Expand Down Expand Up @@ -208,17 +208,13 @@ impl MapValue for VariableValue {
}
}

/// `RouterLookupResult` represents the result of invoking
/// `HttpRouter::lookup_route()`. A successful route lookup includes
/// the handler, a mapping of variables in the configured path to the
/// corresponding values in the actual path, and the expected body
/// content type.
/// The result of invoking `HttpRouter::lookup_route()`.
///
/// A successful route lookup includes the handler and endpoint-related metadata.
#[derive(Debug)]
pub struct RouterLookupResult<Context: ServerContext> {
pub handler: Arc<dyn RouteHandler<Context>>,
pub operation_id: String,
pub variables: VariableSet,
pub body_content_type: ApiEndpointBodyContentType,
pub endpoint: RequestEndpointMetadata,
}

impl<Context: ServerContext> HttpRouterNode<Context> {
Expand Down Expand Up @@ -520,9 +516,11 @@ impl<Context: ServerContext> HttpRouter<Context> {
) {
return Ok(RouterLookupResult {
handler: Arc::clone(&handler.handler),
operation_id: handler.operation_id.clone(),
variables,
body_content_type: handler.body_content_type.clone(),
endpoint: RequestEndpointMetadata {
operation_id: handler.operation_id.clone(),
variables,
body_content_type: handler.body_content_type.clone(),
},
});
}

Expand Down Expand Up @@ -1227,16 +1225,16 @@ mod test {
let result =
router.lookup_route_unversioned(&Method::GET, "/".into()).unwrap();
assert_eq!(result.handler.label(), "h1");
assert!(result.variables.is_empty());
assert!(result.endpoint.variables.is_empty());
let result =
router.lookup_route_unversioned(&Method::GET, "//".into()).unwrap();
assert_eq!(result.handler.label(), "h1");
assert!(result.variables.is_empty());
assert!(result.endpoint.variables.is_empty());
let result = router
.lookup_route_unversioned(&Method::GET, "///".into())
.unwrap();
assert_eq!(result.handler.label(), "h1");
assert!(result.variables.is_empty());
assert!(result.endpoint.variables.is_empty());

// Now insert a handler for a different method at the root. Verify that
// we get both this handler and the previous one if we ask for the
Expand All @@ -1249,14 +1247,14 @@ mod test {
let result =
router.lookup_route_unversioned(&Method::PUT, "/".into()).unwrap();
assert_eq!(result.handler.label(), "h2");
assert!(result.variables.is_empty());
assert!(result.endpoint.variables.is_empty());
let result =
router.lookup_route_unversioned(&Method::GET, "/".into()).unwrap();
assert_eq!(result.handler.label(), "h1");
assert!(result.endpoint.variables.is_empty());
assert!(router
.lookup_route_unversioned(&Method::DELETE, "/".into())
.is_err());
assert!(result.variables.is_empty());

// Now insert a handler one level deeper. Verify that all the previous
// handlers behave as we expect, and that we have one handler at the new
Expand All @@ -1272,31 +1270,31 @@ mod test {
let result =
router.lookup_route_unversioned(&Method::PUT, "/".into()).unwrap();
assert_eq!(result.handler.label(), "h2");
assert!(result.variables.is_empty());
assert!(result.endpoint.variables.is_empty());
let result =
router.lookup_route_unversioned(&Method::GET, "/".into()).unwrap();
assert_eq!(result.handler.label(), "h1");
assert!(result.variables.is_empty());
assert!(result.endpoint.variables.is_empty());
let result = router
.lookup_route_unversioned(&Method::GET, "/foo".into())
.unwrap();
assert_eq!(result.handler.label(), "h3");
assert!(result.variables.is_empty());
assert!(result.endpoint.variables.is_empty());
let result = router
.lookup_route_unversioned(&Method::GET, "/foo/".into())
.unwrap();
assert_eq!(result.handler.label(), "h3");
assert!(result.variables.is_empty());
assert!(result.endpoint.variables.is_empty());
let result = router
.lookup_route_unversioned(&Method::GET, "//foo//".into())
.unwrap();
assert_eq!(result.handler.label(), "h3");
assert!(result.variables.is_empty());
assert!(result.endpoint.variables.is_empty());
let result = router
.lookup_route_unversioned(&Method::GET, "/foo//".into())
.unwrap();
assert_eq!(result.handler.label(), "h3");
assert!(result.variables.is_empty());
assert!(result.endpoint.variables.is_empty());
assert!(router
.lookup_route_unversioned(&Method::PUT, "/foo".into())
.is_err());
Expand Down Expand Up @@ -1404,7 +1402,7 @@ mod test {
.lookup_route_unversioned(&Method::GET, "/not{a}variable".into())
.unwrap();
assert_eq!(result.handler.label(), "h4");
assert!(result.variables.is_empty());
assert!(result.endpoint.variables.is_empty());
assert!(router
.lookup_route_unversioned(&Method::GET, "/not{b}variable".into())
.is_err());
Expand Down Expand Up @@ -1433,11 +1431,11 @@ mod test {
.unwrap();
assert_eq!(result.handler.label(), "h5");
assert_eq!(
result.variables.keys().collect::<Vec<&String>>(),
result.endpoint.variables.keys().collect::<Vec<&String>>(),
vec!["project_id"]
);
assert_eq!(
*result.variables.get("project_id").unwrap(),
*result.endpoint.variables.get("project_id").unwrap(),
VariableValue::String("p12345".to_string())
);
assert!(router
Expand All @@ -1451,7 +1449,7 @@ mod test {
.unwrap();
assert_eq!(result.handler.label(), "h5");
assert_eq!(
*result.variables.get("project_id").unwrap(),
*result.endpoint.variables.get("project_id").unwrap(),
VariableValue::String("p12345".to_string())
);
let result = router
Expand All @@ -1462,7 +1460,7 @@ mod test {
.unwrap();
assert_eq!(result.handler.label(), "h5");
assert_eq!(
*result.variables.get("project_id").unwrap(),
*result.endpoint.variables.get("project_id").unwrap(),
VariableValue::String("p12345".to_string())
);
// Trick question!
Expand All @@ -1474,7 +1472,7 @@ mod test {
.unwrap();
assert_eq!(result.handler.label(), "h5");
assert_eq!(
*result.variables.get("project_id").unwrap(),
*result.endpoint.variables.get("project_id").unwrap(),
VariableValue::String("{project_id}".to_string())
);
}
Expand All @@ -1497,19 +1495,19 @@ mod test {
.unwrap();
assert_eq!(result.handler.label(), "h6");
assert_eq!(
result.variables.keys().collect::<Vec<&String>>(),
result.endpoint.variables.keys().collect::<Vec<&String>>(),
vec!["fwrule_id", "instance_id", "project_id"]
);
assert_eq!(
*result.variables.get("project_id").unwrap(),
*result.endpoint.variables.get("project_id").unwrap(),
VariableValue::String("p1".to_string())
);
assert_eq!(
*result.variables.get("instance_id").unwrap(),
*result.endpoint.variables.get("instance_id").unwrap(),
VariableValue::String("i2".to_string())
);
assert_eq!(
*result.variables.get("fwrule_id").unwrap(),
*result.endpoint.variables.get("fwrule_id").unwrap(),
VariableValue::String("fw3".to_string())
);
}
Expand Down Expand Up @@ -1568,7 +1566,7 @@ mod test {
.unwrap();

assert_eq!(
result.variables.get("path"),
result.endpoint.variables.get("path"),
Some(&VariableValue::Components(vec![
"missiles".to_string(),
"launch".to_string()
Expand Down Expand Up @@ -1604,7 +1602,8 @@ mod test {
.unwrap();

let path =
from_map::<MyPath, VariableValue>(&result.variables).unwrap();
from_map::<MyPath, VariableValue>(&result.endpoint.variables)
.unwrap();

assert_eq!(path.t, "console");
assert_eq!(path.r, "missiles");
Expand Down
4 changes: 1 addition & 3 deletions dropshot/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,9 +910,7 @@ async fn http_request_handle<C: ServerContext>(
let rqctx = RequestContext {
server: Arc::clone(&server),
request: RequestInfo::new(&request, remote_addr),
path_variables: lookup_result.variables,
body_content_type: lookup_result.body_content_type,
operation_id: lookup_result.operation_id,
endpoint: lookup_result.endpoint,
request_id: request_id.to_string(),
log: request_log,
};
Expand Down
12 changes: 7 additions & 5 deletions dropshot/src/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,8 @@ mod tests {
use crate::router::HttpRouter;
use crate::server::{DropshotState, ServerConfig};
use crate::{
ExclusiveExtractor, HttpError, RequestContext, RequestInfo,
VersionPolicy, WebsocketUpgrade,
ExclusiveExtractor, HttpError, RequestContext, RequestEndpointMetadata,
RequestInfo, VersionPolicy, WebsocketUpgrade,
};
use debug_ignore::DebugIgnore;
use http::Request;
Expand Down Expand Up @@ -399,9 +399,11 @@ mod tests {
version_policy: VersionPolicy::Unversioned,
}),
request: RequestInfo::new(&request, remote_addr),
path_variables: Default::default(),
body_content_type: Default::default(),
operation_id: "".to_string(),
endpoint: RequestEndpointMetadata {
operation_id: "".to_string(),
variables: Default::default(),
body_content_type: Default::default(),
},
request_id: "".to_string(),
log: log.clone(),
};
Expand Down