Skip to main content

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 model;
55pub mod report;
56
57pub use dax::{Binding, RawRef, Token, TokenKind, bind, references, tokenize, unescape_name};
58pub use graph::{
59    BindingEdge, BindingSite, DependencyGraph, Provenance, StructuralEdge, UnusedObject, UsedBy,
60};
61pub use identity::{FieldRef, NameKey, ObjectId};
62pub use ingest::{Ingested, SkipKind, SkipNotice};
63pub use model::index::{
64    ColumnHandle, ExpressionHandle, FunctionHandle, HierarchyHandle, MeasureHandle, ModelIndex,
65    Resolved, TableHandle, UnqualifiedMatches,
66};
67pub use model::{
68    CalculationGroup, CalculationItem, Calendar, Column, ColumnKind, DaxExpressionKind,
69    DaxExpressionRef, ExpressionOwner, Function, Hierarchy, HierarchyLevel, Kpi, MExpressionRef,
70    Measure, Partition, PartitionSource, Relationship, Role, SharedExpression, Table,
71    TablePermission, TabularDatabase,
72};
73pub use report::{
74    BindingKind, BindingRef, Bookmark, BookmarkSection, BookmarkVisual, DatasetReference,
75    DrillthroughParameter, FieldTarget, FieldWell, Filter, Page, PageBinding, PageBindingKind,
76    Projection, ReportMeasure, ReportModel, Visual,
77};
78
79use thiserror::Error;
80
81/// Errors produced by ripbi-core.
82#[derive(Debug, Error)]
83pub enum Error {
84    /// A file or directory could not be read.
85    #[error("I/O error: {0}")]
86    Io(#[from] std::io::Error),
87    /// A `.pbix`/`.pbit` archive could not be opened or is malformed.
88    #[error("invalid archive: {0}")]
89    Archive(#[from] zip::result::ZipError),
90    /// A model or report JSON document could not be parsed.
91    #[error("invalid JSON: {0}")]
92    Json(#[from] serde_json::Error),
93    /// A TMDL document is structurally malformed and could not be parsed.
94    #[error("malformed TMDL: {0}")]
95    Tmdl(String),
96    /// The path is not a Power BI source this crate recognizes.
97    #[error("unsupported or unrecognized source format: {0}")]
98    UnsupportedFormat(String),
99}
100
101/// A result whose error is this crate's [`enum@Error`].
102pub type Result<T> = std::result::Result<T, Error>;