use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Relations {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub any: Option<Vec<Relation>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub all: Option<Vec<Relation>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub none: Option<Vec<Relation>>,
}
impl Relations {
pub fn new() -> Self {
Self::default()
}
pub fn any(mut self, relation: Relation) -> Self {
self.any.get_or_insert_with(Vec::new).push(relation);
self
}
pub fn all(mut self, relation: Relation) -> Self {
self.all.get_or_insert_with(Vec::new).push(relation);
self
}
pub fn none(mut self, relation: Relation) -> Self {
self.none.get_or_insert_with(Vec::new).push(relation);
self
}
pub fn is_empty(&self) -> bool {
self.any.is_none() && self.all.is_none() && self.none.is_none()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Relation {
pub kind: RelationKind,
pub target: RelationTarget,
#[serde(default)]
pub transitive: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_depth: Option<u32>,
#[serde(default)]
pub scope: TraversalScope,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub r#as: Option<String>,
}
impl Relation {
pub fn new(kind: RelationKind, target: RelationTarget) -> Self {
Self {
kind,
target,
transitive: false,
max_depth: None,
scope: TraversalScope::default(),
r#as: None,
}
}
pub fn transitive(mut self) -> Self {
self.transitive = true;
self
}
pub fn with_max_depth(mut self, depth: u32) -> Self {
self.max_depth = Some(depth);
self
}
pub fn with_scope(mut self, scope: TraversalScope) -> Self {
self.scope = scope;
self
}
pub fn bind(mut self, var: impl Into<String>) -> Self {
self.r#as = Some(var.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct RelationTarget {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kind: Option<TargetKind>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub r#match: Option<TargetMatch>,
}
impl RelationTarget {
pub fn new() -> Self {
Self {
kind: None,
r#match: None,
}
}
pub fn kind(mut self, kind: TargetKind) -> Self {
self.kind = Some(kind);
self
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.r#match = Some(TargetMatch {
name: Some(name.into()),
..Default::default()
});
self
}
}
impl Default for RelationTarget {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum TargetKind {
Function,
Struct,
Enum,
Trait,
Impl,
Mod,
Const,
Static,
TypeAlias,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct TargetMatch {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub regex: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attributes: Option<Vec<String>>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum TraversalScope {
#[default]
Workspace,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
pub enum RelationKind {
Calls,
CalledBy,
TypeReferences,
TypeReferencedBy,
Implements,
ImplementedBy,
Contains,
ContainedBy,
}
impl RelationKind {
pub fn reverse(&self) -> Self {
match self {
Self::Calls => Self::CalledBy,
Self::CalledBy => Self::Calls,
Self::TypeReferences => Self::TypeReferencedBy,
Self::TypeReferencedBy => Self::TypeReferences,
Self::Implements => Self::ImplementedBy,
Self::ImplementedBy => Self::Implements,
Self::Contains => Self::ContainedBy,
Self::ContainedBy => Self::Contains,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_relations_builder() {
let rels = Relations::new()
.any(Relation::new(
RelationKind::Calls,
RelationTarget::new()
.kind(TargetKind::Function)
.name("unwrap"),
))
.none(Relation::new(
RelationKind::TypeReferences,
RelationTarget::new()
.kind(TargetKind::Struct)
.name("Database"),
));
assert!(rels.any.is_some());
assert!(rels.none.is_some());
assert!(rels.all.is_none());
}
#[test]
fn test_relation_transitive() {
let rel = Relation::new(RelationKind::CalledBy, RelationTarget::new())
.transitive()
.with_max_depth(10)
.with_scope(TraversalScope::Workspace);
assert!(rel.transitive);
assert_eq!(rel.max_depth, Some(10));
assert_eq!(rel.scope, TraversalScope::Workspace);
}
#[test]
fn test_relation_kind_reverse() {
assert_eq!(RelationKind::Calls.reverse(), RelationKind::CalledBy);
assert_eq!(
RelationKind::TypeReferences.reverse(),
RelationKind::TypeReferencedBy
);
assert_eq!(
RelationKind::Implements.reverse(),
RelationKind::ImplementedBy
);
}
}