From 5943bc760026822e6c7f7e85dc73c6a4ef47208b Mon Sep 17 00:00:00 2001 From: BlowaXD <16936368+BlowaXD@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:48:34 +0100 Subject: [PATCH] Add fetch version basis --- registry/proto/registry.proto | 6 +++ registry/src/api/grpc.rs | 44 +++++++++++++++++++--- registry/src/metadata.rs | 11 +++++- registry/src/metadata/memory.rs | 50 ++++++++++++++++++++++++- registry/tests/api/main.rs | 66 ++++++++++++++++++++++++++++++--- 5 files changed, 163 insertions(+), 14 deletions(-) diff --git a/registry/proto/registry.proto b/registry/proto/registry.proto index bf658216..a8ac6fa1 100644 --- a/registry/proto/registry.proto +++ b/registry/proto/registry.proto @@ -35,7 +35,13 @@ message DownloadResponse { } message VersionsRequest { + // package name string name = 1; + /* `requirement` expects to follow the [`semver::VersionReq`] format + * https://docs.rs/semver/latest/semver/struct.VersionReq.html + * Examples: + * >=1.0.0 + * >=1.2.3, <1.8 */ string requirement = 2; } diff --git a/registry/src/api/grpc.rs b/registry/src/api/grpc.rs index 2dd99537..8d056156 100644 --- a/registry/src/api/grpc.rs +++ b/registry/src/api/grpc.rs @@ -26,7 +26,7 @@ use crate::{ }; use async_trait::async_trait; use buffrs::{manifest::PackageManifest, package::PackageName, package::PackageType}; -use semver::Version; +use semver::{Version, VersionReq}; use tonic::{Code, Request, Response, Status}; use tonic_types::{ErrorDetails, StatusExt}; @@ -144,9 +144,43 @@ impl Registry for Context { &self, request: Request, ) -> Result, Status> { - let _req: VersionsRequest = request.into_inner(); - // check from metadata - // if not - todo!() + let req: VersionsRequest = request.into_inner(); + + if req.name.len() > PACKAGE_NAME_LIMIT { + let mut err_details = ErrorDetails::new(); + err_details.add_bad_request_violation( + "name", + format!("package's name exceeds limit: {}", PACKAGE_NAME_LIMIT), + ); + + // Generate error status + let status = Status::with_error_details( + Code::InvalidArgument, + "request contains invalid arguments", + err_details, + ); + return Err(status); + } + + let version_requirement = VersionReq::parse(req.requirement.as_str()) + .map_err(|_x| Status::invalid_argument("Provided version requirement was incorrect, check: https://docs.rs/semver/latest/semver/struct.VersionReq.html"))?; + + let metadata = self.metadata_store(); + + let package_name = PackageName::from_str(req.name.as_str()) + .map_err(|_| Status::invalid_argument("provided name was incorrect"))?; + + let versions = metadata + .get_versions(package_name, Some(version_requirement)) + .await + .map_err(|err| match err { + MetadataStorageError::PackageMissing(name, ..) => { + Status::invalid_argument(format!("Invalid package: {}", name)) + } + _ => Status::internal("Something went wrong on our side"), + })?; + let response = versions.iter().map(|x| x.version.to_string()).collect(); + + Ok(Response::new(VersionsResponse { version: response })) } } diff --git a/registry/src/metadata.rs b/registry/src/metadata.rs index 6b0f170a..e6d8903f 100644 --- a/registry/src/metadata.rs +++ b/registry/src/metadata.rs @@ -20,6 +20,8 @@ pub mod memory; use buffrs::manifest::PackageManifest; +use buffrs::package::PackageName; +use semver::VersionReq; use std::fmt; use std::sync::Arc; @@ -39,7 +41,7 @@ pub type SharedError = Arc; pub enum MetadataStorageError { /// Package missing #[error("package missing")] - PackageMissing(String, String), + PackageMissing(String, Option), /// Duplicate package /// Used on put_version mostly @@ -68,6 +70,13 @@ pub trait MetadataStorage: Send + Sync + fmt::Debug { package: PackageVersion, ) -> Result; + /// Fetches a package with a given version and all packages above that version + async fn get_versions( + &self, + package_name: PackageName, + version: Option, + ) -> Result, MetadataStorageError>; + /// Puts the given package in the storage async fn put_version(&self, package: PackageManifest) -> Result<(), MetadataStorageError>; } diff --git a/registry/src/metadata/memory.rs b/registry/src/metadata/memory.rs index 82431481..175ba307 100644 --- a/registry/src/metadata/memory.rs +++ b/registry/src/metadata/memory.rs @@ -1,4 +1,5 @@ use super::*; + use std::collections::HashMap; use std::fmt::Debug; use std::sync::Mutex; @@ -41,7 +42,7 @@ impl MetadataStorage for InMemoryMetadataStorage { .get(name_string.as_str()) .ok_or(MetadataStorageError::PackageMissing( name_string.clone(), - version_string.clone(), + Some(version_string.clone()), ))?; let versions = versions_mutex @@ -53,12 +54,57 @@ impl MetadataStorage for InMemoryMetadataStorage { .get(version_string.as_str()) .ok_or(MetadataStorageError::PackageMissing( name_string, - version_string, + Some(version_string), ))?; Ok(package.clone()) } + async fn get_versions( + &self, + package: PackageName, + version: Option, + ) -> Result, MetadataStorageError> { + let packages = self + .packages + .lock() + .map_err(|_| MetadataStorageError::Internal)?; + + let package_name = package.to_string(); + let versions_mutex = packages + .get(package_name.as_str()) + .ok_or(MetadataStorageError::PackageMissing(package_name, None))?; + + let versions = versions_mutex + .lock() + .map_err(|_| MetadataStorageError::Internal)?; + + let listed_versions = if let Some(filtered_version) = version { + versions + .iter() + .filter(|(_version, manifest)| filtered_version.matches(&manifest.version)) + .map(|(_version, manifest)| buffrs::manifest::PackageManifest { + kind: manifest.kind, + name: manifest.name.clone(), + version: manifest.version.clone(), + description: manifest.description.clone(), + }) + .collect() + } else { + versions + .iter() + .map(|(_version, manifest)| buffrs::manifest::PackageManifest { + kind: manifest.kind, + name: manifest.name.clone(), + version: manifest.version.clone(), + description: manifest.description.clone(), + }) + .collect() + }; + + Ok(listed_versions) + } + /// Puts a Manifest in the storage /// async fn put_version(&self, package: PackageManifest) -> Result<(), MetadataStorageError> { diff --git a/registry/tests/api/main.rs b/registry/tests/api/main.rs index 951a6140..b15662be 100644 --- a/registry/tests/api/main.rs +++ b/registry/tests/api/main.rs @@ -3,7 +3,7 @@ use buffrs_registry::metadata::memory::InMemoryMetadataStorage; use buffrs_registry::proto::buffrs::package::{Compressed, Package}; use buffrs_registry::proto::buffrs::registry::registry_client::RegistryClient; use buffrs_registry::proto::buffrs::registry::registry_server::RegistryServer; -use buffrs_registry::proto::buffrs::registry::PublishRequest; +use buffrs_registry::proto::buffrs::registry::{PublishRequest, VersionsRequest}; use buffrs_registry::storage; use std::net::SocketAddr; use std::path::Path; @@ -16,12 +16,12 @@ use tonic::transport::{Endpoint, Uri}; use tonic::Code; use tower::service_fn; -pub fn create_publish_request_sample() -> PublishRequest { +pub fn create_publish_request_sample(version: Option) -> PublishRequest { PublishRequest { package: Some(Compressed { metadata: Some(Package { name: "testing".to_string(), - version: "1.0.0".to_string(), + version: version.unwrap_or("1.0.0".to_string()), r#type: 0, }), tgz: vec![0, 0, 0], @@ -29,6 +29,13 @@ pub fn create_publish_request_sample() -> PublishRequest { } } +pub fn create_list_versions_request_sample(version: String) -> VersionsRequest { + VersionsRequest { + name: "testing".to_string(), + requirement: version, + } +} + pub async fn basic_setup() -> RegistryClient { let (client, server) = tokio::io::duplex(1024); @@ -80,17 +87,64 @@ async fn test_publish_registry() { // 1. Insert a package and expect it to be successful { - let req = tonic::Request::new(create_publish_request_sample()); + let req = tonic::Request::new(create_publish_request_sample(None)); client.publish(req).await.expect("Shouldn't happen"); - println!(":: Package Publish OK"); + println!(":: Package Publish 1.0.0 OK"); } // 2. Insert the same package for a duplicate check { // duplicate check - let req = tonic::Request::new(create_publish_request_sample()); + let req = tonic::Request::new(create_publish_request_sample(None)); let res = client.publish(req).await.unwrap_err(); assert_eq!(res.code(), Code::AlreadyExists); println!(":: Package Forbid Duplicate OK"); } + + // 3. Insert a package with another version and expect it to be successful + { + let req = tonic::Request::new(create_publish_request_sample(Some("1.0.1".to_string()))); + client + .publish(req) + .await + .expect("Publishing package failed"); + println!(":: Package Publish 1.0.1 OK"); + } +} + +#[tokio::test] +async fn test_fetching_versions() { + let mut client = basic_setup().await; + + // 1. Insert a package with 1.0.0 version and expect it to be successful + { + let req = tonic::Request::new(create_publish_request_sample(None)); + client + .publish(req) + .await + .expect("Publishing package failed"); + println!(":: Package Publish 1.0.0 OK"); + } + // 1. Insert a package with 1.1.1 version and expect it to be successful + { + let req = tonic::Request::new(create_publish_request_sample(Some("1.1.1".to_string()))); + client + .publish(req) + .await + .expect("Publishing package failed"); + println!(":: Package Publish 1.1.1 OK"); + } + + // 2. Fetch packages with version restriction + { + // duplicate check + let req = tonic::Request::new(create_list_versions_request_sample(">=1.1".to_string())); + let res = client.versions(req).await.expect("get versions failed"); + let versions = res.into_inner().version; + + let expected_version = "1.1.1".to_string(); + assert_eq!(versions.len(), 1); + assert_eq!(versions, vec![expected_version]); + println!(":: Package Versions OK"); + } }