use crate::verification::types::ValidationError;
#[cfg(feature = "url")]
use url::Url;
#[cfg(not(kani))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UrlValid(Url);
#[cfg(kani)]
#[derive(Debug, Clone)]
pub struct UrlValid(std::marker::PhantomData<Url>);
#[cfg(not(kani))]
impl UrlValid {
pub fn new(value: &str) -> Result<Self, ValidationError> {
Url::parse(value)
.map(Self)
.map_err(|_| ValidationError::UrlInvalid)
}
pub fn from_url(url: Url) -> Self {
Self(url)
}
pub fn get(&self) -> &Url {
&self.0
}
pub fn into_inner(self) -> Url {
self.0
}
}
#[cfg(kani)]
impl UrlValid {
pub fn new(_value: &str) -> Result<Self, ValidationError> {
let is_valid: bool = kani::any();
if is_valid {
Ok(Self(std::marker::PhantomData))
} else {
Err(ValidationError::UrlInvalid)
}
}
pub fn from_url(_url: Url) -> Self {
Self(std::marker::PhantomData)
}
pub fn get(&self) -> &Url {
panic!("UrlValid::get() not available in Kani mode - use symbolic validation")
}
pub fn into_inner(self) -> Url {
panic!("UrlValid::into_inner() not available in Kani mode - use symbolic validation")
}
}
#[cfg(not(kani))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UrlHttps(Url);
#[cfg(kani)]
#[derive(Debug, Clone)]
pub struct UrlHttps(std::marker::PhantomData<Url>);
#[cfg(not(kani))]
impl UrlHttps {
pub fn new(value: &str) -> Result<Self, ValidationError> {
let url = Url::parse(value).map_err(|_| ValidationError::UrlInvalid)?;
if url.scheme() == "https" {
Ok(Self(url))
} else {
Err(ValidationError::UrlNotHttps)
}
}
pub fn from_url(url: Url) -> Result<Self, ValidationError> {
if url.scheme() == "https" {
Ok(Self(url))
} else {
Err(ValidationError::UrlNotHttps)
}
}
pub fn get(&self) -> &Url {
&self.0
}
pub fn into_inner(self) -> Url {
self.0
}
}
#[cfg(kani)]
impl UrlHttps {
pub fn new(_value: &str) -> Result<Self, ValidationError> {
let is_valid: bool = kani::any();
let is_https: bool = kani::any();
if !is_valid {
Err(ValidationError::UrlInvalid)
} else if is_https {
Ok(Self(std::marker::PhantomData))
} else {
Err(ValidationError::UrlNotHttps)
}
}
pub fn from_url(_url: Url) -> Result<Self, ValidationError> {
let is_https: bool = kani::any();
if is_https {
Ok(Self(std::marker::PhantomData))
} else {
Err(ValidationError::UrlNotHttps)
}
}
pub fn get(&self) -> &Url {
panic!("UrlHttps::get() not available in Kani mode - use symbolic validation")
}
pub fn into_inner(self) -> Url {
panic!("UrlHttps::into_inner() not available in Kani mode - use symbolic validation")
}
}
#[cfg(not(kani))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UrlHttp(Url);
#[cfg(kani)]
#[derive(Debug, Clone)]
pub struct UrlHttp(std::marker::PhantomData<Url>);
#[cfg(not(kani))]
impl UrlHttp {
pub fn new(value: &str) -> Result<Self, ValidationError> {
let url = Url::parse(value).map_err(|_| ValidationError::UrlInvalid)?;
if url.scheme() == "http" {
Ok(Self(url))
} else {
Err(ValidationError::UrlNotHttp)
}
}
pub fn from_url(url: Url) -> Result<Self, ValidationError> {
if url.scheme() == "http" {
Ok(Self(url))
} else {
Err(ValidationError::UrlNotHttp)
}
}
pub fn get(&self) -> &Url {
&self.0
}
pub fn into_inner(self) -> Url {
self.0
}
}
#[cfg(kani)]
impl UrlHttp {
pub fn new(_value: &str) -> Result<Self, ValidationError> {
let is_valid: bool = kani::any();
let is_http: bool = kani::any();
if !is_valid {
Err(ValidationError::UrlInvalid)
} else if is_http {
Ok(Self(std::marker::PhantomData))
} else {
Err(ValidationError::UrlNotHttp)
}
}
pub fn from_url(_url: Url) -> Result<Self, ValidationError> {
let is_http: bool = kani::any();
if is_http {
Ok(Self(std::marker::PhantomData))
} else {
Err(ValidationError::UrlNotHttp)
}
}
pub fn get(&self) -> &Url {
panic!("UrlHttp::get() not available in Kani mode - use symbolic validation")
}
pub fn into_inner(self) -> Url {
panic!("UrlHttp::into_inner() not available in Kani mode - use symbolic validation")
}
}
#[cfg(not(kani))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UrlWithHost(Url);
#[cfg(kani)]
#[derive(Debug, Clone)]
pub struct UrlWithHost(std::marker::PhantomData<Url>);
#[cfg(not(kani))]
impl UrlWithHost {
pub fn new(value: &str) -> Result<Self, ValidationError> {
let url = Url::parse(value).map_err(|_| ValidationError::UrlInvalid)?;
if url.host().is_some() {
Ok(Self(url))
} else {
Err(ValidationError::UrlNoHost)
}
}
pub fn from_url(url: Url) -> Result<Self, ValidationError> {
if url.host().is_some() {
Ok(Self(url))
} else {
Err(ValidationError::UrlNoHost)
}
}
pub fn get(&self) -> &Url {
&self.0
}
pub fn into_inner(self) -> Url {
self.0
}
}
#[cfg(kani)]
impl UrlWithHost {
pub fn new(_value: &str) -> Result<Self, ValidationError> {
let is_valid: bool = kani::any();
let has_host: bool = kani::any();
if !is_valid {
Err(ValidationError::UrlInvalid)
} else if has_host {
Ok(Self(std::marker::PhantomData))
} else {
Err(ValidationError::UrlNoHost)
}
}
pub fn from_url(_url: Url) -> Result<Self, ValidationError> {
let has_host: bool = kani::any();
if has_host {
Ok(Self(std::marker::PhantomData))
} else {
Err(ValidationError::UrlNoHost)
}
}
pub fn get(&self) -> &Url {
panic!("UrlWithHost::get() not available in Kani mode - use symbolic validation")
}
pub fn into_inner(self) -> Url {
panic!("UrlWithHost::into_inner() not available in Kani mode - use symbolic validation")
}
}
#[cfg(not(kani))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UrlCanBeBase(Url);
#[cfg(kani)]
#[derive(Debug, Clone)]
pub struct UrlCanBeBase(std::marker::PhantomData<Url>);
#[cfg(not(kani))]
impl UrlCanBeBase {
pub fn new(value: &str) -> Result<Self, ValidationError> {
let url = Url::parse(value).map_err(|_| ValidationError::UrlInvalid)?;
if url.cannot_be_a_base() {
Err(ValidationError::UrlCannotBeBase)
} else {
Ok(Self(url))
}
}
pub fn from_url(url: Url) -> Result<Self, ValidationError> {
if url.cannot_be_a_base() {
Err(ValidationError::UrlCannotBeBase)
} else {
Ok(Self(url))
}
}
pub fn get(&self) -> &Url {
&self.0
}
pub fn into_inner(self) -> Url {
self.0
}
}
#[cfg(kani)]
impl UrlCanBeBase {
pub fn new(_value: &str) -> Result<Self, ValidationError> {
let is_valid: bool = kani::any();
let can_be_base: bool = kani::any();
if !is_valid {
Err(ValidationError::UrlInvalid)
} else if can_be_base {
Ok(Self(std::marker::PhantomData))
} else {
Err(ValidationError::UrlCannotBeBase)
}
}
pub fn from_url(_url: Url) -> Result<Self, ValidationError> {
let can_be_base: bool = kani::any();
if can_be_base {
Ok(Self(std::marker::PhantomData))
} else {
Err(ValidationError::UrlCannotBeBase)
}
}
pub fn get(&self) -> &Url {
panic!("UrlCanBeBase::get() not available in Kani mode - use symbolic validation")
}
pub fn into_inner(self) -> Url {
panic!("UrlCanBeBase::into_inner() not available in Kani mode - use symbolic validation")
}
}
use crate::{ElicitCommunicator, ElicitResult, Elicitation, Prompt};
pub use crate::primitives::url::UrlStyle;
impl Prompt for UrlValid {
fn prompt() -> Option<&'static str> {
Some("Enter a valid URL:")
}
}
impl Elicitation for UrlValid {
type Style = UrlStyle;
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
let prompt = Self::prompt().unwrap();
tracing::debug!("Eliciting UrlValid with server-side send_prompt");
let response = communicator.send_prompt(prompt).await?;
let url = url::Url::parse(response.trim()).map_err(|e| {
crate::ElicitError::new(crate::ElicitErrorKind::ParseError(format!(
"Invalid URL: {}",
e
)))
})?;
Ok(Self::from_url(url))
}
}
impl Prompt for UrlHttps {
fn prompt() -> Option<&'static str> {
Some("Enter an HTTPS URL:")
}
}
impl Elicitation for UrlHttps {
type Style = UrlStyle;
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
let value = url::Url::elicit(communicator).await?;
Self::from_url(value).map_err(crate::ElicitError::from)
}
}
impl Prompt for UrlHttp {
fn prompt() -> Option<&'static str> {
Some("Enter an HTTP or HTTPS URL:")
}
}
impl Elicitation for UrlHttp {
type Style = UrlStyle;
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
let value = url::Url::elicit(communicator).await?;
Self::from_url(value).map_err(crate::ElicitError::from)
}
}
impl Prompt for UrlWithHost {
fn prompt() -> Option<&'static str> {
Some("Enter a URL with a host:")
}
}
impl Elicitation for UrlWithHost {
type Style = UrlStyle;
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
let value = url::Url::elicit(communicator).await?;
Self::from_url(value).map_err(crate::ElicitError::from)
}
}
impl Prompt for UrlCanBeBase {
fn prompt() -> Option<&'static str> {
Some("Enter a base URL:")
}
}
impl Elicitation for UrlCanBeBase {
type Style = UrlStyle;
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
let value = url::Url::elicit(communicator).await?;
Self::from_url(value).map_err(crate::ElicitError::from)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_url_valid() {
assert!(UrlValid::new("https://example.com").is_ok());
assert!(UrlValid::new("http://localhost:8080/path").is_ok());
assert!(UrlValid::new("ftp://files.example.com").is_ok());
assert!(UrlValid::new("not a url").is_err());
assert!(UrlValid::new("").is_err());
}
#[test]
fn test_url_https() {
assert!(UrlHttps::new("https://example.com").is_ok());
assert!(UrlHttps::new("https://secure.example.com/path?query=1").is_ok());
assert!(UrlHttps::new("http://example.com").is_err());
assert!(UrlHttps::new("ftp://example.com").is_err());
assert!(UrlHttps::new("not a url").is_err());
}
#[test]
fn test_url_http() {
assert!(UrlHttp::new("http://example.com").is_ok());
assert!(UrlHttp::new("http://localhost:8080/api").is_ok());
assert!(UrlHttp::new("https://example.com").is_err());
assert!(UrlHttp::new("ftp://example.com").is_err());
assert!(UrlHttp::new("not a url").is_err());
let url = Url::parse("http://example.com").unwrap();
let http_url = UrlHttp::from_url(url).unwrap();
assert_eq!(http_url.get().scheme(), "http");
let inner = http_url.into_inner();
assert_eq!(inner.as_str(), "http://example.com/");
}
#[test]
fn test_url_with_host() {
assert!(UrlWithHost::new("https://example.com").is_ok());
assert!(UrlWithHost::new("http://192.168.1.1:8080").is_ok());
assert!(UrlWithHost::new("mailto:user@example.com").is_err()); assert!(UrlWithHost::new("data:text/plain,hello").is_err()); }
#[test]
fn test_url_can_be_base() {
assert!(UrlCanBeBase::new("https://example.com").is_ok());
assert!(UrlCanBeBase::new("http://example.com/path/").is_ok());
assert!(UrlCanBeBase::new("mailto:user@example.com").is_err()); assert!(UrlCanBeBase::new("data:text/plain,hello").is_err()); }
#[test]
fn test_url_accessors() {
let url = UrlValid::new("https://example.com/path").unwrap();
assert_eq!(url.get().scheme(), "https");
assert_eq!(url.get().host_str(), Some("example.com"));
let inner = url.into_inner();
assert_eq!(inner.as_str(), "https://example.com/path");
}
}