1use std::env;
2use std::path::PathBuf;
3use std::str::FromStr;
4
5use anyhow::{Context, Result, anyhow, bail};
6use url::Url;
7
8#[derive(Debug, Clone)]
10pub struct PackConfig {
11 pub source: PackSource,
12 pub index_location: IndexLocation,
13 pub cache_dir: PathBuf,
14 pub public_key: Option<String>,
15}
16
17impl PackConfig {
18 pub fn from_env() -> Result<Self> {
20 let source = env::var("PACK_SOURCE")
21 .ok()
22 .map(|value| PackSource::from_str(&value))
23 .transpose()?
24 .unwrap_or(PackSource::Fs);
25
26 let index_raw = env::var("PACK_INDEX_URL")
27 .context("PACK_INDEX_URL is required to locate the pack index")?;
28 let index_location = IndexLocation::from_value(&index_raw)?;
29
30 let cache_dir = env::var("PACK_CACHE_DIR")
31 .map(PathBuf::from)
32 .unwrap_or_else(|_| PathBuf::from(".packs"));
33
34 let public_key = env::var("PACK_PUBLIC_KEY").ok();
35
36 Ok(Self {
37 source,
38 index_location,
39 cache_dir,
40 public_key,
41 })
42 }
43}
44
45#[derive(Debug, Clone)]
47pub enum IndexLocation {
48 File(PathBuf),
49 Remote(Url),
50}
51
52impl IndexLocation {
53 pub fn from_value(value: &str) -> Result<Self> {
54 if value.starts_with("http://") || value.starts_with("https://") {
55 let url = Url::parse(value).context("PACK_INDEX_URL is not a valid URL")?;
56 return Ok(Self::Remote(url));
57 }
58 if value.starts_with("file://") {
59 let url = Url::parse(value).context("PACK_INDEX_URL is not a valid file:// URL")?;
60 let path = url
61 .to_file_path()
62 .map_err(|_| anyhow!("PACK_INDEX_URL points to an invalid file URI"))?;
63 return Ok(Self::File(path));
64 }
65 Ok(Self::File(PathBuf::from(value)))
66 }
67
68 pub fn display(&self) -> String {
69 match self {
70 Self::File(path) => path.display().to_string(),
71 Self::Remote(url) => url.to_string(),
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum PackSource {
79 Fs,
80 Http,
81 Oci,
82 S3,
83 Gcs,
84 AzBlob,
85}
86
87impl PackSource {
88 pub fn scheme(self) -> &'static str {
89 match self {
90 Self::Fs => "fs",
91 Self::Http => "http",
92 Self::Oci => "oci",
93 Self::S3 => "s3",
94 Self::Gcs => "gcs",
95 Self::AzBlob => "azblob",
96 }
97 }
98}
99
100impl FromStr for PackSource {
101 type Err = anyhow::Error;
102
103 fn from_str(value: &str) -> Result<Self> {
104 match value.to_ascii_lowercase().as_str() {
105 "fs" => Ok(Self::Fs),
106 "http" | "https" => Ok(Self::Http),
107 "oci" => Ok(Self::Oci),
108 "s3" => Ok(Self::S3),
109 "gcs" => Ok(Self::Gcs),
110 "azblob" | "azure" | "azureblob" => Ok(Self::AzBlob),
111 other => bail!("unsupported PACK_SOURCE `{other}`"),
112 }
113 }
114}