use regex::Regex;
use sdml_core::model::{
definitions::Definition,
identifiers::{Identifier, QualifiedIdentifier},
modules::Import,
HasName,
};
use sdml_core::stdlib;
use sdml_errors::Error;
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
fmt::Display,
fs::OpenOptions,
io::{BufReader, Read, Write},
path::Path,
str::FromStr,
};
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct DiagramContentFilter {
#[serde(skip_serializing_if = "Option::is_none", default)]
module_import_filter: Option<NameFilter>,
#[serde(skip_serializing_if = "Option::is_none", default)]
member_import_filter: Option<QualifiedNameFilter>,
#[serde(skip_serializing_if = "Option::is_none", default)]
annotation_filter: Option<QualifiedNameFilter>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
definition_filter: Vec<DefinitionFilter>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
association_filter: Vec<DefinitionFilter>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum NameFilter {
Named(Vec<IdentifierString>),
Matches(#[serde(with = "serde_regex")] Regex),
All,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(try_from = "String", into = "String")]
pub struct IdentifierString(Identifier);
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct QualifiedNameFilter {
#[serde(flatten)]
name_map: HashMap<IdentifierString, NameFilter>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum DefinitionKind {
Datatype,
Entity,
Enum,
Event,
Property,
Rdf,
Structure,
TypeClass,
Union,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum DefinitionFilter {
Kind {
#[serde(flatten)]
kind: DefinitionKind,
},
Named {
#[serde(flatten)]
names: NameFilter,
},
Both {
kind: DefinitionKind,
names: NameFilter,
},
}
impl DiagramContentFilter {
pub fn with_module_import_filter(self, filter: NameFilter) -> Self {
Self {
module_import_filter: Some(filter),
..self
}
}
pub fn filter_stdlib_imports(self) -> Self {
let mut self_mut = self;
let stdlib_names: Vec<IdentifierString> = [
stdlib::dc::MODULE_NAME,
stdlib::dc::terms::MODULE_NAME,
stdlib::iso_3166::MODULE_NAME,
stdlib::iso_4217::MODULE_NAME,
stdlib::owl::MODULE_NAME,
stdlib::rdf::MODULE_NAME,
stdlib::rdfs::MODULE_NAME,
stdlib::sdml::MODULE_NAME,
stdlib::skos::MODULE_NAME,
stdlib::xsd::MODULE_NAME,
]
.into_iter()
.map(Identifier::new_unchecked)
.map(IdentifierString::from)
.collect();
if let Some(NameFilter::Named(filter)) = &mut self_mut.module_import_filter {
filter.extend(stdlib_names);
} else {
self_mut.module_import_filter = Some(NameFilter::from_iter(stdlib_names))
}
self_mut
}
pub fn with_member_import_filter<F>(self, filter: F) -> Self
where
F: Into<QualifiedNameFilter>,
{
Self {
member_import_filter: Some(filter.into()),
..self
}
}
pub fn with_annotation_filter<F>(self, filter: F) -> Self
where
F: Into<QualifiedNameFilter>,
{
Self {
annotation_filter: Some(filter.into()),
..self
}
}
pub fn with_definition_filter<F>(self, filter: F) -> Self
where
F: Into<DefinitionFilter>,
{
Self {
definition_filter: vec![filter.into()],
..self
}
}
pub fn with_definition_filters(self, filter: Vec<DefinitionFilter>) -> Self {
Self {
definition_filter: filter,
..self
}
}
pub fn with_association_filter<F>(self, filter: F) -> Self
where
F: Into<DefinitionFilter>,
{
Self {
association_filter: vec![filter.into()],
..self
}
}
pub fn with_association_filters(self, filter: Vec<DefinitionFilter>) -> Self {
Self {
association_filter: filter,
..self
}
}
pub fn is_empty(&self) -> bool {
self.module_import_filter.is_none()
&& self.member_import_filter.is_none()
&& self.definition_filter.is_empty()
&& self.association_filter.is_empty()
}
pub fn draw_import(&self, id: &Import) -> bool {
match id {
Import::Module(v) => self.draw_module_import(v.name()),
Import::Member(v) => self.draw_member_import(v),
}
}
pub fn draw_module_import(&self, id: &Identifier) -> bool {
!self
.module_import_filter
.as_ref()
.map(|filter| filter.is_excluded(id))
.unwrap_or_default()
}
pub fn draw_member_import(&self, id: &QualifiedIdentifier) -> bool {
!self
.member_import_filter
.as_ref()
.map(|filter| filter.is_excluded(id))
.unwrap_or_default()
}
pub fn draw_member_import_pair(&self, nsid: &Identifier, id: &Identifier) -> bool {
!self
.member_import_filter
.as_ref()
.map(|filter| filter.is_excluded_pair(nsid, id))
.unwrap_or_default()
}
pub fn draw_definition(&self, defn: &Definition) -> bool {
!self
.definition_filter
.iter()
.any(|filter| filter.is_excluded(defn))
}
pub fn draw_definition_named(
&self,
subject_kind: DefinitionKind,
subject_id: &Identifier,
) -> bool {
!self
.definition_filter
.iter()
.any(|filter| filter.is_excluded_pair(subject_kind, subject_id))
}
pub fn draw_member_as_association(&self, defn: &Definition) -> bool {
self.association_filter
.iter()
.any(|names| names.is_excluded(defn))
}
pub fn write_to_file<P>(&self, file: P) -> Result<(), Error>
where
P: AsRef<Path>,
{
self.write_to_writer(
OpenOptions::new()
.create_new(true)
.write(true)
.truncate(true)
.open(file)?,
)
}
pub fn write_to_writer<W>(&self, writer: W) -> Result<(), Error>
where
W: Write,
{
serde_json::to_writer_pretty(writer, self).map_err(into_generator_error)
}
pub fn read_from_file<P>(file: P) -> Result<Self, Error>
where
P: AsRef<Path>,
{
let file = OpenOptions::new().read(true).open(file)?;
let reader = BufReader::new(file);
Self::read_from_reader(reader)
}
pub fn read_from_reader<R>(reader: R) -> Result<Self, Error>
where
R: Read,
{
let filter: Self = serde_json::from_reader(reader).map_err(into_generator_error)?;
Ok(filter)
}
}
impl From<&Definition> for DefinitionKind {
fn from(value: &Definition) -> Self {
match value {
Definition::Datatype(_) => Self::Datatype,
Definition::Entity(_) => Self::Entity,
Definition::Enum(_) => Self::Enum,
Definition::Event(_) => Self::Event,
Definition::Property(_) => Self::Property,
Definition::Rdf(_) => Self::Rdf,
Definition::Structure(_) => Self::Structure,
Definition::TypeClass(_) => Self::TypeClass,
Definition::Union(_) => Self::Union,
}
}
}
impl DefinitionKind {
pub fn is_excluded(&self, defn: &Definition) -> bool {
*self == defn.into()
}
}
impl From<DefinitionKind> for DefinitionFilter {
fn from(kind: DefinitionKind) -> Self {
Self::Kind { kind }
}
}
impl From<NameFilter> for DefinitionFilter {
fn from(value: NameFilter) -> Self {
Self::Named { names: value }
}
}
impl From<(DefinitionKind, NameFilter)> for DefinitionFilter {
fn from(value: (DefinitionKind, NameFilter)) -> Self {
Self::Both {
kind: value.0,
names: value.1,
}
}
}
impl DefinitionFilter {
pub fn exclude_named<F>(names: F) -> Self
where
F: Into<NameFilter>,
{
Self::Named {
names: names.into(),
}
}
pub fn exclude_datatypes() -> Self {
Self::Kind {
kind: DefinitionKind::Datatype,
}
}
pub fn exclude_entities() -> Self {
Self::Kind {
kind: DefinitionKind::Entity,
}
}
pub fn exclude_enums() -> Self {
Self::Kind {
kind: DefinitionKind::Enum,
}
}
pub fn exclude_events() -> Self {
Self::Kind {
kind: DefinitionKind::Event,
}
}
pub fn exclude_properties() -> Self {
Self::Kind {
kind: DefinitionKind::Property,
}
}
pub fn exclude_rdfs() -> Self {
Self::Kind {
kind: DefinitionKind::Rdf,
}
}
pub fn exclude_structures() -> Self {
Self::Kind {
kind: DefinitionKind::Structure,
}
}
pub fn exclude_typeclasses() -> Self {
Self::Kind {
kind: DefinitionKind::TypeClass,
}
}
pub fn exclude_unions() -> Self {
Self::Kind {
kind: DefinitionKind::Union,
}
}
pub fn exclude_datatypes_named_named<F>(names: F) -> Self
where
F: Into<NameFilter>,
{
Self::Both {
kind: DefinitionKind::Datatype,
names: names.into(),
}
}
pub fn exclude_entities_named<F>(names: F) -> Self
where
F: Into<NameFilter>,
{
Self::Both {
kind: DefinitionKind::Entity,
names: names.into(),
}
}
pub fn exclude_enums_named<F>(names: F) -> Self
where
F: Into<NameFilter>,
{
Self::Both {
kind: DefinitionKind::Enum,
names: names.into(),
}
}
pub fn exclude_events_named<F>(names: F) -> Self
where
F: Into<NameFilter>,
{
Self::Both {
kind: DefinitionKind::Event,
names: names.into(),
}
}
pub fn exclude_properties_named<F>(names: F) -> Self
where
F: Into<NameFilter>,
{
Self::Both {
kind: DefinitionKind::Property,
names: names.into(),
}
}
pub fn exclude_rdfs_named<F>(names: F) -> Self
where
F: Into<NameFilter>,
{
Self::Both {
kind: DefinitionKind::Rdf,
names: names.into(),
}
}
pub fn exclude_structures_named<F>(names: F) -> Self
where
F: Into<NameFilter>,
{
Self::Both {
kind: DefinitionKind::Structure,
names: names.into(),
}
}
pub fn exclude_typeclasses_named<F>(names: F) -> Self
where
F: Into<NameFilter>,
{
Self::Both {
kind: DefinitionKind::TypeClass,
names: names.into(),
}
}
pub fn exclude_unions_named<F>(names: F) -> Self
where
F: Into<NameFilter>,
{
Self::Both {
kind: DefinitionKind::Union,
names: names.into(),
}
}
pub fn is_excluded(&self, defn: &Definition) -> bool {
self.is_excluded_pair(defn.into(), defn.name())
}
pub fn is_excluded_pair(&self, subject_kind: DefinitionKind, subject_id: &Identifier) -> bool {
match self {
Self::Kind { kind } => *kind == subject_kind,
Self::Named { names } => names.is_excluded(subject_id),
Self::Both { kind, names } => *kind == subject_kind || names.is_excluded(subject_id),
}
}
}
impl From<HashMap<IdentifierString, NameFilter>> for QualifiedNameFilter {
fn from(value: HashMap<IdentifierString, NameFilter>) -> Self {
Self { name_map: value }
}
}
impl From<(IdentifierString, NameFilter)> for QualifiedNameFilter {
fn from(value: (IdentifierString, NameFilter)) -> Self {
Self {
name_map: HashMap::from_iter(vec![value]),
}
}
}
impl From<Vec<(IdentifierString, NameFilter)>> for QualifiedNameFilter {
fn from(value: Vec<(IdentifierString, NameFilter)>) -> Self {
Self {
name_map: HashMap::from_iter(value),
}
}
}
impl QualifiedNameFilter {
pub fn is_excluded(&self, qid: &QualifiedIdentifier) -> bool {
self.is_excluded_pair(qid.module(), qid.member())
}
pub fn is_excluded_pair(&self, nsid: &Identifier, id: &Identifier) -> bool {
let nsid = IdentifierString::from(nsid.clone());
self.name_map
.get(&nsid)
.map(|names| names.is_excluded(id))
.unwrap_or_default()
}
}
impl From<Identifier> for NameFilter {
fn from(value: Identifier) -> Self {
Self::Named(vec![value.into()])
}
}
impl From<IdentifierString> for NameFilter {
fn from(value: IdentifierString) -> Self {
Self::Named(vec![value])
}
}
impl<I> FromIterator<I> for NameFilter
where
I: Into<IdentifierString>,
{
fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
Self::Named(Vec::from_iter(iter.into_iter().map(|i| i.into())))
}
}
impl From<Regex> for NameFilter {
fn from(value: Regex) -> Self {
Self::Matches(value)
}
}
impl NameFilter {
pub fn is_excluded(&self, id: &Identifier) -> bool {
let id = IdentifierString::from(id.clone());
match self {
Self::Named(names) => names.contains(&id),
Self::Matches(regex) => regex.is_match(id.as_ref()),
Self::All => true,
}
}
}
impl Display for IdentifierString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<Identifier> for IdentifierString {
fn from(value: Identifier) -> Self {
Self(value)
}
}
impl From<IdentifierString> for Identifier {
fn from(value: IdentifierString) -> Self {
value.0
}
}
impl From<IdentifierString> for String {
fn from(value: IdentifierString) -> Self {
value.0.into()
}
}
impl FromStr for IdentifierString {
type Err = sdml_errors::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(Identifier::from_str(s)?))
}
}
impl TryFrom<String> for IdentifierString {
type Error = sdml_errors::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Ok(Self(Identifier::from_str(&value)?))
}
}
impl AsRef<str> for IdentifierString {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
fn into_generator_error(e: serde_json::Error) -> Error {
crate::errors::into_generator_error("draw::filter", e)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::{from_str, to_string_pretty, Error};
#[test]
fn test_create_empty_filter_json() {
println!(
"{}",
to_string_pretty(&DiagramContentFilter::default()).unwrap()
);
}
#[test]
fn test_parse_empty_filter_json() {
let result: Result<DiagramContentFilter, Error> = from_str("{}");
println!("{:?}", result);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_create_no_stdlib_filter_json() {
println!(
"{}",
to_string_pretty(&DiagramContentFilter::default().filter_stdlib_imports()).unwrap()
);
}
#[test]
fn test_parse_no_stdlib_filter_json() {
let result: Result<DiagramContentFilter, Error> = from_str(
r#"{
"module_import_filter": {
"named": [
"dc",
"dc_terms",
"iso_3166",
"iso_4217",
"owl",
"rdf",
"rdfs",
"sdml",
"skos",
"xsd"
]
}
}"#,
);
println!("{:?}", result);
assert!(result.is_ok());
}
#[test]
fn test_create_member_import_filter_json() {
println!(
"{}",
to_string_pretty(&DiagramContentFilter::default().with_member_import_filter(
QualifiedNameFilter::from(vec![
(IdentifierString::from_str("sdml").unwrap(), NameFilter::All,),
(
IdentifierString::from_str("xsd").unwrap(),
Regex::new("^[A-Z]+$").unwrap().into(),
)
])
))
.unwrap()
);
}
#[test]
fn test_parse_member_import_filter_json() {
let result: Result<DiagramContentFilter, Error> = from_str(
r#"{
"member_import_filter": {
"sdml": "all",
"xsd": {
"matches": "^[A-Z]+$"
}
}
}"#,
);
println!("{:?}", result);
assert!(result.is_ok());
}
#[test]
fn test_parse_member_import_filter_all_json() {
let result: Result<DiagramContentFilter, Error> = from_str(
r#"{
"member_import_filter": {
"sdml": "all",
"skos": {
"named": [
"changeNote",
"editorialNote",
"historyNote",
"scopeNote"
]
},
"xsd": {
"matches": "^[A-Z]+$"
}
}
}"#,
);
println!("{:?}", result);
assert!(result.is_ok());
}
#[test]
fn test_create_enum_regex_destination_filter_json() {
println!(
"{}",
to_string_pretty(&DiagramContentFilter::default().with_definition_filter(
DefinitionFilter::Both {
kind: DefinitionKind::Enum,
names: Regex::new("_AC$").unwrap().into(),
}
))
.unwrap()
);
}
#[test]
fn test_parse_enum_regex_destination_filter_json() {
let result: Result<DiagramContentFilter, Error> = from_str(
r#"{
"definition_filter": [
{
"both": {
"kind": "enum",
"names": {
"matches": "_AC$"
}
}
}
]
}"#,
);
println!("{:?}", result);
assert!(result.is_ok());
}
}