Skip to content

Commit 34ce68d

Browse files
committed
Reduce required dependencies
- Move `tracing` to feature flag - Replace `color-eyre` with `thiserror`
1 parent 3b92f62 commit 34ce68d

File tree

5 files changed

+132
-53
lines changed

5 files changed

+132
-53
lines changed

Cargo.toml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@ license = "MIT"
99
name = "git-url-parse"
1010
readme = "README.md"
1111
repository = "https://github.com/tjtelan/git-url-parse-rs"
12-
version = "0.4.5"
12+
version = "0.4.6"
13+
14+
[features]
15+
default = []
16+
tracing = ["dep:tracing"]
1317

1418
[dependencies]
15-
tracing = "0.1"
16-
url = "^2.2"
17-
strum = "^0.26"
18-
strum_macros = "^0.26"
19-
color-eyre = "^0.6"
19+
tracing = { version = "0.1", optional = true }
20+
url = { version = "^2.2", default-features = false }
21+
strum = { version = "^0.26", features = ["derive"] }
22+
thiserror = "^1.0"
2023

2124
[dev-dependencies]
2225
env_logger = "^0.11"
23-
regex = "^1.4"
26+
regex = "^1.10"

examples/multi.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
use color_eyre::Result;
2-
use git_url_parse::GitUrl;
1+
use git_url_parse::{GitUrl, GitUrlParseError};
32

4-
fn main() -> Result<()> {
3+
fn main() -> Result<(), GitUrlParseError> {
54
env_logger::init();
65

76
let test_vec = vec![

examples/trim_auth.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
use color_eyre::Result;
2-
use git_url_parse::GitUrl;
1+
use git_url_parse::{GitUrl, GitUrlParseError};
32

4-
fn main() -> Result<()> {
3+
fn main() -> Result<(), GitUrlParseError> {
54
env_logger::init();
65

76
let test_vec = vec![

src/lib.rs

Lines changed: 116 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
use color_eyre::eyre::{eyre, WrapErr};
2-
pub use color_eyre::Result;
31
use std::fmt;
42
use std::str::FromStr;
5-
use strum_macros::{Display, EnumString, VariantNames};
6-
use tracing::debug;
3+
use strum::{Display, EnumString, VariantNames};
4+
use thiserror::Error;
75
use url::Url;
86

7+
#[cfg(feature = "tracing")]
8+
use tracing::debug;
9+
910
/// Supported uri schemes for parsing
1011
#[derive(Debug, PartialEq, Eq, EnumString, VariantNames, Clone, Display, Copy)]
1112
#[strum(serialize_all = "kebab_case")]
@@ -135,7 +136,7 @@ impl Default for GitUrl {
135136
}
136137

137138
impl FromStr for GitUrl {
138-
type Err = color_eyre::Report;
139+
type Err = GitUrlParseError;
139140

140141
fn from_str(s: &str) -> Result<Self, Self::Err> {
141142
GitUrl::parse(s)
@@ -153,14 +154,22 @@ impl GitUrl {
153154
}
154155

155156
/// Returns a `Result<GitUrl>` after normalizing and parsing `url` for metadata
156-
pub fn parse(url: &str) -> Result<GitUrl> {
157+
pub fn parse(url: &str) -> Result<GitUrl, GitUrlParseError> {
157158
// Normalize the url so we can use Url crate to process ssh urls
158-
let normalized = normalize_url(url)
159-
.with_context(|| "Url normalization into url::Url failed".to_string())?;
159+
let normalized = if let Ok(url) = normalize_url(url) {
160+
url
161+
} else {
162+
return Err(GitUrlParseError::UrlNormalizeFailed);
163+
};
160164

161165
// Some pre-processing for paths
162-
let scheme = Scheme::from_str(normalized.scheme())
163-
.with_context(|| format!("Scheme unsupported: {:?}", normalized.scheme()))?;
166+
let scheme = if let Ok(scheme) = Scheme::from_str(normalized.scheme()) {
167+
scheme
168+
} else {
169+
return Err(GitUrlParseError::UnsupportedScheme(
170+
normalized.scheme().to_string(),
171+
));
172+
};
164173

165174
// Normalized ssh urls can always have their first '/' removed
166175
let urlpath = match &scheme {
@@ -176,6 +185,7 @@ impl GitUrl {
176185

177186
// Parse through path for name,owner,organization
178187
// Support organizations for Azure Devops
188+
#[cfg(feature = "tracing")]
179189
debug!("The urlpath: {:?}", &urlpath);
180190

181191
// Most git services use the path for metadata in the same way, so we're going to separate
@@ -186,10 +196,14 @@ impl GitUrl {
186196
//
187197
// organizations are going to be supported on a per-host basis
188198
let splitpath = &urlpath.rsplit_terminator('/').collect::<Vec<&str>>();
199+
200+
#[cfg(feature = "tracing")]
189201
debug!("rsplit results for metadata: {:?}", splitpath);
190202

191203
let name = splitpath[0].trim_end_matches(".git").to_string();
192204

205+
// TODO: I think here is where we want to update the url pattern identification step.. I want to be able to have a hint that the user can pass
206+
193207
let (owner, organization, fullname) = match &scheme {
194208
// We're not going to assume anything about metadata from a filepath
195209
Scheme::File => (None::<String>, None::<String>, name.clone()),
@@ -200,12 +214,15 @@ impl GitUrl {
200214
let hosts_w_organization_in_path = vec!["dev.azure.com", "ssh.dev.azure.com"];
201215
//vec!["dev.azure.com", "ssh.dev.azure.com", "visualstudio.com"];
202216

203-
let host_str = normalized
204-
.host_str()
205-
.ok_or(eyre!("Host from URL could not be represented as str"))?;
217+
let host_str = if let Some(host) = normalized.host_str() {
218+
host
219+
} else {
220+
return Err(GitUrlParseError::UnsupportedUrlHostFormat);
221+
};
206222

207223
match hosts_w_organization_in_path.contains(&host_str) {
208224
true => {
225+
#[cfg(feature = "tracing")]
209226
debug!("Found a git provider with an org");
210227

211228
// The path differs between git:// and https:// schemes
@@ -241,16 +258,18 @@ impl GitUrl {
241258
fullname.join("/"),
242259
)
243260
}
244-
_ => return Err(eyre!("Scheme not supported for host")),
261+
262+
// TODO: I'm not sure if I want to support throwing this error long-term
263+
_ => return Err(GitUrlParseError::UnexpectedScheme),
245264
}
246265
}
247266
false => {
248267
if !url.starts_with("ssh") && splitpath.len() < 2 {
249-
return Err(eyre!("git url is not of expected format"));
268+
return Err(GitUrlParseError::UnexpectedFormat);
250269
}
251270

252271
let position = match splitpath.len() {
253-
0 => return Err(eyre!("git url is not of expected format")),
272+
0 => return Err(GitUrlParseError::UnexpectedFormat),
254273
1 => 0,
255274
_ => 1,
256275
};
@@ -312,52 +331,60 @@ impl GitUrl {
312331
/// Prepends `ssh://` to url
313332
///
314333
/// Supports absolute and relative paths
315-
fn normalize_ssh_url(url: &str) -> Result<Url> {
334+
fn normalize_ssh_url(url: &str) -> Result<Url, GitUrlParseError> {
316335
let u = url.split(':').collect::<Vec<&str>>();
317336

318337
match u.len() {
319338
2 => {
339+
#[cfg(feature = "tracing")]
320340
debug!("Normalizing ssh url: {:?}", u);
321341
normalize_url(&format!("ssh://{}/{}", u[0], u[1]))
322342
}
323343
3 => {
344+
#[cfg(feature = "tracing")]
324345
debug!("Normalizing ssh url with ports: {:?}", u);
325346
normalize_url(&format!("ssh://{}:{}/{}", u[0], u[1], u[2]))
326347
}
327-
_default => Err(eyre!("SSH normalization pattern not covered for: {:?}", u)),
348+
_default => Err(GitUrlParseError::UnsupportedSshUrlFormat),
328349
}
329350
}
330351

331352
/// `normalize_file_path` takes in a filepath and uses `Url::from_file_path()` to parse
332353
///
333354
/// Prepends `file://` to url
334355
#[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))]
335-
fn normalize_file_path(filepath: &str) -> Result<Url> {
356+
fn normalize_file_path(filepath: &str) -> Result<Url, GitUrlParseError> {
336357
let fp = Url::from_file_path(filepath);
337358

338359
match fp {
339360
Ok(path) => Ok(path),
340-
Err(_e) => Ok(normalize_url(&format!("file://{}", filepath))
341-
.with_context(|| "file:// normalization failed".to_string())?),
361+
Err(_e) => {
362+
if let Ok(file_url) = normalize_url(&format!("file://{}", filepath)) {
363+
Ok(file_url)
364+
} else {
365+
return Err(GitUrlParseError::FileUrlNormalizeFailedSchemeAdded);
366+
}
367+
}
342368
}
343369
}
344370

345371
#[cfg(target_arch = "wasm32")]
346-
fn normalize_file_path(_filepath: &str) -> Result<Url> {
372+
fn normalize_file_path(_filepath: &str) -> Result<Url, GitUrlParseError> {
347373
unreachable!()
348374
}
349375

350376
/// `normalize_url` takes in url as `&str` and takes an opinionated approach to identify
351377
/// `ssh://` or `file://` urls that require more information to be added so that
352378
/// they can be parsed more effectively by `url::Url::parse()`
353-
pub fn normalize_url(url: &str) -> Result<Url> {
379+
pub fn normalize_url(url: &str) -> Result<Url, GitUrlParseError> {
380+
#[cfg(feature = "tracing")]
354381
debug!("Processing: {:?}", &url);
355382

356383
// TODO: Should this be extended to check for any whitespace?
357384
// Error if there are null bytes within the url
358385
// https://github.com/tjtelan/git-url-parse-rs/issues/16
359386
if url.contains('\0') {
360-
return Err(eyre!("Found null bytes within input url before parsing"));
387+
return Err(GitUrlParseError::FoundNullBytes);
361388
}
362389

363390
// We're going to remove any trailing slash before running through Url::parse
@@ -366,10 +393,7 @@ pub fn normalize_url(url: &str) -> Result<Url> {
366393
// TODO: Remove support for this form when I go to next major version.
367394
// I forget what it supports, and it isn't obvious after searching for examples
368395
// normalize short git url notation: git:host/path
369-
let url_to_parse = if trim_url.starts_with("git:") && !trim_url.starts_with("git://")
370-
//.with_context(|| "Failed to build short git url regex for testing against url".to_string())?
371-
//.is_match(trim_url)
372-
{
396+
let url_to_parse = if trim_url.starts_with("git:") && !trim_url.starts_with("git://") {
373397
trim_url.replace("git:", "git://")
374398
} else {
375399
trim_url.to_string()
@@ -383,18 +407,20 @@ pub fn normalize_url(url: &str) -> Result<Url> {
383407
Ok(_p) => u,
384408
Err(_e) => {
385409
// Catch case when an ssh url is given w/o a user
410+
#[cfg(feature = "tracing")]
386411
debug!("Scheme parse fail. Assuming a userless ssh url");
387-
normalize_ssh_url(trim_url).with_context(|| {
388-
"No url scheme was found, then failed to normalize as ssh url.".to_string()
389-
})?
412+
if let Ok(ssh_url) = normalize_ssh_url(trim_url) {
413+
ssh_url
414+
} else {
415+
return Err(GitUrlParseError::SshUrlNormalizeFailedNoScheme);
416+
}
390417
}
391418
}
392419
}
393420

421+
// If we're here, we're only looking for Scheme::Ssh or Scheme::File
394422
// TODO: Add test for this
395423
Err(url::ParseError::RelativeUrlWithoutBase) => {
396-
// If we're here, we're only looking for Scheme::Ssh or Scheme::File
397-
398424
// Assuming we have found Scheme::Ssh if we can find an "@" before ":"
399425
// Otherwise we have Scheme::File
400426
//let re = Regex::new(r"^\S+(@)\S+(:).*$").with_context(|| {
@@ -403,19 +429,19 @@ pub fn normalize_url(url: &str) -> Result<Url> {
403429

404430
match is_ssh_url(trim_url) {
405431
true => {
432+
#[cfg(feature = "tracing")]
406433
debug!("Scheme::SSH match for normalization");
407-
normalize_ssh_url(trim_url)
408-
.with_context(|| "Failed to normalize as ssh url".to_string())?
434+
normalize_ssh_url(trim_url)?
409435
}
410436
false => {
437+
#[cfg(feature = "tracing")]
411438
debug!("Scheme::File match for normalization");
412-
normalize_file_path(trim_url)
413-
.with_context(|| "Failed to normalize as file url".to_string())?
439+
normalize_file_path(trim_url)?
414440
}
415441
}
416442
}
417443
Err(err) => {
418-
return Err(eyre!("url parsing failed: {:?}", err));
444+
return Err(GitUrlParseError::from(err));
419445
}
420446
})
421447
}
@@ -446,9 +472,61 @@ fn is_ssh_url(url: &str) -> bool {
446472

447473
// it's an ssh url if we have a domain:path pattern
448474
let parts: Vec<&str> = url.split(':').collect();
475+
476+
// FIXME: I am not sure how to validate a url with a port
477+
//if parts.len() != 3 && !parts[0].is_empty() && !parts[1].is_empty() && !parts[2].is_empty() {
478+
// return false;
479+
//}
480+
481+
// This should also handle if a port is specified
482+
// no port example: ssh://user@domain:path/to/repo.git
483+
// port example: ssh://user@domain:port/path/to/repo.git
449484
if parts.len() != 2 && !parts[0].is_empty() && !parts[1].is_empty() {
450485
return false;
451486
} else {
452487
return true;
453488
}
454489
}
490+
491+
#[derive(Error, Debug)]
492+
pub enum GitUrlParseError {
493+
#[error("Error from Url crate")]
494+
UrlParseError(#[from] url::ParseError),
495+
496+
#[error("Url normalization into url::Url failed")]
497+
UrlNormalizeFailed,
498+
499+
#[error("No url scheme was found, then failed to normalize as ssh url.")]
500+
SshUrlNormalizeFailedNoScheme,
501+
502+
#[error("No url scheme was found, then failed to normalize as ssh url after adding 'ssh://'")]
503+
SshUrlNormalizeFailedSchemeAdded,
504+
505+
#[error("Failed to normalize as ssh url after adding 'ssh://'")]
506+
SshUrlNormalizeFailedSchemeAddedWithPorts,
507+
508+
#[error("No url scheme was found, then failed to normalize as file url.")]
509+
FileUrlNormalizeFailedNoScheme,
510+
511+
#[error(
512+
"No url scheme was found, then failed to normalize as file url after adding 'file://'"
513+
)]
514+
FileUrlNormalizeFailedSchemeAdded,
515+
516+
#[error("Git Url not in expected format")]
517+
UnexpectedFormat,
518+
519+
// FIXME: Keep an eye on this error for removal
520+
#[error("Git Url for host using unexpected scheme")]
521+
UnexpectedScheme,
522+
523+
#[error("Scheme unsupported: {0}")]
524+
UnsupportedScheme(String),
525+
#[error("Host from Url cannot be str or does not exist")]
526+
UnsupportedUrlHostFormat,
527+
#[error("Git Url not in expected format for SSH")]
528+
UnsupportedSshUrlFormat,
529+
530+
#[error("Found null bytes within input url before parsing")]
531+
FoundNullBytes,
532+
}

tests/parse.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ fn relative_windows_path() {
310310
}
311311

312312
// Issue #7 - Absolute Windows paths will not parse at all
313-
#[should_panic(expected = "git url is not of expected format")]
313+
#[should_panic(expected = "URL parse failed: UnexpectedFormat")]
314314
#[test]
315315
fn absolute_windows_path() {
316316
let test_url = "c:\\project-name.git";
@@ -341,7 +341,7 @@ fn ssh_user_path_not_acctname_reponame_format() {
341341
assert!(e.is_err());
342342
assert_eq!(
343343
format!("{}", e.err().unwrap()),
344-
"git url is not of expected format"
344+
"Git Url not in expected format"
345345
);
346346
}
347347

0 commit comments

Comments
 (0)