1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
/*
 * DMNTK - Decision Model and Notation Toolkit
 *
 * Common definitions
 *
 * Copyright 2018-2021 Dariusz Depta Engos Software <dariusz.depta@engos.software>
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

use self::errors::*;
use crate::DmntkError;
use std::convert::TryFrom;
use uriparse::{RelativeReference, URI};

/// Optional reference to an element.
pub type OptHRef = Option<HRef>;

/// Reference to an element using `href` attribute.
#[derive(Debug, Clone)]
pub struct HRef(String);

impl<'href> From<&'href HRef> for &'href str {
  fn from(value: &'href HRef) -> Self {
    &value.0
  }
}

impl TryFrom<&str> for HRef {
  type Error = DmntkError;
  /// Tries to convert string into [HRef].
  fn try_from(value: &str) -> Result<Self, Self::Error> {
    if let Ok(relative_reference) = RelativeReference::try_from(value) {
      let s = relative_reference.to_string();
      return Ok(Self(if s.starts_with('#') { s.strip_prefix('#').unwrap().to_string() } else { s }));
    }
    if let Ok(uri) = URI::try_from(value) {
      return Ok(Self(uri.to_string()));
    }
    Err(invalid_reference(value))
  }
}

/// Definitions of errors reported by module `href`.
mod errors {
  use crate::DmntkError;

  /// HRef errors.
  #[derive(Debug, PartialEq)]
  pub enum HRefError {
    /// Error reported when the specified text is not a valid `FEEL` type name.
    InvalidReference(String),
  }

  impl From<HRefError> for DmntkError {
    /// Converts [HRefError] into [DmntkError].
    fn from(e: HRefError) -> Self {
      DmntkError::new("HRefError", &e.to_string())
    }
  }

  impl std::fmt::Display for HRefError {
    /// Implements [Display] trait for [HRefErrors](HRefError).
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
      match self {
        HRefError::InvalidReference(s) => write!(f, "invalid reference): `{}`", s),
      }
    }
  }

  /// Creates an [InvalidReference](HRefError::InvalidReference) error.
  pub fn invalid_reference(s: &str) -> DmntkError {
    HRefError::InvalidReference(s.to_owned()).into()
  }
}

#[cfg(test)]
mod tests {
  use crate::HRef;
  use std::convert::TryFrom;

  fn assert_href(expected: &str, uri: &str) {
    let href = &HRef::try_from(uri).unwrap();
    let actual: &str = href.into();
    assert_eq!(expected, actual);
  }

  #[test]
  fn valid_references() {
    assert_href("", "");
    assert_href("ref", "#ref");
    assert_href(":alfa", ":alfa");
    assert_href("//beta/gamma", "//beta/gamma");
    assert_href("ee412cf7-4dc9-4555-ab90-61907cb5b10e", "#ee412cf7-4dc9-4555-ab90-61907cb5b10e");
    assert_href("_82032dc2-36a7-4477-9392-9921353c4b44", "#_82032dc2-36a7-4477-9392-9921353c4b44");
    assert_href("https://dmntk.io/examples/example1#model2", "https://dmntk.io/examples/example1#model2");
  }

  #[test]
  fn invalid_references() {
    assert!(HRef::try_from("##").is_err());
  }
}