summaryrefslogtreecommitdiff
path: root/src/dot.rs
diff options
context:
space:
mode:
authorseancarroll <seanc28@gmail.com>2021-01-20 21:51:57 -0600
committerseancarroll <seanc28@gmail.com>2021-01-20 21:51:57 -0600
commit7d5d439ed2f465120825c918f920bd9fbfd3169f (patch)
tree4545ef6d834d8bd5f6b0d3c860511d3ddc042984 /src/dot.rs
parentec3468a275314f16769439736b7514b40e3b49c0 (diff)
downloaddotavious-7d5d439ed2f465120825c918f920bd9fbfd3169f.zip
Attribute Constraints
This adds some attribute constraints. We validate certain constraints based on information from http://www.graphviz.org/doc/info/attrs.html. Validation errors are collected as part of builders, we still add the attribute regardless or not if it fails validation, and build methods return ValidationResult<T> where errors is a Vec<ValidationError>. We still add attributes even if the validation fails so that users can ignore validation errors and build appropriate struct.
Diffstat (limited to 'src/dot.rs')
-rw-r--r--src/dot.rs108
1 files changed, 100 insertions, 8 deletions
diff --git a/src/dot.rs b/src/dot.rs
index 5c1dc29..a6828b7 100644
--- a/src/dot.rs
+++ b/src/dot.rs
@@ -6,6 +6,7 @@ use crate::attributes::{
};
use indexmap::IndexMap;
use std::borrow::Cow;
+use std::borrow::Cow::Borrowed;
use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter};
use std::io;
@@ -13,6 +14,14 @@ use std::io::prelude::*;
static INDENT: &str = " ";
+pub type ValidationResult<T> = std::result::Result<T, Vec<ValidationError>>;
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct ValidationError {
+ pub message: Cow<'static, str>,
+ pub field: Cow<'static, str>,
+}
+
pub trait DotString<'a> {
fn dot_string(&self) -> Cow<'a, str>;
}
@@ -301,6 +310,8 @@ pub struct GraphBuilder<'a> {
edges: Vec<Edge<'a>>,
comment: Option<String>,
+
+ errors: Vec<ValidationError>,
}
// TODO: id should be an escString
@@ -317,6 +328,7 @@ impl<'a> GraphBuilder<'a> {
nodes: Vec::new(),
edges: Vec::new(),
comment: None,
+ errors: Vec::new(),
}
}
@@ -332,6 +344,7 @@ impl<'a> GraphBuilder<'a> {
nodes: Vec::new(),
edges: Vec::new(),
comment: None,
+ errors: Vec::new(),
}
}
@@ -411,7 +424,14 @@ impl<'a> GraphBuilder<'a> {
self
}
- pub fn build(&self) -> Graph<'a> {
+ pub fn build(&self) -> ValidationResult<Graph<'a>> {
+ if !self.errors.is_empty() {
+ return Err(self.errors.clone());
+ }
+ Ok(self.build_ignore_validation())
+ }
+
+ pub fn build_ignore_validation(&self) -> Graph<'a> {
Graph {
id: self.id.to_owned(),
is_directed: self.is_directed,
@@ -479,6 +499,8 @@ pub struct SubGraphBuilder<'a> {
nodes: Vec<Node<'a>>,
edges: Vec<Edge<'a>>,
+
+ errors: Vec<ValidationError>,
}
// TODO: id should be an escString
@@ -492,6 +514,7 @@ impl<'a> SubGraphBuilder<'a> {
sub_graphs: Vec::new(),
nodes: Vec::new(),
edges: Vec::new(),
+ errors: Vec::new(),
}
}
@@ -573,8 +596,12 @@ impl<'a> SubGraphBuilder<'a> {
self
}
- pub fn build(&self) -> SubGraph<'a> {
- SubGraph {
+ pub fn build(&self) -> ValidationResult<SubGraph<'a>> {
+ if !self.errors.is_empty() {
+ return Err(self.errors.clone());
+ }
+
+ Ok(SubGraph {
id: self.id.to_owned(),
graph_attributes: self.graph_attributes.clone(),
node_attributes: self.node_attributes.clone(),
@@ -582,7 +609,7 @@ impl<'a> SubGraphBuilder<'a> {
sub_graphs: self.sub_graphs.clone(),
nodes: self.nodes.clone(), // TODO: is clone the only option here?
edges: self.edges.clone(), // TODO: is clone the only option here?
- }
+ })
}
}
@@ -614,6 +641,7 @@ impl<'a> DotString<'a> for Node<'a> {
pub struct NodeBuilder<'a> {
id: String,
attributes: IndexMap<String, AttributeText<'a>>,
+ errors: Vec<ValidationError>,
}
impl<'a> NodeAttributes<'a> for NodeBuilder<'a> {
@@ -638,6 +666,13 @@ impl<'a> NodeAttributes<'a> for NodeBuilder<'a> {
fn get_attributes_mut(&mut self) -> &mut IndexMap<String, AttributeText<'a>> {
&mut self.attributes
}
+
+ fn add_validation_error(&mut self, field: &'static str, message: &'static str) {
+ self.errors.push(ValidationError {
+ field: Borrowed(field),
+ message: Borrowed(message),
+ })
+ }
}
impl<'a> NodeBuilder<'a> {
@@ -645,10 +680,18 @@ impl<'a> NodeBuilder<'a> {
Self {
id,
attributes: IndexMap::new(),
+ errors: Vec::new(),
+ }
+ }
+
+ pub fn build(&self) -> ValidationResult<Node<'a>> {
+ if !self.errors.is_empty() {
+ return Err(self.errors.clone());
}
+ Ok(self.build_ignore_validation())
}
- pub fn build(&self) -> Node<'a> {
+ pub fn build_ignore_validation(&self) -> Node<'a> {
Node {
// TODO: are these to_owned and clones necessary?
id: self.id.to_owned(),
@@ -699,6 +742,7 @@ pub struct EdgeBuilder<'a> {
pub target: String,
pub target_port_position: Option<PortPosition>,
attributes: IndexMap<String, AttributeText<'a>>,
+ errors: Vec<ValidationError>,
}
impl<'a> EdgeAttributes<'a> for EdgeBuilder<'a> {
@@ -714,6 +758,13 @@ impl<'a> EdgeAttributes<'a> for EdgeBuilder<'a> {
fn get_attributes_mut(&mut self) -> &mut IndexMap<String, AttributeText<'a>> {
&mut self.attributes
}
+
+ fn add_validation_error(&mut self, field: &'static str, message: &'static str) {
+ self.errors.push(ValidationError {
+ field: Borrowed(field),
+ message: Borrowed(message),
+ })
+ }
}
impl<'a> EdgeBuilder<'a> {
@@ -724,6 +775,7 @@ impl<'a> EdgeBuilder<'a> {
source_port_position: None,
target_port_position: None,
attributes: IndexMap::new(),
+ errors: Vec::new(),
}
}
@@ -739,6 +791,7 @@ impl<'a> EdgeBuilder<'a> {
source_port_position: Some(source_port_position),
target_port_position: Some(target_port_position),
attributes: IndexMap::new(),
+ errors: Vec::new(),
}
}
@@ -770,7 +823,14 @@ impl<'a> EdgeBuilder<'a> {
self
}
- pub fn build(&self) -> Edge<'a> {
+ pub fn build(&self) -> ValidationResult<Edge<'a>> {
+ if !self.errors.is_empty() {
+ return Err(self.errors.clone());
+ }
+ Ok(self.build_ignore_validation())
+ }
+
+ pub fn build_ignore_validation(&self) -> Edge<'a> {
Edge {
// TODO: are these to_owned and clones necessary?
source: self.source.to_owned(),
@@ -804,21 +864,37 @@ impl<'a> NodeAttributes<'a> for NodeAttributeStatementBuilder<'a> {
fn get_attributes_mut(&mut self) -> &mut IndexMap<String, AttributeText<'a>> {
&mut self.attributes
}
+
+ fn add_validation_error(&mut self, field: &'static str, message: &'static str) {
+ self.errors.push(ValidationError {
+ field: Borrowed(field),
+ message: Borrowed(message),
+ })
+ }
}
// I'm not a huge fan of needing this builder but having a hard time getting around &mut without it
pub struct NodeAttributeStatementBuilder<'a> {
pub attributes: IndexMap<String, AttributeText<'a>>,
+ errors: Vec<ValidationError>,
}
impl<'a> NodeAttributeStatementBuilder<'a> {
pub fn new() -> Self {
Self {
attributes: IndexMap::new(),
+ errors: Vec::new(),
}
}
- pub fn build(&self) -> IndexMap<String, AttributeText<'a>> {
+ pub fn build(&self) -> ValidationResult<IndexMap<String, AttributeText<'a>>> {
+ if !self.errors.is_empty() {
+ return Err(self.errors.clone());
+ }
+ Ok(self.build_ignore_validation())
+ }
+
+ pub fn build_ignore_validation(&self) -> IndexMap<String, AttributeText<'a>> {
self.attributes.clone()
}
}
@@ -836,21 +912,37 @@ impl<'a> EdgeAttributes<'a> for EdgeAttributeStatementBuilder<'a> {
fn get_attributes_mut(&mut self) -> &mut IndexMap<String, AttributeText<'a>> {
&mut self.attributes
}
+
+ fn add_validation_error(&mut self, field: &'static str, message: &'static str) {
+ self.errors.push(ValidationError {
+ field: Borrowed(field),
+ message: Borrowed(message),
+ })
+ }
}
// I'm not a huge fan of needing this builder but having a hard time getting around &mut without it
pub struct EdgeAttributeStatementBuilder<'a> {
pub attributes: IndexMap<String, AttributeText<'a>>,
+ errors: Vec<ValidationError>,
}
impl<'a> EdgeAttributeStatementBuilder<'a> {
pub fn new() -> Self {
Self {
attributes: IndexMap::new(),
+ errors: Vec::new(),
+ }
+ }
+
+ pub fn build(&self) -> ValidationResult<IndexMap<String, AttributeText<'a>>> {
+ if !self.errors.is_empty() {
+ return Err(self.errors.clone());
}
+ Ok(self.build_ignore_validation())
}
- pub fn build(&self) -> IndexMap<String, AttributeText<'a>> {
+ pub fn build_ignore_validation(&self) -> IndexMap<String, AttributeText<'a>> {
self.attributes.clone()
}
}