wasm_pkg_client/
local.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! Local filesystem-based package backend.
//!
//! Each package release is a file: `<root>/<namespace>/<name>/<version>.wasm`

use std::path::PathBuf;

use anyhow::anyhow;
use async_trait::async_trait;
use futures_util::{StreamExt, TryStreamExt};
use serde::Deserialize;
use tokio_util::io::ReaderStream;
use wasm_pkg_common::{
    config::RegistryConfig,
    digest::ContentDigest,
    package::{PackageRef, Version},
    Error,
};

use crate::{
    loader::PackageLoader,
    publisher::PackagePublisher,
    release::{Release, VersionInfo},
    ContentStream, PublishingSource,
};

#[derive(Clone, Debug, Deserialize)]
pub struct LocalConfig {
    pub root: PathBuf,
}

pub(crate) struct LocalBackend {
    root: PathBuf,
}

impl LocalBackend {
    pub fn new(registry_config: RegistryConfig) -> Result<Self, Error> {
        let config = registry_config
            .backend_config::<LocalConfig>("local")?
            .ok_or_else(|| {
                Error::InvalidConfig(anyhow!("'local' backend requires configuration"))
            })?;
        Ok(Self { root: config.root })
    }

    fn package_dir(&self, package: &PackageRef) -> PathBuf {
        self.root
            .join(package.namespace().as_ref())
            .join(package.name().as_ref())
    }

    fn version_path(&self, package: &PackageRef, version: &Version) -> PathBuf {
        self.package_dir(package).join(format!("{version}.wasm"))
    }
}

#[async_trait]
impl PackageLoader for LocalBackend {
    async fn list_all_versions(&self, package: &PackageRef) -> Result<Vec<VersionInfo>, Error> {
        let mut versions = vec![];
        let package_dir = self.package_dir(package);
        tracing::debug!(?package_dir, "Reading versions from path");
        let mut entries = tokio::fs::read_dir(package_dir).await?;
        while let Some(entry) = entries.next_entry().await? {
            let path = entry.path();
            if path.extension() != Some("wasm".as_ref()) {
                continue;
            }
            let Some(version) = path
                .file_stem()
                .unwrap()
                .to_str()
                .and_then(|stem| Version::parse(stem).ok())
            else {
                tracing::warn!("invalid package file name at {path:?}");
                continue;
            };
            versions.push(VersionInfo {
                version,
                yanked: false,
            });
        }
        Ok(versions)
    }

    async fn get_release(&self, package: &PackageRef, version: &Version) -> Result<Release, Error> {
        let path = self.version_path(package, version);
        tracing::debug!(path = %path.display(), "Reading content from path");
        let content_digest = ContentDigest::sha256_from_file(path).await?;
        Ok(Release {
            version: version.clone(),
            content_digest,
        })
    }

    async fn stream_content_unvalidated(
        &self,
        package: &PackageRef,
        content: &Release,
    ) -> Result<ContentStream, Error> {
        let path = self.version_path(package, &content.version);
        tracing::debug!("Streaming content from {path:?}");
        let file = tokio::fs::File::open(path).await?;
        Ok(ReaderStream::new(file).map_err(Into::into).boxed())
    }
}

#[async_trait::async_trait]
impl PackagePublisher for LocalBackend {
    async fn publish(
        &self,
        package: &PackageRef,
        version: &Version,
        mut data: PublishingSource,
    ) -> Result<(), Error> {
        let package_dir = self.package_dir(package);
        // Ensure the package directory exists.
        tokio::fs::create_dir_all(package_dir).await?;
        let path = self.version_path(package, version);
        let mut out = tokio::fs::File::create(path).await?;
        tokio::io::copy(&mut data, &mut out)
            .await
            .map_err(Error::IoError)
            .map(|_| ())
    }
}