Skip to main content

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}