give better error if required field of url join is invalid (#10589)

# Description
Fix #10506 by adding `ExpectedNonNull` `ShellError` if required field is
entered as `$nothing`, `null`, " ", etc.

This adds a new `ShellError`, `ExpectedNonNull`, taking the expected
type and span.

# User-Facing Changes
Will get a more helpful error in the case described by #10506. Examples:
```nushell
➜ {scheme: "", host: "github.com"} | url join
Error: nu:🐚:expected_non_null

  × Expected string found null.
   ╭─[entry #16:1:1]
 1 │ {scheme: "", host: "github.com"} | url join
   ·          ─┬
   ·           ╰── expected string, found null
   ╰────
```

```nushell
❯ {scheme: "https", host: null} | url join
Error: nu:🐚:expected_non_null

  × Expected string found null.
   ╭─[entry #19:1:1]
 1 │ {scheme: "https", host: null} | url join
   ·                         ──┬─
   ·                           ╰── expected string, found null
   ╰────
```

# Tests + Formatting
All pass.
This commit is contained in:
Marshall Bruner 2023-10-10 19:24:23 +02:00 committed by GitHub
parent dc3c34275d
commit 1402508416
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -223,81 +223,96 @@ impl UrlComponents {
};
}
// a part from port and params all other keys are strings.
match value.as_string() {
Ok(s) => {
if s.trim().is_empty() {
Ok(self)
// apart from port and params all other keys are strings.
let s = value.as_string()?; // If value fails String conversion, just output this ShellError
if !Self::check_empty_string_ok(&key, &s, value_span)? {
return Ok(self);
}
match key.as_str() {
"host" => Ok(Self {
host: Some(s),
..self
}),
"scheme" => Ok(Self {
scheme: Some(s),
..self
}),
"username" => Ok(Self {
username: Some(s),
..self
}),
"password" => Ok(Self {
password: Some(s),
..self
}),
"path" => Ok(Self {
path: Some(if s.starts_with('/') {
s
} else {
match key.as_str() {
"host" => Ok(Self {
host: Some(s),
..self
}),
"scheme" => Ok(Self {
scheme: Some(s),
..self
}),
"username" => Ok(Self {
username: Some(s),
..self
}),
"password" => Ok(Self {
password: Some(s),
..self
}),
"path" => Ok(Self {
path: Some(if s.starts_with('/') {
s
} else {
format!("/{s}")
}),
..self
}),
"query" => {
if let Some(q) = self.query {
if q != s {
// if query is present it means that also params_span is set.
return Err(ShellError::IncompatibleParameters {
left_message: format!("Mismatch, query param is: {s}"),
left_span: value.span(),
right_message: format!("instead qs from params is: {q}"),
right_span: self.params_span.unwrap_or(Span::unknown()),
});
}
}
Ok(Self {
query: Some(format!("?{s}")),
query_span: Some(value.span()),
..self
})
}
"fragment" => Ok(Self {
fragment: Some(if s.starts_with('#') {
s
} else {
format!("#{s}")
}),
..self
}),
_ => {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError(
format!("'{key}' is not a valid URL field"),
format!("Remove '{key}' col from input record"),
Some(span),
None,
vec![],
),
);
Ok(self)
}
format!("/{s}")
}),
..self
}),
"query" => {
if let Some(q) = self.query {
if q != s {
// if query is present it means that also params_span is set.
return Err(ShellError::IncompatibleParameters {
left_message: format!("Mismatch, query param is: {s}"),
left_span: value.span(),
right_message: format!("instead qs from params is: {q}"),
right_span: self.params_span.unwrap_or(Span::unknown()),
});
}
}
Ok(Self {
query: Some(format!("?{s}")),
query_span: Some(value.span()),
..self
})
}
_ => Ok(self),
"fragment" => Ok(Self {
fragment: Some(if s.starts_with('#') {
s
} else {
format!("#{s}")
}),
..self
}),
_ => {
nu_protocol::report_error_new(
engine_state,
&ShellError::GenericError(
format!("'{key}' is not a valid URL field"),
format!("remove '{key}' col from input record"),
Some(span),
None,
vec![],
),
);
Ok(self)
}
}
}
// Check if value is empty. If so, check if that is fine, i.e., not a required input
fn check_empty_string_ok(key: &str, s: &str, value_span: Span) -> Result<bool, ShellError> {
if !s.trim().is_empty() {
return Ok(true);
}
match key {
"host" => Err(ShellError::UnsupportedConfigValue(
"non-empty string".into(),
"empty string".into(),
value_span,
)),
"scheme" => Err(ShellError::UnsupportedConfigValue(
"non-empty string".into(),
"empty string".into(),
value_span,
)),
_ => Ok(false),
}
}