metar_taf_parser/common/parse.rs
1//! Unified entry-point for METAR and TAF parsing.
2//!
3//! [`parse`] inspects the leading token of the input string and dispatches to
4//! the correct specialised parser, returning a [`ParsedReport`] that wraps
5//! either a [`Metar`] or a [`Taf`].
6//!
7//! [`parse_strict`] behaves the same but requires an explicit `METAR`,
8//! `SPECI`, or `TAF` prefix and delegates to the strict parser variants, which
9//! reject any unrecognised group.
10
11use crate::metar::errors::MetarError;
12use crate::metar::models::Metar;
13use crate::metar::parser::metar::{parse_metar, parse_metar_strict};
14use crate::taf::errors::TafError;
15use crate::taf::models::taf::Taf;
16use crate::taf::parser::taf::{parse_taf, parse_taf_strict};
17use thiserror::Error;
18
19// ---------------------------------------------------------------------------
20// Public types
21// ---------------------------------------------------------------------------
22
23/// Result of a successful [`parse`] or [`parse_strict`] call.
24///
25/// Wraps either a fully parsed [`Metar`] or [`Taf`] report. Use pattern
26/// matching to access the inner value:
27///
28/// ```rust
29/// use metar_taf_parser::{parse, ParsedReport};
30///
31/// let report = parse("METAR LIRF 121250Z 18010KT 9999 FEW030 18/12 Q1015").unwrap();
32/// match report {
33/// ParsedReport::Metar(m) => println!("station: {}", m.station),
34/// ParsedReport::Taf(t) => println!("station: {}", t.station),
35/// }
36/// ```
37///
38/// The `Metar` variant is heap-allocated (`Box<Metar>`) to keep the enum size
39/// small: `Metar` is significantly larger than `Taf` on the stack.
40#[derive(Debug)]
41pub enum ParsedReport {
42 /// A parsed METAR or SPECI observation.
43 Metar(Box<Metar>),
44 /// A parsed TAF forecast.
45 Taf(Taf),
46}
47
48/// Error returned by [`parse`] and [`parse_strict`].
49///
50/// Wraps the underlying specialised error (`MetarError` or `TafError`) or
51/// signals that the report type could not be determined from the input prefix.
52#[derive(Debug, Error)]
53pub enum ParseError {
54 /// The input was dispatched to the METAR parser, which rejected it.
55 #[error("METAR parse error: {0}")]
56 Metar(#[from] MetarError),
57
58 /// The input was dispatched to the TAF parser, which rejected it.
59 #[error("TAF parse error: {0}")]
60 Taf(#[from] TafError),
61
62 /// The leading token is not `METAR`, `SPECI`, or `TAF`.
63 ///
64 /// Only returned by [`parse_strict`]; [`parse`] defaults to the METAR
65 /// parser when no recognised prefix is present.
66 #[error("unknown report type: expected a leading METAR, SPECI, or TAF token")]
67 UnknownReportType,
68}
69
70// ---------------------------------------------------------------------------
71// Public functions
72// ---------------------------------------------------------------------------
73
74/// Parses a METAR, SPECI, or TAF string, automatically selecting the correct
75/// decoder based on the leading token.
76///
77/// | Leading token | Parser used |
78/// |------------------------|-----------------|
79/// | `TAF` | [`parse_taf`] |
80/// | `METAR`, `SPECI`, none | [`parse_metar`] |
81///
82/// This function is tolerant: unrecognised groups are collected in
83/// `unparsed_groups` rather than causing an error. Use [`parse_strict`] for
84/// strict validation.
85///
86/// # Arguments
87///
88/// * `input` - Raw METAR or TAF string.
89///
90/// # Errors
91///
92/// Returns [`ParseError::Metar`] or [`ParseError::Taf`] if the selected
93/// parser rejects the input (e.g. empty string or missing mandatory fields).
94///
95/// # Example
96///
97/// ```rust
98/// use metar_taf_parser::{parse, ParsedReport};
99///
100/// let metar = parse("METAR LIRF 121250Z 18010KT 9999 FEW030 18/12 Q1015").unwrap();
101/// assert!(matches!(metar, ParsedReport::Metar(_)));
102///
103/// let taf = parse("TAF LIRF 121100Z 1212/1318 18010KT 9999 SCT020").unwrap();
104/// assert!(matches!(taf, ParsedReport::Taf(_)));
105/// ```
106pub fn parse(input: &str) -> Result<ParsedReport, ParseError> {
107 let first = input.split_whitespace().next().unwrap_or("");
108 if first == "TAF" {
109 Ok(ParsedReport::Taf(parse_taf(input)?))
110 } else {
111 Ok(ParsedReport::Metar(Box::new(parse_metar(input)?)))
112 }
113}
114
115/// Strict variant of [`parse`]: requires an explicit `METAR`, `SPECI`, or
116/// `TAF` leading token and rejects any unrecognised group.
117///
118/// | Leading token | Parser used |
119/// |-----------------|-----------------------|
120/// | `METAR`/`SPECI` | [`parse_metar_strict`] |
121/// | `TAF` | [`parse_taf_strict`] |
122/// | anything else | `Err(UnknownReportType)` |
123///
124/// # Errors
125///
126/// * [`ParseError::UnknownReportType`] — no recognised prefix found.
127/// * [`ParseError::Metar`] — METAR strict parser rejected the input.
128/// * [`ParseError::Taf`] — TAF strict parser rejected the input.
129///
130/// # Example
131///
132/// ```rust
133/// use metar_taf_parser::{parse_strict, ParseError};
134///
135/// assert!(parse_strict("LIRF 121250Z 18010KT 9999 FEW030 18/12 Q1015").is_err());
136/// ```
137pub fn parse_strict(input: &str) -> Result<ParsedReport, ParseError> {
138 let first = input.split_whitespace().next().unwrap_or("");
139 match first {
140 "METAR" | "SPECI" => Ok(ParsedReport::Metar(Box::new(parse_metar_strict(input)?))),
141 "TAF" => Ok(ParsedReport::Taf(parse_taf_strict(input)?)),
142 _ => Err(ParseError::UnknownReportType),
143 }
144}