freighter-server 1.0.0

Cloudflare's third-party Rust registry implementation
Documentation
pub mod utils;

use std::{
    collections::{BTreeMap, HashSet},
    future::Future,
    net::{IpAddr, Ipv4Addr, SocketAddr},
    pin::Pin,
    sync::Arc,
};

use async_trait::async_trait;
use axum::body::Bytes;
use freighter_api_types::index::request::{ListQuery, Publish};
use freighter_api_types::index::response::{
    CompletedPublication, CrateVersion, ListAll, ListAllCrateEntry, ListAllCrateVersion,
    SearchResults,
};
use freighter_api_types::index::{IndexError, IndexProvider, IndexResult};
use freighter_api_types::ownership::response::ListedOwner;
use freighter_api_types::storage::{StorageProvider, StorageResult};
use freighter_auth::{AuthError, AuthProvider, AuthResult};
use freighter_server::{ServiceConfig, ServiceState};
use semver::Version;

#[derive(Default)]
pub struct MockIndexProvider {
    pub crates: BTreeMap<String, Vec<CrateVersion>>,
}

#[async_trait]
impl IndexProvider for MockIndexProvider {
    type Config = ();

    async fn healthcheck(&self) -> anyhow::Result<()> {
        Ok(())
    }

    async fn get_sparse_entry(&self, crate_name: &str) -> IndexResult<Vec<CrateVersion>> {
        if let Some(versions) = self.crates.get(crate_name).cloned() {
            Ok(versions)
        } else {
            Err(IndexError::NotFound)
        }
    }
    async fn confirm_existence(&self, _crate_name: &str, _version: &Version) -> IndexResult<bool> {
        unimplemented!()
    }
    async fn yank_crate(&self, _crate_name: &str, _version: &Version) -> IndexResult<()> {
        unimplemented!()
    }
    async fn unyank_crate(&self, _crate_name: &str, _version: &Version) -> IndexResult<()> {
        unimplemented!()
    }
    async fn search(&self, _query_string: &str, _limit: usize) -> IndexResult<SearchResults> {
        unimplemented!()
    }
    async fn publish(
        &self,
        _version: &Publish,
        _checksum: &str,
        end_step: Pin<&mut (dyn Future<Output = IndexResult<()>> + Send)>,
    ) -> IndexResult<CompletedPublication> {
        end_step.await?;
        Ok(CompletedPublication { warnings: None })
    }

    async fn list(&self, _pagination: &ListQuery) -> IndexResult<ListAll> {
        let crates = self
            .crates
            .iter()
            .map(|(k, v)| {
                let versions = v
                    .iter()
                    .map(|v| ListAllCrateVersion {
                        version: v.vers.clone(),
                    })
                    .collect();

                ListAllCrateEntry {
                    name: k.clone(),
                    description: format!("Description {k}"),
                    created_at: Default::default(),
                    updated_at: Default::default(),
                    versions,
                    homepage: Some("e.com".to_owned()),
                    repository: Some("ssh://git@b.com/a/f.git".to_owned()),
                    documentation: None,
                    keywords: vec!["example".to_owned()],
                    categories: vec!["a".to_owned(), "x".to_owned()],
                }
            })
            .collect();

        Ok(ListAll { results: crates })
    }
}

#[derive(Clone, Default)]
pub struct MockStorageProvider;

#[async_trait]
impl StorageProvider for MockStorageProvider {
    async fn pull_crate(&self, _name: &str, _version: &str) -> StorageResult<Bytes> {
        unimplemented!()
    }

    async fn put_crate(
        &self,
        _name: &str,
        _version: &str,
        _crate_bytes: Bytes,
        _sha256: [u8; 32],
    ) -> StorageResult<()> {
        Ok(())
    }

    async fn delete_crate(&self, _name: &str, _version: &str) -> StorageResult<()> {
        Ok(())
    }

    async fn healthcheck(&self) -> anyhow::Result<()> {
        Ok(())
    }
}

#[derive(Default)]
pub struct MockAuthProvider {
    pub valid_tokens: HashSet<String>,
}

#[async_trait]
impl AuthProvider for MockAuthProvider {
    type Config = ();

    async fn register(&self, _username: &str) -> AuthResult<String> {
        unimplemented!()
    }
    async fn list_owners(&self, _token: &str, _crate_name: &str) -> AuthResult<Vec<ListedOwner>> {
        unimplemented!()
    }
    async fn add_owners(&self, _token: &str, _users: &[&str], _crate_name: &str) -> AuthResult<()> {
        unimplemented!()
    }
    async fn remove_owners(
        &self,
        _token: &str,
        _users: &[&str],
        _crate_name: &str,
    ) -> AuthResult<()> {
        unimplemented!()
    }
    async fn publish(&self, token: &str, _crate_name: &str) -> AuthResult<()> {
        if self.valid_tokens.contains(token) {
            Ok(())
        } else {
            Err(AuthError::Forbidden)
        }
    }
    async fn auth_yank(&self, _token: &str, _crate_name: &str) -> AuthResult<()> {
        unimplemented!()
    }

    async fn auth_view_full_index(&self, token: &str) -> AuthResult<()> {
        if self.valid_tokens.contains(token) {
            Ok(())
        } else {
            Err(AuthError::Forbidden)
        }
    }

    async fn auth_config(&self, token: &str) -> AuthResult<()> {
        if self.valid_tokens.contains(token) {
            Ok(())
        } else {
            Err(AuthError::Forbidden)
        }
    }

    async fn healthcheck(&self) -> anyhow::Result<()> {
        Ok(())
    }
}

pub struct ServiceStateBuilder {
    pub config: ServiceConfig,
    pub index: MockIndexProvider,
    pub storage: MockStorageProvider,
    pub auth: MockAuthProvider,
}

impl Default for ServiceStateBuilder {
    fn default() -> Self {
        Self {
            config: ServiceConfig {
                address: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 3000),
                download_endpoint: "localhost:4000".to_owned(),
                api_endpoint: "localhost:5000".to_owned(),
                metrics_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 3001),
                allow_registration: true,
                auth_required: false,
            },
            index: Default::default(),
            storage: Default::default(),
            auth: Default::default(),
        }
    }
}

impl ServiceStateBuilder {
    #[must_use]
    pub fn index_provider(mut self, provider: MockIndexProvider) -> Self {
        self.index = provider;
        self
    }

    #[must_use]
    pub fn storage_provider(mut self, provider: MockStorageProvider) -> Self {
        self.storage = provider;
        self
    }

    #[must_use]
    pub fn auth_provider(mut self, provider: MockAuthProvider) -> Self {
        self.auth = provider;
        self
    }

    #[must_use]
    pub fn auth_required(mut self, req: bool) -> Self {
        self.config.auth_required = req;
        self
    }

    #[must_use]
    pub fn build(
        self,
    ) -> Arc<ServiceState<MockIndexProvider, MockStorageProvider, MockAuthProvider>> {
        Arc::new(ServiceState {
            config: self.config,
            index: self.index,
            storage: self.storage,
            auth: self.auth,
        })
    }

    #[must_use]
    pub fn build_no_arc(
        self,
    ) -> ServiceState<MockIndexProvider, MockStorageProvider, MockAuthProvider> {
        ServiceState {
            config: self.config,
            index: self.index,
            storage: self.storage,
            auth: self.auth,
        }
    }
}