Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update pagination to support v1 API patterns #2111

Merged
merged 9 commits into from
Jan 11, 2023
271 changes: 118 additions & 153 deletions common/src/api/external/http_pagination.rs

Large diffs are not rendered by default.

53 changes: 52 additions & 1 deletion common/src/api/external/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

mod error;
pub mod http_pagination;
use dropshot::HttpError;
pub use error::*;

use anyhow::anyhow;
Expand Down Expand Up @@ -111,6 +112,56 @@ impl<'a, NameType> DataPageParams<'a, NameType> {
}
}

impl<'a> TryFrom<&DataPageParams<'a, NameOrId>> for DataPageParams<'a, Name> {
type Error = HttpError;

fn try_from(
value: &DataPageParams<'a, NameOrId>,
) -> Result<Self, Self::Error> {
match value.marker {
Some(NameOrId::Name(name)) => Ok(DataPageParams {
marker: Some(name),
direction: value.direction,
limit: value.limit,
}),
None => Ok(DataPageParams {
marker: None,
direction: value.direction,
limit: value.limit,
}),
_ => Err(HttpError::for_bad_request(
None,
String::from("invalid pagination marker"),
)),
}
}
}

impl<'a> TryFrom<&DataPageParams<'a, NameOrId>> for DataPageParams<'a, Uuid> {
type Error = HttpError;

fn try_from(
value: &DataPageParams<'a, NameOrId>,
) -> Result<Self, Self::Error> {
match value.marker {
Some(NameOrId::Id(id)) => Ok(DataPageParams {
marker: Some(id),
direction: value.direction,
limit: value.limit,
}),
None => Ok(DataPageParams {
marker: None,
direction: value.direction,
limit: value.limit,
}),
_ => Err(HttpError::for_bad_request(
None,
String::from("invalid pagination marker"),
)),
}
}
}

/// A name used in the API
///
/// Names are generally user-provided unique identifiers, highly constrained as
Expand Down Expand Up @@ -268,7 +319,7 @@ impl Name {
}
}

#[derive(Serialize, Deserialize, Display, Clone)]
#[derive(Debug, Serialize, Deserialize, Display, Clone, PartialEq)]
#[display("{0}")]
#[serde(untagged)]
pub enum NameOrId {
Expand Down
8 changes: 2 additions & 6 deletions common/tests/output/pagination-examples.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,10 @@ example pagination parameters: page selector: by name ascending
example pagination parameters: page selector: by name or id, using id ascending
{
"sort_by": "id_ascending",
"last_seen": {
"id": "61a78113-d3c6-4b35-a410-23e9eae64328"
}
"last_seen": "61a78113-d3c6-4b35-a410-23e9eae64328"
}
example pagination parameters: page selector: by name or id, using id ascending
{
"sort_by": "name_ascending",
"last_seen": {
"name": "bort"
}
"last_seen": "bort"
}
32 changes: 12 additions & 20 deletions common/tests/output/pagination-schema.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ schema for pagination parameters: scan parameters, scan by id only
schema for pagination parameters: scan parameters, scan by name or id
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ScanByNameOrId",
"title": "ScanByNameOrId_for_Null",
"description": "Scan parameters for resources that support scanning by name or id",
"type": "object",
"properties": {
Expand Down Expand Up @@ -196,7 +196,7 @@ schema for pagination parameters: page selector, scan by id only
schema for pagination parameters: page selector, scan by name or id
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PageSelector_for_ScanByNameOrId_and_NameOrIdMarker",
"title": "PageSelector_for_ScanByNameOrId_for_Null_and_NameOrId",
"description": "Specifies which page of results we're on\n\nThis type is generic over the different scan modes that we support.",
"type": "object",
"required": [
Expand All @@ -207,7 +207,7 @@ schema for pagination parameters: page selector, scan by name or id
"description": "value of the marker field last seen by the client",
"allOf": [
{
"$ref": "#/definitions/NameOrIdMarker"
"$ref": "#/definitions/NameOrId"
}
]
},
Expand All @@ -228,32 +228,24 @@ schema for pagination parameters: page selector, scan by name or id
"maxLength": 63,
"pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z][a-z0-9-]*[a-zA-Z0-9]$"
},
"NameOrIdMarker": {
"NameOrId": {
"oneOf": [
{
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"title": "id",
"allOf": [
{
"type": "string",
"format": "uuid"
}
},
"additionalProperties": false
]
},
{
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"title": "name",
"allOf": [
{
"$ref": "#/definitions/Name"
}
},
"additionalProperties": false
]
}
]
},
Expand Down
17 changes: 15 additions & 2 deletions nexus/src/app/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,20 @@ impl super::Nexus {
Ok(db_instance)
}

pub async fn project_list_instances(
pub async fn project_list_instances_by_id(
&self,
opctx: &OpContext,
project_lookup: &lookup::Project<'_>,
pagparams: &DataPageParams<'_, Uuid>,
) -> ListResultVec<db::model::Instance> {
let (.., authz_project) =
project_lookup.lookup_for(authz::Action::ListChildren).await?;
self.db_datastore
.project_list_instances_by_id(opctx, &authz_project, pagparams)
.await
}

pub async fn project_list_instances_by_name(
&self,
opctx: &OpContext,
project_lookup: &lookup::Project<'_>,
Expand All @@ -237,7 +250,7 @@ impl super::Nexus {
let (.., authz_project) =
project_lookup.lookup_for(authz::Action::ListChildren).await?;
self.db_datastore
.project_list_instances(opctx, &authz_project, pagparams)
.project_list_instances_by_name(opctx, &authz_project, pagparams)
.await
}

Expand Down
22 changes: 20 additions & 2 deletions nexus/src/db/datastore/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,25 @@ impl DataStore {
Ok(instance)
}

pub async fn project_list_instances(
pub async fn project_list_instances_by_id(
&self,
opctx: &OpContext,
authz_project: &authz::Project,
pagparams: &DataPageParams<'_, Uuid>,
) -> ListResultVec<Instance> {
opctx.authorize(authz::Action::ListChildren, authz_project).await?;

use db::schema::instance::dsl;
paginated(dsl::instance, dsl::id, &pagparams)
.filter(dsl::project_id.eq(authz_project.id()))
.filter(dsl::time_deleted.is_null())
.select(Instance::as_select())
.load_async::<Instance>(self.pool_authorized(opctx).await?)
.await
.map_err(|e| public_error_from_diesel_pool(e, ErrorHandler::Server))
}

pub async fn project_list_instances_by_name(
&self,
opctx: &OpContext,
authz_project: &authz::Project,
Expand All @@ -122,8 +140,8 @@ impl DataStore {

use db::schema::instance::dsl;
paginated(dsl::instance, dsl::name, &pagparams)
.filter(dsl::time_deleted.is_null())
.filter(dsl::project_id.eq(authz_project.id()))
.filter(dsl::time_deleted.is_null())
.select(Instance::as_select())
.load_async::<Instance>(self.pool_authorized(opctx).await?)
.await
Expand Down
Loading