ripbi_core/lib.rs
1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3#![deny(rustdoc::broken_intra_doc_links)]
4
5//! Core library for ripbi: Power BI ingestion, DAX lexing, and dependency-graph analysis.
6//!
7//! This crate never prints or exits; all fallible operations return [`Result`].
8//!
9//! Analysis starts from a [`TabularDatabase`] — the format-agnostic semantic model
10//! every source format normalizes into — paired with a [`ModelIndex`] for resolving
11//! the names that DAX and report bindings refer to objects by:
12//!
13//! ```
14//! use ripbi_core::{Measure, ModelIndex, Table, TabularDatabase};
15//!
16//! let db = TabularDatabase {
17//! tables: vec![Table {
18//! name: "Sales".to_string(),
19//! measures: vec![Measure {
20//! name: "Total".to_string(),
21//! expression: "SUM(Sales[Amount])".to_string(),
22//! ..Default::default()
23//! }],
24//! ..Default::default()
25//! }],
26//! ..Default::default()
27//! };
28//!
29//! // Names compare case-insensitively, as the Analysis Services engine does.
30//! let index = ModelIndex::build(&db);
31//! assert!(index.resolve_table("SALES").is_some());
32//!
33//! // Expressions are enumerated with their owner, ready for the DAX lexer.
34//! let expressions = db.dax_expressions();
35//! assert_eq!(expressions.len(), 1);
36//! assert_eq!(expressions[0].text, "SUM(Sales[Amount])");
37//! ```
38//!
39//! The report side is one [`ReportModel`] per report sharing the model: its
40//! [`bindings`](ReportModel::bindings) are the reachability roots, and its
41//! [`dax_expressions`](ReportModel::dax_expressions) add report-level measures on
42//! top of the model's own expressions.
43//!
44//! Source formats are ingested through [`ingest`]: `semantic_model` parses a TMDL
45//! `.SemanticModel` folder into a [`TabularDatabase`], and `report` parses a PBIR
46//! `.Report` folder into a [`ReportModel`]. Every ingestion entry point returns
47//! [`Ingested`], pairing the parsed value with the [`SkipNotice`]s it recorded —
48//! warnings as data, so the CLI decides how to surface them.
49
50pub mod dax;
51pub mod graph;
52pub mod identity;
53pub mod ingest;
54pub mod m;
55pub mod model;
56pub mod report;
57
58pub use dax::{Binding, RawRef, Token, TokenKind, bind, references, tokenize, unescape_name};
59pub use graph::{
60 AutoDateTimeStatus, AutoDateTimeVerdict, BindingEdge, BindingSite, DependencyGraph, Provenance,
61 StructuralEdge, UnusedObject, UsedBy,
62};
63pub use identity::{FieldRef, NameKey, ObjectId};
64pub use ingest::{Ingested, SkipKind, SkipNotice};
65pub use model::index::{
66 ColumnHandle, ExpressionHandle, FunctionHandle, HierarchyHandle, MeasureHandle, ModelIndex,
67 Resolved, TableHandle, UnqualifiedMatches,
68};
69pub use model::{
70 CalculationGroup, CalculationItem, Calendar, Column, ColumnKind, DaxExpressionKind,
71 DaxExpressionRef, ExpressionOwner, Function, Hierarchy, HierarchyLevel, HierarchyRef, Kpi,
72 MExpressionRef, Measure, Partition, PartitionSource, Relationship, Role, SharedExpression,
73 Table, TablePermission, TabularDatabase, Variation,
74};
75pub use report::{
76 BindingKind, BindingRef, Bookmark, BookmarkSection, BookmarkVisual, DatasetReference,
77 DrillthroughParameter, FieldTarget, FieldWell, Filter, Page, PageBinding, PageBindingKind,
78 Projection, ReportMeasure, ReportModel, Visual,
79};
80
81use thiserror::Error;
82
83/// Errors produced by ripbi-core.
84#[derive(Debug, Error)]
85pub enum Error {
86 /// A file or directory could not be read.
87 #[error("I/O error: {0}")]
88 Io(#[from] std::io::Error),
89 /// A `.pbix`/`.pbit` archive could not be opened or is malformed.
90 #[error("invalid archive: {0}")]
91 Archive(#[from] zip::result::ZipError),
92 /// A model or report JSON document could not be parsed.
93 #[error("invalid JSON: {0}")]
94 Json(#[from] serde_json::Error),
95 /// A TMDL document is structurally malformed and could not be parsed.
96 #[error("malformed TMDL: {0}")]
97 Tmdl(String),
98 /// The path is not a Power BI source this crate recognizes.
99 #[error("unsupported or unrecognized source format: {0}")]
100 UnsupportedFormat(String),
101}
102
103/// A result whose error is this crate's [`enum@Error`].
104pub type Result<T> = std::result::Result<T, Error>;