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
269 changes: 117 additions & 152 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