nreplops_tool/conn_expr/
resolution.rs

1// conn_expr/resolution.rs
2// Copyright 2022 Matti Hänninen
3//
4// Licensed under the Apache License, Version 2.0 (the "License"); you may not
5// use this file except in compliance with the License. You may obtain a copy of
6// the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13// License for the specific language governing permissions and limitations under
14// the License.
15
16use std::{fs, io, path, thread, time};
17
18use crate::error::Error;
19
20use super::conn_expr::ConnectionExpr;
21
22#[derive(Debug)]
23pub enum ConnectionExprSource {
24  /// Use this connection expression.
25  Direct(ConnectionExpr),
26  /// Read the connection expression from the port file.
27  PortFile {
28    /// Use this instead of the nearest .nrepl-port file.
29    path: Option<path::PathBuf>,
30    /// If not available, give it this amount of time to appear.
31    wait_for: Option<time::Duration>,
32  },
33}
34
35impl From<ConnectionExpr> for ConnectionExprSource {
36  fn from(e: ConnectionExpr) -> Self {
37    ConnectionExprSource::Direct(e)
38  }
39}
40
41impl From<&ConnectionExpr> for ConnectionExprSource {
42  fn from(e: &ConnectionExpr) -> Self {
43    e.clone().into()
44  }
45}
46
47impl ConnectionExprSource {
48  pub fn resolve_expr(&self) -> Result<ConnectionExpr, Error> {
49    const THROTTLING_DELAY: time::Duration = time::Duration::from_millis(50);
50    match self {
51      ConnectionExprSource::Direct(e) => Ok(e.clone()),
52      ConnectionExprSource::PortFile {
53        path,
54        wait_for: None,
55      } => try_load_from_port_file(path.as_ref()),
56      ConnectionExprSource::PortFile {
57        path,
58        wait_for: Some(duration),
59      } => {
60        let deadline = time::SystemTime::now() + *duration;
61        loop {
62          match try_load_from_port_file(path.as_ref()) {
63            Ok(r) => return Ok(r),
64            Err(e) => match e {
65              Error::NotSpecified | Error::NotFound(_) => {
66                if time::SystemTime::now() >= deadline {
67                  return Err(Error::PortFileTimeout);
68                }
69              }
70              _ => return Err(e),
71            },
72          }
73          thread::sleep(THROTTLING_DELAY);
74        }
75      }
76    }
77  }
78}
79
80fn try_load_from_port_file(
81  given: Option<impl AsRef<path::Path>>,
82) -> Result<ConnectionExpr, Error> {
83  if let Some(f) = given {
84    load_from_port_file(f)
85  } else if let Some(ref f) = find_port_file().map_err(|_| Error::Unknown)? {
86    load_from_port_file(f)
87  } else {
88    Err(Error::NotSpecified)
89  }
90}
91
92fn find_port_file() -> io::Result<Option<path::PathBuf>> {
93  let current_dir = path::PathBuf::from(".").canonicalize()?;
94  for dir in current_dir.ancestors() {
95    let mut path = path::PathBuf::from(dir);
96    path.push(".nrepl-port");
97    if path.is_file() {
98      return Ok(Some(path));
99    }
100  }
101  Ok(None)
102}
103
104fn load_from_port_file(
105  path: impl AsRef<path::Path>,
106) -> Result<ConnectionExpr, Error> {
107  let path = path.as_ref();
108  fs::read_to_string(path)
109    .map_err(|e| {
110      if e.kind() == io::ErrorKind::NotFound {
111        Error::NotFound(path.to_string_lossy().into())
112      } else {
113        Error::CannotReadFile(path.to_string_lossy().into())
114      }
115    })?
116    .trim()
117    .parse()
118    .map_err(|_| Error::CannotParsePortFile(path.to_string_lossy().into()))
119}