use super::format::ModelFormat;
use super::model::{ElementId, ElementKind, Model};
use super::views::ElementView;
use super::{InterchangeError, JsonLd, Kpar, Xmi, Yaml};
pub struct ModelHost {
model: Model,
source: Option<String>,
}
impl ModelHost {
pub fn from_xmi(bytes: &[u8]) -> Result<Self, ModelHostError> {
Self::from_format(&Xmi, bytes)
}
pub fn from_jsonld(bytes: &[u8]) -> Result<Self, ModelHostError> {
Self::from_format(&JsonLd, bytes)
}
pub fn from_yaml(bytes: &[u8]) -> Result<Self, ModelHostError> {
Self::from_format(&Yaml, bytes)
}
pub fn from_kpar(bytes: &[u8]) -> Result<Self, ModelHostError> {
Self::from_format(&Kpar, bytes)
}
pub fn from_format(fmt: &dyn ModelFormat, bytes: &[u8]) -> Result<Self, ModelHostError> {
let model = fmt.read(bytes).map_err(ModelHostError::Interchange)?;
Ok(Self {
model,
source: None,
})
}
pub fn from_file(path: &std::path::Path) -> Result<Self, ModelHostError> {
let fmt = super::detect_format(path).ok_or_else(|| {
ModelHostError::UnsupportedFormat(
path.extension()
.and_then(|e| e.to_str())
.unwrap_or("?")
.to_string(),
)
})?;
let bytes = std::fs::read(path).map_err(ModelHostError::Io)?;
Self::from_format(fmt.as_ref(), &bytes)
}
pub fn from_model(model: Model) -> Self {
Self {
model,
source: None,
}
}
pub fn model(&self) -> &Model {
&self.model
}
pub fn model_mut(&mut self) -> &mut Model {
&mut self.model
}
pub fn into_model(self) -> Model {
self.model
}
pub fn source(&self) -> Option<&str> {
self.source.as_deref()
}
pub fn root_views(&self) -> Vec<ElementView<'_>> {
self.model.root_views()
}
pub fn view(&self, id: &ElementId) -> Option<ElementView<'_>> {
self.model.view(id)
}
pub fn find_by_name(&self, name: &str) -> Vec<ElementView<'_>> {
self.model.find_by_name(name)
}
pub fn find_by_qualified_name(&self, qn: &str) -> Option<ElementView<'_>> {
self.model.find_by_qualified_name(qn)
}
pub fn find_by_kind(&self, kind: ElementKind) -> Vec<ElementView<'_>> {
self.model.find_by_kind(kind)
}
pub fn render(&self) -> String {
super::decompile(&self.model).text
}
pub fn to_xmi(&self) -> Result<Vec<u8>, ModelHostError> {
Xmi.write(&self.model).map_err(ModelHostError::Interchange)
}
pub fn to_jsonld(&self) -> Result<Vec<u8>, ModelHostError> {
JsonLd
.write(&self.model)
.map_err(ModelHostError::Interchange)
}
pub fn to_yaml(&self) -> Result<Vec<u8>, ModelHostError> {
Yaml.write(&self.model).map_err(ModelHostError::Interchange)
}
pub fn element_count(&self) -> usize {
self.model.element_count()
}
pub fn relationship_count(&self) -> usize {
self.model.relationship_count()
}
}
impl std::fmt::Debug for ModelHost {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ModelHost")
.field("elements", &self.model.element_count())
.field("relationships", &self.model.relationship_count())
.field("roots", &self.model.roots.len())
.field("has_source", &self.source.is_some())
.finish()
}
}
#[derive(Debug)]
pub enum ModelHostError {
EmptyModel,
Interchange(InterchangeError),
Io(std::io::Error),
UnsupportedFormat(String),
}
impl std::fmt::Display for ModelHostError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::EmptyModel => write!(f, "parsed text produced no model elements"),
Self::Interchange(e) => write!(f, "interchange: {e}"),
Self::Io(e) => write!(f, "io: {e}"),
Self::UnsupportedFormat(ext) => write!(f, "unsupported format: .{ext}"),
}
}
}
impl std::error::Error for ModelHostError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Interchange(e) => Some(e),
Self::Io(e) => Some(e),
_ => None,
}
}
}
#[cfg(test)]
impl ModelHost {
pub(crate) fn from_text(source: &str) -> Result<Self, ModelHostError> {
use crate::base::FileId;
use crate::hir::{FileText, RootDatabase, file_symbols_from_text};
use crate::interchange::model_from_symbols;
let db = RootDatabase::new();
let file_text = FileText::new(&db, FileId::new(0), source.to_string());
let symbols = file_symbols_from_text(&db, file_text);
if symbols.is_empty() {
return Err(ModelHostError::EmptyModel);
}
let model = model_from_symbols(&symbols);
Ok(Self {
model,
source: Some(source.to_string()),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn host_from_text_basic() {
let host = ModelHost::from_text("package P { part def A; }").expect("should parse");
assert!(host.element_count() > 0);
assert!(host.source().is_some());
let roots = host.root_views();
assert_eq!(roots.len(), 1);
assert_eq!(roots[0].name(), Some("P"));
}
#[test]
fn host_find_by_name() {
let host =
ModelHost::from_text("package P { part def Vehicle; part def Wheel; part w: Wheel; }")
.expect("should parse");
let vehicles = host.find_by_name("Vehicle");
assert_eq!(vehicles.len(), 1);
assert_eq!(vehicles[0].kind(), ElementKind::PartDefinition);
let ws = host.find_by_name("w");
assert_eq!(ws.len(), 1);
assert_eq!(ws[0].kind(), ElementKind::PartUsage);
}
#[test]
fn host_find_by_qualified_name() {
let host = ModelHost::from_text("package Outer { part def Inner; }").expect("should parse");
let found = host.find_by_qualified_name("Outer::Inner");
assert!(found.is_some());
assert_eq!(found.unwrap().kind(), ElementKind::PartDefinition);
}
#[test]
fn host_find_by_kind() {
let host = ModelHost::from_text("package P { part def A; part def B; part x: A; }")
.expect("should parse");
let defs = host.find_by_kind(ElementKind::PartDefinition);
assert_eq!(defs.len(), 2);
let usages = host.find_by_kind(ElementKind::PartUsage);
assert_eq!(usages.len(), 1);
}
#[test]
fn host_render_roundtrip() {
let source = "package P {\n part def A;\n}";
let host = ModelHost::from_text(source).expect("should parse");
let rendered = host.render();
assert!(rendered.contains("package P"));
assert!(rendered.contains("part def A"));
}
#[test]
fn host_empty_text_errors() {
let result = ModelHost::from_text("");
assert!(result.is_err());
}
#[test]
fn host_from_model_direct() {
let mut model = Model::new();
use super::super::model::Element;
let pkg = Element::new("pkg1", ElementKind::Package).with_name("Direct");
model.add_element(pkg);
let host = ModelHost::from_model(model);
assert_eq!(host.element_count(), 1);
assert!(host.source().is_none());
assert_eq!(host.find_by_name("Direct").len(), 1);
}
#[test]
fn host_debug_fmt() {
let host = ModelHost::from_text("package P;").expect("should parse");
let dbg = format!("{:?}", host);
assert!(dbg.contains("ModelHost"));
assert!(dbg.contains("elements"));
}
}