assemble_core/
resources.rs1use crate::dependencies::project_dependency::{subproject_url, ProjectUrlError, PROJECT_SCHEME};
2use crate::flow::shared::Artifact;
3use crate::identifier::{InvalidId, ProjectId};
4use crate::project::{GetProjectId, VisitProject};
5use crate::Project;
6use crate::__export::TaskId;
7use crate::lazy_evaluation::Provider;
8use crate::prelude::ProjectResult;
9use crate::project::buildable::Buildable;
10
11use crate::error::PayloadError;
12use std::collections::HashSet;
13use std::path::PathBuf;
14use thiserror::Error;
15use url::Url;
16
17#[derive(Debug, Clone, Eq, PartialEq, Hash)]
19pub struct ResourceLocation {
20 project: ProjectId,
21 configuration: Option<String>,
22}
23
24impl ResourceLocation {
25 pub fn new<'a, I>(project: ProjectId, configuration: I) -> Self
26 where
27 I: Into<Option<&'a str>>,
28 {
29 Self {
30 project,
31 configuration: configuration.into().map(|s| s.to_string()),
32 }
33 }
34
35 pub fn find<'a, P, I>(
36 project: &P,
37 path: &str,
38 configuration: I,
39 ) -> Result<Self, InvalidResourceLocation>
40 where
41 P: GetProjectId,
42 I: Into<Option<&'a str>>,
43 {
44 let url = subproject_url(project, path, configuration.into().map(str::to_string))?;
45 Self::try_from(url)
46 }
47
48 pub fn project(&self) -> &ProjectId {
49 &self.project
50 }
51
52 pub fn configuration(&self) -> Option<&str> {
53 self.configuration.as_deref()
54 }
55}
56
57impl From<ResourceLocation> for Url {
58 fn from(r: ResourceLocation) -> Self {
59 subproject_url(&r.project, "", r.configuration).unwrap()
60 }
61}
62
63impl TryFrom<Url> for ResourceLocation {
64 type Error = InvalidResourceLocation;
65
66 fn try_from(value: Url) -> Result<Self, Self::Error> {
67 if value.scheme() != PROJECT_SCHEME {
68 return Err(InvalidResourceLocation::BadSchema(
69 value.scheme().to_string(),
70 ));
71 }
72
73 let path = value.path();
74 if path.ends_with('/') {
75 let path = PathBuf::from(path);
77 let id = ProjectId::try_from(path.as_path())?;
78
79 Ok(Self::new(id, None))
80 } else {
81 let path = PathBuf::from(path);
83 let configuration = path.file_name().and_then(|os| os.to_str()).unwrap();
84 let project = ProjectId::try_from(path.parent().unwrap())?;
85
86 Ok(Self::new(project, configuration))
87 }
88 }
89}
90
91#[derive(Debug, Error)]
92pub enum InvalidResourceLocation {
93 #[error(
94 "Unexpected schema found, must be {:?} (found = {0:?})",
95 PROJECT_SCHEME
96 )]
97 BadSchema(String),
98 #[error(transparent)]
99 InvalidId(#[from] InvalidId),
100 #[error(transparent)]
101 ProjectUrlError(#[from] ProjectUrlError),
102 #[error("No resources could be found")]
103 NoResourceFound,
104}
105
106pub struct ResourceLocator {
108 location: ResourceLocation,
109}
110
111impl ResourceLocator {
112 pub fn new(location: ResourceLocation) -> Self {
113 Self { location }
114 }
115}
116
117impl VisitProject<Option<Box<dyn Artifact>>> for ResourceLocator {
118 fn visit(&mut self, project: &Project) -> Option<Box<dyn Artifact>> {
119 let mut project_ptr = project.root_project();
120
121 for part in self.location.project.iter().skip(1) {
122 project_ptr = project_ptr.with(|p| p.get_subproject(part).ok().cloned())?;
123 }
124
125 let artifact = project_ptr.with(|p| {
126 let configuration = self
127 .location
128 .configuration
129 .as_ref()
130 .cloned()
131 .unwrap_or(p.variants().default());
132
133 p.variant(&configuration)
134 })?;
135 Some(Box::new(artifact.get()))
136 }
137}
138
139pub trait ProjectResourceExt {
140 fn get_resource<R>(
142 &self,
143 resource: R,
144 ) -> Result<Box<dyn Artifact>, PayloadError<InvalidResourceLocation>>
145 where
146 R: TryInto<ResourceLocation>;
147}
148
149impl ProjectResourceExt for Project {
150 fn get_resource<R>(
151 &self,
152 resource: R,
153 ) -> Result<Box<dyn Artifact>, PayloadError<InvalidResourceLocation>>
154 where
155 R: TryInto<ResourceLocation>,
156 {
157 let location = resource
158 .try_into()
159 .map_err(|_| InvalidResourceLocation::NoResourceFound)
160 .map_err(PayloadError::new)?;
161 let mut visitor = ResourceLocator::new(location);
162 self.visitor(&mut visitor)
163 .ok_or(InvalidResourceLocation::NoResourceFound)
164 .map_err(PayloadError::new)
165 }
166}
167
168impl Buildable for ResourceLocation {
169 fn get_dependencies(&self, project: &Project) -> ProjectResult<HashSet<TaskId>> {
170 let resource = project.get_resource(self.clone()).map_err(PayloadError::into)?;
171 match resource.buildable() {
172 None => Ok(HashSet::new()),
173 Some(b) => b.get_dependencies(project),
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::prelude::ProjectId;
182 use crate::resources::ResourceLocation;
183 use std::str::FromStr;
184
185 #[test]
186 fn url_conversion() {
187 let resource1 = ResourceLocation::new(ProjectId::from_str(":root").unwrap(), None);
188 let as_url = Url::from(resource1.clone());
189 assert_eq!(ResourceLocation::try_from(as_url).unwrap(), resource1);
190
191 let resource2 = (ResourceLocation::find(
192 &ProjectId::from_str(":root").unwrap(),
193 "child1:child2",
194 Some("jar"),
195 ))
196 .unwrap();
197 assert_eq!(
198 resource2.project,
199 ProjectId::from_str(":root:child1:child2").unwrap()
200 );
201 let as_url = Url::from(resource2.clone());
202 assert_eq!(ResourceLocation::try_from(as_url).unwrap(), resource2);
203 }
204}