• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dcdpr / jp / 21478625683

29 Jan 2026 12:44PM UTC coverage: 53.861% (-0.03%) from 53.886%
21478625683

push

github

web-flow
refactor: switch to `camino` for UTF-8 path handling (#385)

The codebase now uses `camino::Utf8Path` and `camino::Utf8PathBuf`
instead of the standard library's `Path` and `PathBuf`. This ensures
that paths handled by the application are valid UTF-8, which simplifies
string conversions and improves interoperability with other crates and
CLI output.

Affected crates include `jp_attachment` and its implementations, as well
as the `jp_cli` crate. Dev-dependencies have also been updated to use
`camino-tempfile` for testing.

---------

Signed-off-by: Jean Mertz <git@jeanmertz.com>

48 of 217 new or added lines in 22 files covered. (22.12%)

8 existing lines in 5 files now uncovered.

10728 of 19918 relevant lines covered (53.86%)

117.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/crates/jp_cli/src/parser.rs
1
use std::convert::Infallible;
2

3
use camino::{FromPathBufError, Utf8Path, Utf8PathBuf, absolute_utf8};
4
use clean_path::Clean as _;
5
use relative_path::RelativePathBuf;
6
use tracing::trace;
7
use url::Url;
8

9
use crate::error::{Error, Result};
10

11
#[derive(Debug, Clone)]
12
pub(crate) enum AttachmentUrlOrPath {
13
    Url(Url),
14
    Path(RelativePathBuf),
15
}
16

17
impl AttachmentUrlOrPath {
NEW
18
    pub fn parse(&self, root: Option<&Utf8Path>) -> Result<Url> {
×
19
        let path = match &self {
×
20
            AttachmentUrlOrPath::Url(url) => return Ok(url.clone()),
×
21
            AttachmentUrlOrPath::Path(path) => path,
×
22
        };
23

24
        // Special case for file attachments
25
        trace!(
×
26
            path = path.as_str(),
×
27
            "URI is not a valid URL, treating as file path."
28
        );
29
        let (path, exclude) = match path.as_str().strip_prefix('!') {
×
30
            Some(path) => (path, "?exclude=true"),
×
31
            None => (path.as_str(), ""),
×
32
        };
33

34
        // Check if the path falls within the workspace.
35
        //
36
        // If `root` is `None`, then we allow absolute paths, otherwise we
37
        // assume the context is a workspace and we only allow relative paths.
NEW
38
        let mut path = Utf8PathBuf::from(path);
×
39
        if let Some(root) = root {
×
40
            if path.is_relative() {
×
NEW
41
                path = absolute_utf8(&path).map_err(|error| {
×
NEW
42
                    Error::Attachment(format!(
×
NEW
43
                        "Attachment path {path} is relative, but the current directory could not \
×
NEW
44
                         be determined: {error}",
×
NEW
45
                    ))
×
NEW
46
                })?;
×
UNCOV
47
            }
×
48

49
            if !path.exists() {
×
50
                return Err(Error::Attachment(format!(
×
NEW
51
                    "Attachment path does not exist: {path}",
×
52
                )));
×
53
            }
×
54

NEW
55
            let p: Utf8PathBuf = path
×
NEW
56
                .as_std_path()
×
NEW
57
                .clean()
×
NEW
58
                .try_into()
×
NEW
59
                .map_err(FromPathBufError::into_io_error)?;
×
60

61
            let Ok(p) = p.strip_prefix(root) else {
×
62
                return Err(Error::Attachment(format!(
×
NEW
63
                    "Attachment path must be relative to the workspace: {path}",
×
64
                )));
×
65
            };
66

67
            path = p.to_path_buf();
×
68
        } else if !path.exists() {
×
69
            return Err(Error::Attachment(format!(
×
NEW
70
                "Attachment path does not exist: {path}",
×
71
            )));
×
72
        }
×
73

NEW
74
        Url::parse(&format!("file:{path}{exclude}")).map_err(Into::into)
×
75
    }
×
76
}
77

78
impl std::str::FromStr for AttachmentUrlOrPath {
79
    type Err = Infallible;
80

81
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
×
82
        Url::parse(s)
×
83
            .map(Self::Url)
×
84
            .or_else(|_| Ok(Self::Path(RelativePathBuf::from(s))))
×
85
    }
×
86
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc