es_fluent_shared/resource/
plan.rs1use std::collections::HashSet;
2
3use crate::fluent::FluentDomain;
4use crate::namespace::ResolvedNamespace;
5use crate::registry::StaticFluentDomain;
6
7use super::{ModuleResourceSpec, ResourceKey, ResourcePlanError};
8
9#[derive(Clone, Debug, Eq, PartialEq)]
11pub struct ResourcePlan {
12 specs: Vec<ModuleResourceSpec>,
13}
14
15impl ResourcePlan {
16 pub fn for_static_domain(
18 domain: StaticFluentDomain,
19 namespaces: &[&str],
20 ) -> Result<Self, ResourcePlanError> {
21 let mut resolved_namespaces = Vec::with_capacity(namespaces.len());
22 let mut seen = HashSet::new();
23
24 for namespace in namespaces {
25 let namespace = ResolvedNamespace::new(*namespace).map_err(|details| {
26 ResourcePlanError::InvalidNamespace {
27 namespace: (*namespace).to_string(),
28 details,
29 }
30 })?;
31
32 if seen.insert(namespace.clone()) {
33 resolved_namespaces.push(namespace);
34 }
35 }
36
37 Ok(Self::from_static_domain_and_namespaces(
38 domain,
39 &resolved_namespaces,
40 namespaces.is_empty(),
41 ))
42 }
43
44 fn from_static_domain_and_namespaces(
45 domain: StaticFluentDomain,
46 namespaces: &[ResolvedNamespace],
47 base_required: bool,
48 ) -> Self {
49 if namespaces.is_empty() {
50 return Self {
51 specs: vec![ModuleResourceSpec::base_for_static_domain(
52 domain,
53 base_required,
54 )],
55 };
56 }
57
58 let mut specs = Vec::with_capacity(namespaces.len() + 1);
59 specs.push(ModuleResourceSpec::base_for_static_domain(domain, false));
60 for namespace in namespaces {
61 specs.push(ModuleResourceSpec::namespaced_for_static_domain(
62 domain, namespace, true,
63 ));
64 }
65
66 Self { specs }
67 }
68
69 pub fn for_domain(domain: &str, namespaces: &[&str]) -> Result<Self, ResourcePlanError> {
71 let domain =
72 FluentDomain::try_new(domain).map_err(|details| ResourcePlanError::InvalidDomain {
73 domain: domain.to_string(),
74 details,
75 })?;
76
77 if namespaces.is_empty() {
78 return Ok(Self {
79 specs: vec![ModuleResourceSpec::base_for_domain(&domain, true)],
80 });
81 }
82
83 let mut specs = Vec::with_capacity(namespaces.len() + 1);
84 specs.push(ModuleResourceSpec::base_for_domain(&domain, false));
85
86 let mut seen = HashSet::new();
87 for namespace in namespaces {
88 let namespace = ResolvedNamespace::new(*namespace).map_err(|details| {
89 ResourcePlanError::InvalidNamespace {
90 namespace: (*namespace).to_string(),
91 details,
92 }
93 })?;
94
95 if !seen.insert(namespace.clone()) {
96 continue;
97 }
98
99 specs.push(ModuleResourceSpec::namespaced_for_domain(
100 &domain, &namespace, true,
101 ));
102 }
103
104 Ok(Self { specs })
105 }
106
107 pub fn sparse_for_domain(
109 domain: &str,
110 has_base_resource: bool,
111 namespaces: &[ResolvedNamespace],
112 base_required: bool,
113 ) -> Self {
114 let domain =
115 FluentDomain::try_new(domain).expect("sparse resource domain should be validated");
116 Self::sparse_for_validated_domain(&domain, has_base_resource, namespaces, base_required)
117 }
118
119 pub fn sparse_for_static_domain(
121 domain: StaticFluentDomain,
122 has_base_resource: bool,
123 namespaces: &[ResolvedNamespace],
124 base_required: bool,
125 ) -> Self {
126 let mut specs = Vec::with_capacity(namespaces.len() + usize::from(has_base_resource));
127
128 if has_base_resource {
129 specs.push(ModuleResourceSpec::base_for_static_domain(
130 domain,
131 base_required,
132 ));
133 }
134
135 for namespace in namespaces {
136 specs.push(ModuleResourceSpec::namespaced_for_static_domain(
137 domain, namespace, true,
138 ));
139 }
140
141 Self { specs }
142 }
143
144 fn sparse_for_validated_domain(
145 domain: &FluentDomain,
146 has_base_resource: bool,
147 namespaces: &[ResolvedNamespace],
148 base_required: bool,
149 ) -> Self {
150 let mut specs = Vec::with_capacity(namespaces.len() + usize::from(has_base_resource));
151
152 if has_base_resource {
153 specs.push(ModuleResourceSpec::base_for_domain(domain, base_required));
154 }
155
156 for namespace in namespaces {
157 specs.push(ModuleResourceSpec::namespaced_for_domain(
158 domain, namespace, true,
159 ));
160 }
161
162 Self { specs }
163 }
164
165 pub fn specs(&self) -> &[ModuleResourceSpec] {
167 &self.specs
168 }
169
170 pub fn into_specs(self) -> Vec<ModuleResourceSpec> {
172 self.specs
173 }
174}
175
176impl From<ResourcePlan> for Vec<ModuleResourceSpec> {
177 fn from(plan: ResourcePlan) -> Self {
178 plan.into_specs()
179 }
180}
181
182pub fn try_resource_plan_for(
192 domain: &str,
193 namespaces: &[&str],
194) -> Result<Vec<ModuleResourceSpec>, ResourcePlanError> {
195 ResourcePlan::for_domain(domain, namespaces).map(ResourcePlan::into_specs)
196}
197
198#[allow(
204 clippy::panic,
205 reason = "static metadata uses literal namespaces; use try_resource_plan_for for dynamic input"
206)]
207pub fn resource_plan_for(domain: &str, namespaces: &[&str]) -> Vec<ModuleResourceSpec> {
208 try_resource_plan_for(domain, namespaces)
209 .unwrap_or_else(|error| panic!("resource_plan_for received {error}"))
210}
211
212pub fn required_resource_keys_from_plan(plan: &[ModuleResourceSpec]) -> HashSet<ResourceKey> {
214 plan.iter()
215 .filter(|spec| spec.required)
216 .map(|spec| spec.key.clone())
217 .collect()
218}
219
220pub fn optional_resource_keys_from_plan(plan: &[ModuleResourceSpec]) -> HashSet<ResourceKey> {
222 plan.iter()
223 .filter(|spec| !spec.required)
224 .map(|spec| spec.key.clone())
225 .collect()
226}
227
228pub fn locale_is_ready(
230 required_keys: &HashSet<ResourceKey>,
231 loaded_keys: &HashSet<ResourceKey>,
232) -> bool {
233 required_keys.iter().all(|key| loaded_keys.contains(key))
234}