use crate::model::ProcessDefinition;
use thiserror::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BpmnFormat {
Json,
Xml,
}
#[derive(Debug, Error)]
pub enum DetectionError {
#[error("Unable to detect format: input is empty or ambiguous")]
UnableToDetect,
#[error("Invalid format: {0}")]
InvalidFormat(String),
}
pub struct FormatDetector;
impl FormatDetector {
pub fn detect(input: &str) -> Result<BpmnFormat, DetectionError> {
let trimmed = input.trim();
if trimmed.is_empty() {
return Err(DetectionError::UnableToDetect);
}
if trimmed.starts_with("<?xml") || trimmed.starts_with("<bpmn2:") || trimmed.starts_with("<bpmn:") {
return Ok(BpmnFormat::Xml);
}
if trimmed.starts_with('{') || trimmed.starts_with('[') {
return Ok(BpmnFormat::Json);
}
if trimmed.contains('<') && trimmed.contains('>') {
if trimmed.contains("bpmn2:") || trimmed.contains("bpmn:") || trimmed.contains("definitions") {
return Ok(BpmnFormat::Xml);
}
}
if trimmed.starts_with('{') {
return Ok(BpmnFormat::Json);
}
Err(DetectionError::UnableToDetect)
}
pub fn detect_with_confidence(input: &str) -> Result<(BpmnFormat, f64), DetectionError> {
let format = Self::detect(input)?;
let confidence = match format {
BpmnFormat::Xml => {
if input.trim().starts_with("<?xml") {
1.0
} else if input.contains("bpmn2:") || input.contains("bpmn:") {
0.9
} else {
0.7
}
}
BpmnFormat::Json => {
if input.trim().starts_with('{') && input.trim().ends_with('}') {
0.95
} else {
0.8
}
}
};
Ok((format, confidence))
}
}
#[derive(Debug, Error)]
pub enum ParseError {
#[error("JSON parse error: {0}")]
Json(#[from] serde_json::Error),
#[error("XML parse error: {0}")]
Xml(String),
#[error("Format detection error: {0}")]
Detection(#[from] DetectionError),
#[error("Unsupported format")]
UnsupportedFormat,
}
#[derive(Debug, Error)]
pub enum SerializeError {
#[error("JSON serialize error: {0}")]
Json(#[from] serde_json::Error),
#[error("XML serialize error: {0}")]
Xml(String),
#[error("Unsupported format")]
UnsupportedFormat,
}
pub trait BpmnParser: Send + Sync {
fn parse(&self, input: &str) -> Result<ProcessDefinition, ParseError>;
fn format(&self) -> BpmnFormat;
}
pub struct JsonParser;
impl BpmnParser for JsonParser {
fn parse(&self, input: &str) -> Result<ProcessDefinition, ParseError> {
ProcessDefinition::from_json(input).map_err(ParseError::Json)
}
fn format(&self) -> BpmnFormat {
BpmnFormat::Json
}
}
pub struct XmlParser;
impl BpmnParser for XmlParser {
fn parse(&self, input: &str) -> Result<ProcessDefinition, ParseError> {
ProcessDefinition::from_xml(input)
}
fn format(&self) -> BpmnFormat {
BpmnFormat::Xml
}
}
pub struct AutoParser;
impl AutoParser {
pub fn parse(input: &str) -> Result<(ProcessDefinition, BpmnFormat), ParseError> {
let format = FormatDetector::detect(input)?;
let definition = match format {
BpmnFormat::Json => ProcessDefinition::from_json(input).map_err(ParseError::Json)?,
BpmnFormat::Xml => ProcessDefinition::from_xml(input)?,
};
Ok((definition, format))
}
}