1use serde_json::Value;
5
6use crate::HapError;
7use crate::value::{self, Findings, Line, Note, Obj};
8
9#[derive(Debug, Clone, PartialEq)]
11#[non_exhaustive]
12pub struct PackInfo {
13 pub app: PackApp,
15 pub modules: Vec<PackModule>,
17 pub packages: Vec<PackPackage>,
19 pub notes: Vec<Note>,
21 pub unmodelled: Vec<String>,
23 raw: Value,
24}
25
26#[derive(Debug, Clone, Default, PartialEq, Eq)]
28#[non_exhaustive]
29pub struct PackApp {
30 pub bundle_name: Option<String>,
32 pub bundle_type: Option<String>,
34 pub version_code: Option<i64>,
36 pub version_name: Option<String>,
38}
39
40#[derive(Debug, Clone, Default, PartialEq, Eq)]
42#[non_exhaustive]
43pub struct PackModule {
44 pub name: Option<String>,
46 pub kind: Option<String>,
48 pub installation_free: Option<bool>,
50 pub delivery_with_install: Option<bool>,
52 pub main_ability: Option<String>,
54 pub device_types: Vec<String>,
56 pub api_compatible: Option<i64>,
58 pub api_target: Option<i64>,
60 pub api_release_type: Option<String>,
62 pub abilities: Vec<PackAbility>,
64 pub extension_abilities: Vec<PackExtension>,
66}
67
68#[derive(Debug, Clone, Default, PartialEq, Eq)]
70#[non_exhaustive]
71pub struct PackAbility {
72 pub name: Option<String>,
74 pub label: Option<String>,
76}
77
78#[derive(Debug, Clone, Default, PartialEq, Eq)]
80#[non_exhaustive]
81pub struct PackExtension {
82 pub name: Option<String>,
84 pub forms: Vec<PackForm>,
86}
87
88#[derive(Debug, Clone, Default, PartialEq, Eq)]
90#[non_exhaustive]
91pub struct PackForm {
92 pub name: Option<String>,
94 pub kind: Option<String>,
96 pub update_enabled: Option<bool>,
98 pub update_duration: Option<i64>,
100 pub scheduled_update_time: Option<String>,
102 pub default_dimension: Option<String>,
104 pub support_dimensions: Vec<String>,
106}
107
108#[derive(Debug, Clone, Default, PartialEq, Eq)]
110#[non_exhaustive]
111pub struct PackPackage {
112 pub name: Option<String>,
114 pub module_type: Option<String>,
116 pub device_types: Vec<String>,
118 pub delivery_with_install: Option<bool>,
120}
121
122impl PackInfo {
123 pub fn parse(bytes: &[u8]) -> Result<Self, HapError> {
129 let map = value::parse_object(bytes)?;
130 let mut findings = Findings::default();
131 let mut root = Obj::root(&map, &mut findings);
132 let summary = root.object("summary", |s| (s.object("app", read_app).unwrap_or_default(), s.objects("modules", read_module)));
133 let (app, modules) = summary.unwrap_or_default();
134 let packages = root.objects("packages", |p| PackPackage {
135 name: p.str("name"),
136 module_type: p.str("moduleType"),
137 device_types: p.strings("deviceType"),
138 delivery_with_install: p.bool("deliveryWithInstall"),
139 });
140 root.finish();
141 Ok(PackInfo { app, modules, packages, notes: findings.notes, unmodelled: findings.unmodelled, raw: Value::Object(map) })
142 }
143
144 pub fn raw(&self) -> &Value {
146 &self.raw
147 }
148
149 pub fn pretty(&self) -> String {
151 value::pretty(&self.raw)
152 }
153
154 pub fn summary_lines(&self) -> Vec<String> {
158 let mut out = vec![
159 Line::new("pack-app")
160 .word(self.app.bundle_name.as_deref().unwrap_or("-"))
161 .opt("bundleType", self.app.bundle_type.as_deref())
162 .num("versionCode", self.app.version_code)
163 .opt("versionName", self.app.version_name.as_deref())
164 .end(),
165 ];
166 for module in &self.modules {
167 let name = module.name.as_deref().unwrap_or("-");
168 out.push(
169 Line::new("pack-module")
170 .word(name)
171 .opt("type", module.kind.as_deref())
172 .opt("mainAbility", module.main_ability.as_deref())
173 .list("deviceTypes", &module.device_types)
174 .flag("installationFree", module.installation_free)
175 .flag("deliveryWithInstall", module.delivery_with_install)
176 .num("apiCompatible", module.api_compatible)
177 .num("apiTarget", module.api_target)
178 .opt("apiReleaseType", module.api_release_type.as_deref())
179 .end(),
180 );
181 out.extend(module.abilities.iter().map(|a| {
182 Line::new("pack-ability").word(name).word(a.name.as_deref().unwrap_or("-")).opt("label", a.label.as_deref()).end()
183 }));
184 for extension in &module.extension_abilities {
185 let extension_name = extension.name.as_deref().unwrap_or("-");
186 out.push(Line::new("pack-extension").word(name).word(extension_name).raw("forms", extension.forms.len()).end());
187 out.extend(extension.forms.iter().map(|f| {
188 Line::new("pack-form")
189 .word(name)
190 .word(extension_name)
191 .word(f.name.as_deref().unwrap_or("-"))
192 .opt("type", f.kind.as_deref())
193 .flag("updateEnabled", f.update_enabled)
194 .num("updateDuration", f.update_duration)
195 .opt_present("scheduledUpdateTime", f.scheduled_update_time.as_deref())
196 .opt("defaultDimension", f.default_dimension.as_deref())
197 .list("supportDimensions", &f.support_dimensions)
198 .end()
199 }));
200 }
201 }
202 out.extend(self.packages.iter().map(|p| {
203 Line::new("pack-package")
204 .word(p.name.as_deref().unwrap_or("-"))
205 .opt("moduleType", p.module_type.as_deref())
206 .list("deviceTypes", &p.device_types)
207 .flag("deliveryWithInstall", p.delivery_with_install)
208 .end()
209 }));
210 out.extend(self.unmodelled.iter().map(|path| Line::new("unmodelled").word(path).end()));
211 out.extend(self.notes.iter().map(|note| format!("note {note}")));
212 out
213 }
214
215 pub fn summary(&self) -> String {
217 crate::join_lines(&self.summary_lines())
218 }
219}
220
221fn read_app(o: &mut Obj<'_, '_>) -> PackApp {
222 let version = o.object("version", |v| (v.int("code"), v.str("name")));
223 let (version_code, version_name) = version.unwrap_or_default();
224 PackApp { bundle_name: o.str("bundleName"), bundle_type: o.str("bundleType"), version_code, version_name }
225}
226
227fn read_module(o: &mut Obj<'_, '_>) -> PackModule {
228 let distro =
229 o.object("distro", |d| (d.str("moduleName"), d.str("moduleType"), d.bool("installationFree"), d.bool("deliveryWithInstall")));
230 let (name, kind, installation_free, delivery_with_install) = distro.unwrap_or_default();
231 let api = o.object("apiVersion", |a| (a.int("compatible"), a.int("target"), a.str("releaseType")));
232 let (api_compatible, api_target, api_release_type) = api.unwrap_or_default();
233 PackModule {
234 name,
235 kind,
236 installation_free,
237 delivery_with_install,
238 main_ability: o.str("mainAbility"),
239 device_types: o.strings("deviceType"),
240 api_compatible,
241 api_target,
242 api_release_type,
243 abilities: o.objects("abilities", |a| PackAbility { name: a.str("name"), label: a.str("label") }),
244 extension_abilities: o.objects("extensionAbilities", |e| PackExtension {
245 name: e.str("name"),
246 forms: e.objects("forms", |f| PackForm {
247 name: f.str("name"),
248 kind: f.str("type"),
249 update_enabled: f.bool("updateEnabled"),
250 update_duration: f.int("updateDuration"),
251 scheduled_update_time: f.str("scheduledUpdateTime"),
252 default_dimension: f.str("defaultDimension"),
253 support_dimensions: f.strings("supportDimensions"),
254 }),
255 }),
256 }
257}