1#![deny(clippy::all)]
3
4use std::{collections::HashMap, sync::Arc};
5
6use anyhow::{Context, Error};
7use dashmap::DashMap;
8use from_variant::FromVariant;
9use once_cell::sync::Lazy;
10use rustc_hash::FxBuildHasher;
11use serde::Deserialize;
12
13use crate::{version::Version, BrowserData, Versions};
14
15#[derive(Debug, Clone, Deserialize, FromVariant)]
16#[serde(untagged)]
17#[allow(clippy::large_enum_variant)]
18pub enum Targets {
19 Query(Query),
20 EsModules(EsModules),
21 Versions(Versions),
22 HashMap(HashMap<String, QueryOrVersion, FxBuildHasher>),
23}
24
25#[derive(Debug, Clone, Copy, Deserialize)]
26pub struct EsModules {
27 #[allow(dead_code)]
28 esmodules: bool,
29}
30
31#[derive(Debug, Clone, Deserialize, FromVariant)]
32#[serde(untagged)]
33pub enum QueryOrVersion {
34 Query(Query),
35 Version(Version),
36}
37
38#[derive(Debug, Clone, Deserialize, FromVariant, Eq, PartialEq, PartialOrd, Ord, Hash)]
39#[serde(untagged)]
40pub enum Query {
41 Single(String),
42 Multiple(Vec<String>),
43}
44
45type QueryResult = Result<Arc<Versions>, Error>;
46
47impl Query {
48 fn exec(&self) -> QueryResult {
49 fn query<T>(s: &[T]) -> QueryResult
50 where
51 T: AsRef<str>,
52 {
53 let distribs = browserslist::resolve(
54 s,
55 &browserslist::Opts {
56 mobile_to_desktop: true,
57 ignore_unknown_versions: true,
58 ..Default::default()
59 },
60 )
61 .with_context(|| {
62 format!(
63 "failed to resolve browserslist query: {:?}",
64 s.iter().map(|v| v.as_ref()).collect::<Vec<_>>()
65 )
66 })?;
67
68 let versions =
69 BrowserData::parse_versions(distribs).expect("failed to parse browser version");
70
71 Ok(Arc::new(versions))
72 }
73
74 static CACHE: Lazy<DashMap<Query, Arc<Versions>, FxBuildHasher>> =
75 Lazy::new(Default::default);
76
77 if let Some(v) = CACHE.get(self) {
78 return Ok(v.clone());
79 }
80
81 let result = match *self {
82 Query::Single(ref s) => {
83 if s.is_empty() {
84 query(&["defaults"])
85 } else {
86 query(&[s])
87 }
88 }
89 Query::Multiple(ref s) => query(s),
90 }
91 .context("failed to execute query")?;
92
93 CACHE.insert(self.clone(), result.clone());
94
95 Ok(result)
96 }
97}
98
99pub fn targets_to_versions(v: Option<Targets>) -> Result<Arc<Versions>, Error> {
100 match v {
101 None => Ok(Default::default()),
102 Some(Targets::Versions(v)) => Ok(Arc::new(v)),
103 Some(Targets::Query(q)) => q
104 .exec()
105 .context("failed to convert target query to version data"),
106 Some(Targets::HashMap(mut map)) => {
107 let q = map.remove("browsers").map(|q| match q {
108 QueryOrVersion::Query(q) => q.exec().expect("failed to run query"),
109 _ => unreachable!(),
110 });
111
112 let node = map.remove("node").map(|q| match q {
113 QueryOrVersion::Version(v) => v,
114 QueryOrVersion::Query(..) => unreachable!(),
115 });
116
117 if map.is_empty() {
118 if let Some(q) = q {
119 let mut q = *q;
120 q.node = node;
121 return Ok(Arc::new(q));
122 }
123 }
124
125 let mut result = Versions::default();
126 for (k, v) in map.iter() {
127 match v {
128 QueryOrVersion::Query(q) => {
129 let v = q.exec().context("failed to run query")?;
130
131 for (k, v) in v.iter() {
132 result.insert(k, *v);
133 }
134 }
135 QueryOrVersion::Version(v) => {
136 result.insert(k, Some(*v));
137 }
138 }
139 }
140
141 unimplemented!("Targets: {:?}", map)
142 }
143 _ => unimplemented!("Option<Targets>: {:?}", v),
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::Query;
150
151 #[test]
152 fn test_empty() {
153 let res = Query::Single("".into()).exec().unwrap();
154 assert!(
155 !res.is_any_target(),
156 "empty query should return non-empty result"
157 );
158 }
159}