Skip to main content

finance_query/
lib.rs

1//! # finance-query
2//!
3//! A Rust library for querying financial data.
4//! Inspired by yfinance, with smart lazy loading for efficient data fetching.
5//!
6//! ## Quick Start
7//!
8//! ```no_run
9//! use finance_query::Ticker;
10//!
11//! #[tokio::main]
12//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
13//!     // Simple: Create a ticker with default configuration
14//!     let ticker = Ticker::new("AAPL").await?;
15//!
16//!     // First access to any quote property fetches ALL quote modules in one request
17//!     if let Some(financials) = ticker.financial_data().await? {
18//!         println!("Financial data: {:?}", financials);
19//!     }
20//!
21//!     // Subsequent accesses use cached data (no additional network calls)
22//!     if let Some(profile) = ticker.asset_profile().await? {
23//!         println!("Company profile: {:?}", profile);
24//!     }
25//!
26//!     // Chart data is fetched separately and cached by interval/range
27//!     let chart = ticker.chart(
28//!         finance_query::Interval::OneDay,
29//!         finance_query::TimeRange::OneMonth
30//!     ).await?;
31//!     println!("Candles: {}", chart.candles.len());
32//!
33//!     // Builder pattern: Fluent configuration
34//!     let ticker_jp = Ticker::builder("7203.T")
35//!         .lang("ja-JP")
36//!         .region_code("JP")
37//!         .timeout(std::time::Duration::from_secs(30))
38//!         .build()
39//!         .await?;
40//!
41//!     Ok(())
42//! }
43//! ```
44//!
45//! ## Lazy Loading and Caching
46//!
47//! The library fetches on demand and caches by default:
48//! - **Quote data**: all quote modules fetched together on first property access, then reused
49//! - **Chart data**: fetched and cached per (interval, range) combination
50//! - **Recommendations**: fetched once and cached
51//!
52//! A handle caches each response for 60 seconds by default. Use `.cache(ttl)`
53//! on the builder to change that window, or `.no_cache()` to fetch fresh on
54//! every call.
55
56#![warn(missing_docs)]
57#![warn(rustdoc::missing_crate_level_docs)]
58
59// === Modules ===
60// Public modules
61/// External data source adapters (internal — use the public API modules).
62pub(crate) mod adapters;
63/// Error types and result definitions.
64pub mod error;
65/// Non-symbol-specific operations (search, lookup, screeners, market data, etc.).
66pub mod finance;
67pub mod edgar {
68    //! SEC EDGAR API client (keyless — always available, no feature flag needed).
69    //!
70    //!
71    //! Requires a one-time [`init`] call with a contact email address.
72    pub use crate::adapters::edgar::{
73        company_facts, filing_index, init, init_with_config, resolve_cik, search, submissions,
74    };
75}
76
77// Internal modules
78mod backoff;
79mod constants;
80mod models;
81#[cfg(any(feature = "risk", feature = "backtesting"))]
82mod perf_metrics;
83mod providers;
84pub(crate) mod rate_limiter;
85mod scrapers;
86mod ticker;
87mod tickers;
88mod utils;
89
90// Feature-gated external data source modules
91#[cfg(feature = "alphavantage")]
92pub mod alphavantage {
93    //! Alpha Vantage configuration (requires the `alphavantage` feature).
94    //!
95    //! Call [`init`] before constructing a provider when the API key should
96    //! come from application configuration instead of the process environment.
97    pub use crate::adapters::alphavantage::{init, init_with_timeout};
98}
99
100#[cfg(feature = "fmp")]
101pub mod fmp {
102    //! Financial Modeling Prep configuration (requires the `fmp` feature).
103    //!
104    //! Quotes, charts, fundamentals, corporate, and research data are served
105    //! through [`Providers`](crate::Providers). Only key configuration is
106    //! exposed here; the adapter's raw response types stay internal.
107    pub use crate::adapters::fmp::{init, init_with_timeout};
108}
109
110#[cfg(feature = "polygon")]
111pub mod polygon {
112    //! Massive (formerly Polygon.io) configuration (requires the `polygon`
113    //! feature).
114    //!
115    //! REST operations route through [`Providers`](crate::Providers) and
116    //! Polygon's real-time channels through
117    //! [`streaming`](crate::streaming). Only key configuration is exposed
118    //! here; the adapter's raw response types stay internal.
119    pub use crate::adapters::polygon::{init, init_with_timeout};
120}
121
122#[cfg(feature = "fred")]
123pub mod fred {
124    //! FRED economic data API (requires `fred` feature).
125    //!
126    //! Access 800k+ macroeconomic time series and US Treasury yield curve data.
127    pub use crate::adapters::fred::{
128        ReleaseDate, init, init_with_timeout, release_dates, series, treasury_yields,
129    };
130    pub use crate::models::economic::{MacroObservation, MacroSeries, TreasuryYield};
131}
132
133#[cfg(feature = "crypto")]
134pub mod crypto {
135    //! CoinGecko cryptocurrency data (requires `crypto` feature).
136    //!
137    //! Keyless shortcuts, the crypto counterpart to [`finance`](crate::finance).
138    //! [`Market::crypto_trending`](crate::domains::Market::crypto_trending) and
139    //! [`crypto_global`](crate::domains::Market::crypto_global) reach the same
140    //! data through provider routing when other CRYPTO providers are configured.
141    pub use crate::adapters::coingecko::{
142        CoinQuote, coin, coins, fetch_crypto_global_response as global,
143        fetch_crypto_trending_response as trending, fetch_symbol_search_response as search,
144    };
145    pub use crate::models::crypto::{GlobalCryptoStats, TrendingCoin};
146    pub use crate::models::discovery::reference::SymbolMatch;
147}
148
149#[cfg(feature = "gdelt")]
150pub mod gdelt {
151    //! GDELT global news search (requires `gdelt` feature, keyless).
152    //!
153    //! Keyless shortcut for callers that want GDELT specifically;
154    //! [`Ticker::news`](crate::Ticker::news) reaches the same data through
155    //! `Capability::CORPORATE` when GDELT is routed.
156    pub use crate::adapters::gdelt::fetch_news_response as news;
157    pub use crate::models::corporate::news::News;
158}
159
160#[cfg(feature = "cftc")]
161pub mod cftc {
162    //! CFTC Commitments of Traders positioning (requires `cftc` feature, keyless).
163    //!
164    //! Keyless shortcut;
165    //! [`FuturesContract::commitments_of_traders`](crate::FuturesContract::commitments_of_traders)
166    //! reaches the same data through `Capability::FUTURES` when CFTC is routed.
167    pub use crate::adapters::cftc::fetch_commitments_of_traders_response as commitments_of_traders;
168    pub use crate::models::futures::cot::{CommitmentsOfTraders, CotObservation};
169}
170
171#[cfg(feature = "openfigi")]
172pub mod openfigi {
173    //! Security-identifier resolution via OpenFIGI (requires `openfigi`
174    //! feature, keyless).
175    //!
176    //! Resolves a CUSIP, ISIN, SEDOL, or FIGI to the instruments carrying it —
177    //! the missing step for any dataset that identifies holdings by CUSIP
178    //! rather than ticker, such as 13F filings.
179    //!
180    //! Lives here rather than behind the Providers API because resolution is
181    //! not tied to a symbol handle and maps onto no
182    //! [`Capability`](crate::Capability), the same reasoning that puts
183    //! [`edgar`](crate::edgar) and [`fred`](crate::fred) at the crate root.
184    //!
185    //! No API key is required; `OPENFIGI_API_KEY` is optional and only raises
186    //! the quota.
187    //!
188    //! ```no_run
189    //! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
190    //! use finance_query::openfigi;
191    //!
192    //! // One CUSIP maps to every venue listing of the security.
193    //! for listing in openfigi::resolve_cusip("037833100").await? {
194    //!     println!("{:?} on {:?}", listing.ticker, listing.exchange_code);
195    //! }
196    //! # Ok(())
197    //! # }
198    //! ```
199
200    use crate::error::Result;
201    pub use crate::models::discovery::figi::{SecurityIdKind, SecurityMapping};
202
203    /// Resolve a CUSIP to every instrument carrying it.
204    ///
205    /// Returns an empty list when the identifier is well-formed but matches
206    /// nothing; a malformed identifier is an error.
207    pub async fn resolve_cusip(cusip: &str) -> Result<Vec<SecurityMapping>> {
208        crate::adapters::openfigi::resolve(SecurityIdKind::Cusip, cusip).await
209    }
210
211    /// Resolve an ISIN to every instrument carrying it.
212    pub async fn resolve_isin(isin: &str) -> Result<Vec<SecurityMapping>> {
213        crate::adapters::openfigi::resolve(SecurityIdKind::Isin, isin).await
214    }
215
216    /// Resolve a SEDOL to every instrument carrying it.
217    pub async fn resolve_sedol(sedol: &str) -> Result<Vec<SecurityMapping>> {
218        crate::adapters::openfigi::resolve(SecurityIdKind::Sedol, sedol).await
219    }
220
221    /// Resolve an identifier of any supported [`SecurityIdKind`].
222    pub async fn resolve(kind: SecurityIdKind, id: &str) -> Result<Vec<SecurityMapping>> {
223        crate::adapters::openfigi::resolve(kind, id).await
224    }
225
226    /// Resolve many identifiers of the same kind in as few requests as
227    /// possible (OpenFIGI accepts 10 per request without a key).
228    ///
229    /// The result is positional: element `i` answers `ids[i]`, with an empty
230    /// list where nothing matched.
231    pub async fn resolve_many(
232        kind: SecurityIdKind,
233        ids: &[&str],
234    ) -> Result<Vec<Vec<SecurityMapping>>> {
235        crate::adapters::openfigi::resolve_many(kind, ids).await
236    }
237}
238
239#[cfg(feature = "defi")]
240pub mod defi {
241    //! Market-wide DeFi data via DefiLlama (requires `defi` feature, keyless).
242    //!
243    //! Chain rankings and stablecoin supplies describe the market as a whole,
244    //! not one asset, so there is no symbol handle to hang them off — they sit
245    //! at the crate root the way [`edgar`](crate::edgar) and
246    //! [`fred`](crate::fred) do.
247    //!
248    //! Protocol-shaped data *is* symbol-shaped and lives on
249    //! [`CryptoCoin::tvl`](crate::CryptoCoin::tvl) instead.
250    //!
251    //! ```no_run
252    //! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
253    //! use finance_query::defi;
254    //!
255    //! for chain in defi::chains().await?.into_iter().take(5) {
256    //!     println!("{}: ${:?}", chain.name, chain.tvl);
257    //! }
258    //! # Ok(())
259    //! # }
260    //! ```
261
262    use crate::error::Result;
263    pub use crate::models::crypto::defi::{
264        ChainAllocation, ChainTvl, ProtocolTvl, StablecoinSupply, TvlPoint,
265    };
266
267    /// Fetch aggregate total value locked for every chain, largest first.
268    pub async fn chains() -> Result<Vec<ChainTvl>> {
269        crate::adapters::defillama::chains().await
270    }
271
272    /// Fetch circulating supply for every tracked stablecoin, largest first.
273    ///
274    /// Supplies are denominated in the coin's pegged asset — read `peg_type`
275    /// before summing across coins pegged to different currencies.
276    pub async fn stablecoins() -> Result<Vec<StablecoinSupply>> {
277        crate::adapters::defillama::stablecoins().await
278    }
279}
280
281pub mod feeds;
282
283#[cfg(feature = "risk")]
284pub mod risk;
285
286#[cfg(feature = "translation")]
287pub mod translation;
288
289// ============================================================================
290// High-level API - Primary interface for most use cases
291// ============================================================================
292pub mod domains;
293pub use models::quote::response::QuoteSummaryResponse;
294
295/// Yahoo `quoteSummary` module types carried by [`QuoteSummaryResponse`].
296///
297/// A [`QuoteProvider`] implementation populates these; they are also what
298/// [`Ticker::price`], [`Ticker::asset_profile`] and the other module
299/// accessors return.
300pub mod quote {
301    pub use crate::models::corporate::{
302        AssetProfile, CalendarEvents, CompanyOfficer, Earnings, EarningsHistory, EarningsTrend,
303        EquityPerformance, FundOwnership, FundPerformance, FundProfile, InsiderHolders,
304        InsiderTransactions, InstitutionOwnership, MajorHoldersBreakdown, NetSharePurchaseActivity,
305        RecommendationTrend, SecFilings, SummaryProfile, TopHoldings, UpgradeDowngradeHistory,
306    };
307    pub use crate::models::fundamentals::{DefaultKeyStatistics, FinancialData, SummaryDetail};
308    pub use crate::models::market::{IndexTrend, IndustryTrend, SectorTrend};
309    pub use crate::models::quote::price::Price;
310    pub use crate::models::quote::quote_type::QuoteTypeData;
311}
312pub use providers::CryptoProvider;
313pub use providers::EconomicProvider;
314pub use providers::ForexProvider;
315pub use providers::config::{Providers, ProvidersBuilder};
316pub use providers::{
317    CalendarProvider, ChartProvider, CommoditiesProvider, CorporateProvider, DiscoveryProvider,
318    FilingsProvider, FundamentalsProvider, FuturesProvider, IndicesProvider, MarketProvider,
319    OptionsProvider, ProviderAdapter, ProviderCore, ProviderSet, QuoteProvider, Routes,
320};
321pub use providers::{
322    Capability, CustomId, Fetch, Operation, Provider, ProviderHealth, RetryPolicy,
323};
324
325/// The attribute every capability trait implementation needs.
326///
327/// Re-exported so a downstream crate does not add its own `async-trait`
328/// dependency and risk a version mismatch with this one. The trade is that
329/// this crate's semver now covers `async-trait`: an `async-trait` 0.2 would
330/// be a breaking change here, because downstream impls use this attribute.
331pub use async_trait::async_trait;
332pub use ticker::{ClientHandle, Ticker, TickerBuilder};
333
334// Domain-specific query handles — constructable via Providers factory methods.
335pub use domains::CryptoCoin;
336pub use domains::ForexPair;
337pub use domains::{EconomicCatalog, EconomicIndicator};
338
339// Remaining Capability handles — indices, futures, commodities, filings, discovery
340pub use domains::Commodity;
341pub use domains::Discovery;
342pub use domains::Filings;
343pub use domains::FuturesContract;
344pub use domains::Index;
345pub use domains::Snapshot;
346// `Market` is unconditional — its grouped-daily/crypto methods route through
347// CHART/CRYPTO, which have their own (broader) per-method gating rather than
348// requiring fmp/polygon/alphavantage.
349pub use domains::Market;
350pub use domains::MarketCalendar;
351
352// Provider-specific financial data functions
353// (FMP, Polygon, Alpha Vantage — defined in the finance module)
354#[cfg(feature = "polygon")]
355pub use finance::symbol_sentiment;
356#[cfg(feature = "fmp")]
357pub use finance::{
358    AnalystEstimate, AnalystRecommendation, InsiderTransaction, Period, analyst_estimates,
359    analyst_recommendations, insider_trading,
360};
361#[cfg(feature = "alphavantage")]
362pub use finance::{EarningsCalendarEntry, IpoCalendarEntry, earnings_calendar, ipo_calendar};
363
364pub use tickers::{
365    BatchCapitalGainsResponse, BatchChartsResponse, BatchDividendsResponse,
366    BatchFinancialsResponse, BatchNewsResponse, BatchOptionsResponse, BatchQuotesResponse,
367    BatchRecommendationsResponse, BatchSparksResponse, BatchSplitsResponse, Tickers,
368    TickersBuilder,
369};
370
371#[cfg(feature = "indicators")]
372pub use tickers::BatchIndicatorsResponse;
373
374// ============================================================================
375// Error types and results
376// ============================================================================
377// Capability-routed response types (DISCOVERY / CALENDAR / MARKET)
378pub use models::calendar::market::{CalendarDetail, CalendarKind, MarketCalendarEntry};
379pub use models::discovery::reference::{
380    ExchangeInfo, ScreenerFilters, ScreenerMatch, SymbolDetails, SymbolMatch,
381};
382pub use models::market::performance::{
383    IndustryPe, MoverDirection, MoverQuote, SectorPe, SectorPerformance, SectorPerformanceHistory,
384};
385
386pub use error::{ErrorCategory, FinanceError, Result};
387
388// ============================================================================
389// Options - Configure API requests
390// ============================================================================
391pub use finance::{LookupOptions, LookupType, SearchOptions};
392
393// ============================================================================
394// Parameter enums - Used with Ticker and finance methods
395// ============================================================================
396pub use constants::indices::Region as IndicesRegion;
397pub use constants::screeners::Screener;
398pub use constants::sectors::Sector;
399pub use constants::{Frequency, Interval, Region, StatementType, TimeRange, ValueFormat};
400
401// ============================================================================
402// Response types - Top-level types returned by API methods
403// ============================================================================
404pub use models::{
405    calendar::{CalendarEvent, EventKind},
406    chart::Chart,
407    chart::spark::Spark,
408    corporate::earnings_transcript::EarningsTranscript,
409    corporate::governance::{EmployeeCount, ExecutiveCompensation},
410    corporate::news::News,
411    corporate::press_release::PressRelease,
412    corporate::recommendation::Recommendation,
413    corporate::transcript::{Transcript, TranscriptWithMeta},
414    discovery::lookup::LookupResults,
415    discovery::screeners::ScreenerResults,
416    discovery::search::SearchResults,
417    discovery::trending::TrendingQuote,
418    filings::{
419        CompanyFacts, CongressionalTrade, EdgarSearchResults, EdgarSubmissions, FailToDeliver,
420        FilingSearchFilters, FilingSearchHit, FilingSection, FilingSectionForm, InsiderTrade,
421        InstitutionalHolding, ProviderFiling, ProviderFilings, RiskFactor,
422    },
423    fundamentals::{
424        CompanyProfile, EarningsSurprise, EtfCountryWeighting, EtfHolding, EtfProfile,
425        EtfSectorWeighting, FinancialRatiosTtm, FinancialStatement, GradingAction, KeyMetricsTtm,
426        PriceTargetConsensus, PriceTargetSummary, RatingConsensus, ShareFloat, ShortInterest,
427        ShortVolume,
428    },
429    market::currencies::Currency,
430    market::exchanges::Exchange,
431    market::hours::MarketHours,
432    market::industries::IndustryData,
433    market::market_summary::MarketSummaryQuote,
434    market::sectors::SectorData,
435    options::Options,
436    quote::Quote,
437    sentiment::{FearAndGreed, FearGreedLabel, SymbolSentiment},
438};
439// Offline VADER sentiment scoring (feature-gated)
440#[cfg(feature = "sentiment")]
441pub use models::sentiment::{Sentiment, SentimentLabel, analyze as analyze_sentiment};
442// Multi-provider capability response types (feature-gated)
443pub use models::commodities::CommodityQuote;
444pub use models::crypto::CryptoQuote;
445pub use models::economic::{
446    EconomicCategory, EconomicRelease, EconomicSeries, EconomicSeriesMatch,
447};
448pub use models::forex::ForexQuote;
449pub use models::futures::FuturesQuote;
450#[cfg(feature = "cftc")]
451pub use models::futures::cot::{CommitmentsOfTraders, CotObservation};
452pub use models::indices::{IndexConstituent, IndexConstituentChange, IndexQuote, MajorIndex};
453pub use models::quote::snapshot::{AssetClass, MarketSnapshot};
454
455// ============================================================================
456// Nested types - Commonly accessed fields within response types
457// ============================================================================
458pub use models::{
459    chart::{Candle, CapitalGain, ChartEvents, ChartMeta, Dividend, DividendAnalytics, Split},
460    corporate::recommendation::SimilarSymbol,
461    discovery::lookup::LookupQuote,
462    discovery::screeners::ScreenerQuote,
463    discovery::search::{
464        ResearchReport, ResearchReports, SearchNews, SearchNewsList, SearchQuote, SearchQuotes,
465    },
466    filings::filing_index::{EdgarFilingIndex, EdgarFilingIndexItem},
467    filings::{
468        CikEntry, EdgarFiling, EdgarFilingFile, EdgarFilingRecent, EdgarFilings, EdgarSearchHit,
469        EdgarSearchHitsContainer, EdgarSearchSource, EdgarSearchTotal, FactConcept, FactUnit,
470        FactsByTaxonomy,
471    },
472    market::hours::MarketTime,
473    market::market_summary::SparkData,
474    options::{Contracts, OptionChain, OptionContract, OptionsQuote},
475    quote::FormattedValue,
476};
477
478// ============================================================================
479// Query builders - Types for constructing custom screener queries
480// ============================================================================
481pub use constants::exchange_codes::ExchangeCode;
482pub use constants::industries::Industry;
483pub use models::discovery::screeners::{
484    ConditionValue, EquityField, EquityScreenerQuery, FundField, FundScreenerQuery,
485    LogicalOperator, Operator, QueryCondition, QueryGroup, QueryOperand, QuoteType, ScreenerField,
486    ScreenerFieldExt, ScreenerFundCategory, ScreenerPeerGroup, ScreenerQuery, SortType,
487};
488
489// ============================================================================
490// Real-time streaming
491// ============================================================================
492// WebSocket-based real-time price streaming with a Flow-like Stream API.
493pub mod streaming;
494
495// ============================================================================
496// Format type parameters — phantom types for compile-time format selection
497// ============================================================================
498
499/// Compile-time format type parameters for [`Quote`] and other
500/// `FormattedValue`-bearing structs.
501///
502/// | Marker | `F::Value<f64>` | Access pattern |
503/// |---|---|---|
504/// | [`format::Both`]  | `FormattedValue<f64>` | `.raw` / `.fmt` / `.long_fmt` |
505/// | [`format::Raw`]   | `f64`                 | direct (no unwrapping) |
506/// | [`format::Pretty`] | `String`             | human-readable string |
507///
508/// ```no_run
509/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
510/// use finance_query::{format, Ticker};
511/// let ticker = Ticker::new("AAPL").await?;
512/// let quote: finance_query::Quote<format::Raw> = ticker.quote().await?;
513/// # Ok(())
514/// # }
515/// ```
516pub mod format {
517    pub use crate::models::format::{Both, Pretty, Raw};
518}
519
520// ============================================================================
521// DataFrame support (requires "dataframe" feature)
522// ============================================================================
523// When enabled, structs with #[derive(ToDataFrame)] get a to_dataframe() method.
524// The derive macro auto-generates DataFrame conversion for all scalar fields.
525#[cfg(feature = "dataframe")]
526pub use finance_query_derive::ToDataFrame;
527
528// ============================================================================
529// Technical Indicators (requires "indicators" feature)
530// ============================================================================
531// Technical analysis indicators for price data (SMA, EMA, RSI, MACD, Bollinger Bands).
532// When enabled, Chart gets extension methods: chart.sma(), chart.ema(), chart.rsi(), etc.
533#[cfg(feature = "indicators")]
534pub mod indicators;
535
536#[cfg(feature = "indicators")]
537pub use indicators::{
538    // Summary types
539    AroonData,
540    // Individual indicator types
541    BollingerBands,
542    BollingerBandsData,
543    BullBearPowerData,
544    // Candlestick pattern types
545    CandlePattern,
546    DonchianChannelsData,
547    ElderRayData,
548    IchimokuData,
549    Indicator,
550    IndicatorError,
551    IndicatorResult,
552    IndicatorsSummary,
553    KeltnerChannelsData,
554    MacdData,
555    MacdResult,
556    PatternSentiment,
557    StochasticData,
558    SuperTrendData,
559    atr,
560    patterns,
561};
562
563// ============================================================================
564// Backtesting Engine (requires "backtesting" feature)
565// ============================================================================
566// Strategy backtesting with pre-built and custom strategies, position tracking,
567// stop-loss/take-profit, comprehensive performance metrics, parameter optimization,
568// walk-forward validation, Monte Carlo simulation, and multi-symbol portfolio.
569#[cfg(feature = "backtesting")]
570pub mod backtesting;
571
572// ============================================================================
573// Compile-time thread-safety assertions
574// ============================================================================
575// Ticker and Tickers must be Send + Sync so they can be shared across
576// async tasks and held across .await points (e.g., in Arc, tokio::spawn).
577const _: () = {
578    const fn assert_send_sync<T: Send + Sync>() {}
579    let _ = assert_send_sync::<Ticker>;
580    let _ = assert_send_sync::<Tickers>;
581};
582
583/// Parse PDF text lines from arbitrary bytes, for `fuzz/fuzz_targets`.
584///
585/// Not part of the public API. Requires the `fuzzing` feature, which no normal
586/// build enables.
587#[cfg(all(feature = "fuzzing", feature = "housetrades"))]
588#[doc(hidden)]
589pub fn __fuzz_pdf_extract_lines(bytes: Vec<u8>) -> Option<Vec<String>> {
590    crate::adapters::housetrades::pdf::extract_lines(bytes).ok()
591}
592
593/// Drive the PDF parsers that need no decryption, for `fuzz/fuzz_targets`.
594///
595/// Not part of the public API. Requires the `fuzzing` feature.
596#[cfg(all(feature = "fuzzing", feature = "housetrades"))]
597#[doc(hidden)]
598pub fn __fuzz_pdf_unencrypted(bytes: &[u8]) {
599    crate::adapters::housetrades::pdf::fuzz_unencrypted(bytes);
600}