1use serde::{Deserialize, Serialize};
6use std::path::{Path, PathBuf};
7use strop_workspace::{AddressError, RemoteLocation};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum FileTarget {
13 Local(PathBuf),
14 Remote(RemoteLocation),
15 Container {
18 container: strop_workspace::ContainerId,
19 path: PathBuf,
20 },
21}
22
23impl Serialize for FileTarget {
24 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
25 #[derive(Serialize)]
26 struct RemoteRecord<'a> {
27 remote: &'a RemoteLocation,
28 }
29 #[derive(Serialize)]
30 struct ContainerRecord<'a> {
31 container: &'a strop_workspace::ContainerId,
32 #[serde(with = "strop_core::path_serde")]
33 path: &'a PathBuf,
34 }
35 match self {
36 Self::Local(path) => strop_core::path_serde::serialize(path, serializer),
37 Self::Remote(remote) => RemoteRecord { remote }.serialize(serializer),
38 Self::Container { container, path } => {
39 ContainerRecord { container, path }.serialize(serializer)
40 }
41 }
42 }
43}
44impl<'de> Deserialize<'de> for FileTarget {
45 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
46 #[derive(Deserialize)]
47 #[serde(untagged)]
48 enum Record {
49 Remote {
50 remote: RemoteLocation,
51 },
52 Container {
53 container: strop_workspace::ContainerId,
54 #[serde(with = "strop_core::path_serde")]
55 path: PathBuf,
56 },
57 Local(#[serde(with = "strop_core::path_serde")] PathBuf),
58 }
59 Ok(match Record::deserialize(deserializer)? {
60 Record::Remote { remote } => Self::Remote(remote),
61 Record::Container { container, path } => Self::Container { container, path },
62 Record::Local(path) => Self::Local(path),
63 })
64 }
65}
66impl FileTarget {
67 pub fn resource_location(&self) -> Option<strop_workspace::ResourceLocation> {
68 use strop_workspace::{Filesystem, ResourceLocation};
69 match self {
70 Self::Local(path) if path.is_absolute() => Some(ResourceLocation::local(path.clone())),
71 Self::Remote(location) => location.absolute_file().map(|file| {
72 ResourceLocation::remote(file.endpoint().clone(), file.path().to_path_buf())
73 }),
74 Self::Container { container, path } if path.is_absolute() => Some(ResourceLocation {
75 filesystem: Filesystem::Container(container.clone()),
76 path: path.clone(),
77 }),
78 _ => None,
79 }
80 }
81 pub fn from_location(
82 location: &strop_workspace::ResourceLocation,
83 ) -> Result<Self, AddressError> {
84 use strop_workspace::Filesystem;
85 if !location.path.is_absolute() {
86 return Err(AddressError::RelativePath);
87 }
88 Ok(match &location.filesystem {
89 Filesystem::Local => Self::Local(location.path.clone()),
90 Filesystem::Remote(endpoint) => Self::Remote(
91 strop_workspace::RemoteFile::from_path(endpoint.clone(), location.path.clone())?
92 .into(),
93 ),
94 Filesystem::Container(container) => Self::Container {
95 container: container.clone(),
96 path: location.path.clone(),
97 },
98 })
99 }
100
101 pub fn matches_location(&self, location: &strop_workspace::ResourceLocation) -> bool {
102 use strop_workspace::Filesystem;
103 match (self, &location.filesystem) {
104 (Self::Local(path), Filesystem::Local) => path == &location.path,
105 (Self::Remote(remote), Filesystem::Remote(endpoint)) => remote
106 .absolute_file()
107 .is_some_and(|file| file.endpoint() == endpoint && file.path() == location.path),
108 (Self::Container { container, path }, Filesystem::Container(expected)) => {
109 container == expected && path == &location.path
110 }
111 _ => false,
112 }
113 }
114 pub fn parse(value: PathBuf) -> Result<Self, AddressError> {
117 if let Some(text) = value.to_str() {
118 if text.starts_with("ssh://") {
119 return RemoteLocation::parse(text).map(Self::Remote);
120 }
121 if text.starts_with("file://") || text.starts_with("container:") {
122 return strop_workspace::ResourceLocation::parse_uri(text)
123 .and_then(|location| Self::from_location(&location));
124 }
125 }
126 Ok(Self::Local(value))
127 }
128 pub fn local_path(&self) -> Option<&Path> {
129 match self {
130 Self::Local(path) => Some(path),
131 _ => None,
132 }
133 }
134}
135impl std::fmt::Display for FileTarget {
136 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 match self {
138 Self::Local(path) => path.display().fmt(formatter),
139 Self::Remote(file) => file.fmt(formatter),
140 Self::Container { container, path } => write!(
141 formatter,
142 "container:{}{}",
143 &container.as_str()[..12],
144 path.display()
145 ),
146 }
147 }
148}