haste_fhir_package/
lib.rs1mod error;
11mod registry;
12mod tarball;
13
14pub use error::PackageError;
15pub use registry::{download, resolve_version};
16pub use tarball::{Limits, pack, read};
17
18use serde::Deserialize;
19use std::{collections::BTreeMap, fmt::Display, str::FromStr};
20
21#[derive(Debug, Clone, Default, Deserialize)]
23#[serde(default)]
24pub struct Manifest {
25 pub name: String,
26 pub version: String,
27 pub canonical: Option<String>,
29 pub title: Option<String>,
30 pub description: Option<String>,
31 #[serde(rename = "fhirVersions")]
32 pub fhir_versions: Vec<String>,
33 pub dependencies: BTreeMap<String, String>,
35 pub directories: Directories,
36}
37
38#[derive(Debug, Clone, Default, Deserialize)]
39#[serde(default)]
40pub struct Directories {
41 pub example: Option<String>,
44}
45
46#[derive(Debug, Clone)]
48pub struct PackageFile {
49 pub path: String,
51 pub content: Vec<u8>,
52}
53
54#[derive(Debug, Clone)]
56pub struct Package {
57 pub manifest: Manifest,
58 pub resources: Vec<PackageFile>,
61 pub examples: Vec<PackageFile>,
63 pub unpacked_bytes: u64,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct PackageSpec {
72 pub name: String,
73 pub version: Option<String>,
74}
75
76impl FromStr for PackageSpec {
77 type Err = PackageError;
78
79 fn from_str(value: &str) -> Result<Self, Self::Err> {
80 let value = value.trim();
81 let (name, version) = match value.find(['@', '#']) {
82 Some(at) => (&value[..at], Some(&value[at + 1..])),
83 None => (value, None),
84 };
85 PackageSpec::new(name, version)
86 }
87}
88
89impl PackageSpec {
90 pub fn new(name: &str, version: Option<&str>) -> Result<Self, PackageError> {
96 if !valid_name(name) {
97 return Err(PackageError::InvalidName(name.to_string()));
98 }
99 let version = match version.map(str::trim) {
100 None | Some("") => None,
101 Some(version) if valid_version(version) => Some(version.to_string()),
102 Some(version) => return Err(PackageError::InvalidVersion(version.to_string())),
103 };
104 Ok(PackageSpec {
105 name: name.to_string(),
106 version,
107 })
108 }
109}
110
111impl Display for PackageSpec {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 match &self.version {
114 Some(version) => write!(f, "{}@{version}", self.name),
115 None => write!(f, "{}", self.name),
116 }
117 }
118}
119
120#[must_use]
123pub fn valid_name(name: &str) -> bool {
124 !name.is_empty()
125 && name.len() <= 214
126 && name.starts_with(|c: char| c.is_ascii_alphanumeric())
127 && name
128 .chars()
129 .all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '_'))
130}
131
132#[must_use]
135pub fn valid_version(version: &str) -> bool {
136 !version.is_empty()
137 && version.len() <= 64
138 && version.starts_with(|c: char| c.is_ascii_alphanumeric())
139 && version
140 .chars()
141 .all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '+'))
142}