1use serde_json::Value;
5
6use crate::HapError;
7use crate::value::{self, Findings, Line, Note, Obj};
8
9#[derive(Debug, Clone, PartialEq)]
11pub struct PackInfo {
12 pub app: PackApp,
14 pub modules: Vec<PackModule>,
16 pub packages: Vec<PackPackage>,
18 pub notes: Vec<Note>,
20 pub unmodelled: Vec<String>,
22 raw: Value,
23}
24
25#[derive(Debug, Clone, Default, PartialEq, Eq)]
26pub struct PackApp {
27 pub bundle_name: Option<String>,
28 pub bundle_type: Option<String>,
29 pub version_code: Option<i64>,
31 pub version_name: Option<String>,
33}
34
35#[derive(Debug, Clone, Default, PartialEq, Eq)]
36pub struct PackModule {
37 pub name: Option<String>,
39 pub kind: Option<String>,
41 pub installation_free: Option<bool>,
42 pub delivery_with_install: Option<bool>,
43 pub main_ability: Option<String>,
44 pub device_types: Vec<String>,
45 pub api_compatible: Option<i64>,
47 pub api_target: Option<i64>,
49 pub api_release_type: Option<String>,
51 pub abilities: Vec<PackAbility>,
52 pub extension_abilities: Vec<PackExtension>,
53}
54
55#[derive(Debug, Clone, Default, PartialEq, Eq)]
56pub struct PackAbility {
57 pub name: Option<String>,
58 pub label: Option<String>,
59}
60
61#[derive(Debug, Clone, Default, PartialEq, Eq)]
62pub struct PackExtension {
63 pub name: Option<String>,
64 pub forms: Vec<PackForm>,
66}
67
68#[derive(Debug, Clone, Default, PartialEq, Eq)]
69pub struct PackForm {
70 pub name: Option<String>,
71 pub kind: Option<String>,
73 pub update_enabled: Option<bool>,
74 pub update_duration: Option<i64>,
75 pub scheduled_update_time: Option<String>,
77 pub default_dimension: Option<String>,
78 pub support_dimensions: Vec<String>,
79}
80
81#[derive(Debug, Clone, Default, PartialEq, Eq)]
82pub struct PackPackage {
83 pub name: Option<String>,
85 pub module_type: Option<String>,
86 pub device_types: Vec<String>,
87 pub delivery_with_install: Option<bool>,
88}
89
90impl PackInfo {
91 pub fn parse(bytes: &[u8]) -> Result<Self, HapError> {
97 let map = value::parse_object(bytes)?;
98 let mut findings = Findings::default();
99 let mut root = Obj::root(&map, &mut findings);
100 let summary = root.object("summary", |s| (s.object("app", read_app).unwrap_or_default(), s.objects("modules", read_module)));
101 let (app, modules) = summary.unwrap_or_default();
102 let packages = root.objects("packages", |p| PackPackage {
103 name: p.str("name"),
104 module_type: p.str("moduleType"),
105 device_types: p.strings("deviceType"),
106 delivery_with_install: p.bool("deliveryWithInstall"),
107 });
108 root.finish();
109 Ok(PackInfo { app, modules, packages, notes: findings.notes, unmodelled: findings.unmodelled, raw: Value::Object(map) })
110 }
111
112 pub fn raw(&self) -> &Value {
114 &self.raw
115 }
116
117 pub fn pretty(&self) -> String {
119 value::pretty(&self.raw)
120 }
121
122 pub fn summary_lines(&self) -> Vec<String> {
126 let mut out = vec![
127 Line::new("pack-app")
128 .word(self.app.bundle_name.as_deref().unwrap_or("-"))
129 .opt("bundleType", self.app.bundle_type.as_deref())
130 .num("versionCode", self.app.version_code)
131 .opt("versionName", self.app.version_name.as_deref())
132 .end(),
133 ];
134 for module in &self.modules {
135 let name = module.name.as_deref().unwrap_or("-");
136 out.push(
137 Line::new("pack-module")
138 .word(name)
139 .opt("type", module.kind.as_deref())
140 .opt("mainAbility", module.main_ability.as_deref())
141 .list("deviceTypes", &module.device_types)
142 .flag("installationFree", module.installation_free)
143 .flag("deliveryWithInstall", module.delivery_with_install)
144 .num("apiCompatible", module.api_compatible)
145 .num("apiTarget", module.api_target)
146 .opt("apiReleaseType", module.api_release_type.as_deref())
147 .end(),
148 );
149 out.extend(module.abilities.iter().map(|a| {
150 Line::new("pack-ability").word(name).word(a.name.as_deref().unwrap_or("-")).opt("label", a.label.as_deref()).end()
151 }));
152 for extension in &module.extension_abilities {
153 let extension_name = extension.name.as_deref().unwrap_or("-");
154 out.push(Line::new("pack-extension").word(name).word(extension_name).raw("forms", extension.forms.len()).end());
155 out.extend(extension.forms.iter().map(|f| {
156 Line::new("pack-form")
157 .word(name)
158 .word(extension_name)
159 .word(f.name.as_deref().unwrap_or("-"))
160 .opt("type", f.kind.as_deref())
161 .flag("updateEnabled", f.update_enabled)
162 .num("updateDuration", f.update_duration)
163 .opt_present("scheduledUpdateTime", f.scheduled_update_time.as_deref())
164 .opt("defaultDimension", f.default_dimension.as_deref())
165 .list("supportDimensions", &f.support_dimensions)
166 .end()
167 }));
168 }
169 }
170 out.extend(self.packages.iter().map(|p| {
171 Line::new("pack-package")
172 .word(p.name.as_deref().unwrap_or("-"))
173 .opt("moduleType", p.module_type.as_deref())
174 .list("deviceTypes", &p.device_types)
175 .flag("deliveryWithInstall", p.delivery_with_install)
176 .end()
177 }));
178 out.extend(self.unmodelled.iter().map(|path| Line::new("unmodelled").word(path).end()));
179 out.extend(self.notes.iter().map(|note| format!("note {note}")));
180 out
181 }
182
183 pub fn summary(&self) -> String {
185 crate::join_lines(&self.summary_lines())
186 }
187}
188
189fn read_app(o: &mut Obj<'_, '_>) -> PackApp {
190 let version = o.object("version", |v| (v.int("code"), v.str("name")));
191 let (version_code, version_name) = version.unwrap_or_default();
192 PackApp { bundle_name: o.str("bundleName"), bundle_type: o.str("bundleType"), version_code, version_name }
193}
194
195fn read_module(o: &mut Obj<'_, '_>) -> PackModule {
196 let distro =
197 o.object("distro", |d| (d.str("moduleName"), d.str("moduleType"), d.bool("installationFree"), d.bool("deliveryWithInstall")));
198 let (name, kind, installation_free, delivery_with_install) = distro.unwrap_or_default();
199 let api = o.object("apiVersion", |a| (a.int("compatible"), a.int("target"), a.str("releaseType")));
200 let (api_compatible, api_target, api_release_type) = api.unwrap_or_default();
201 PackModule {
202 name,
203 kind,
204 installation_free,
205 delivery_with_install,
206 main_ability: o.str("mainAbility"),
207 device_types: o.strings("deviceType"),
208 api_compatible,
209 api_target,
210 api_release_type,
211 abilities: o.objects("abilities", |a| PackAbility { name: a.str("name"), label: a.str("label") }),
212 extension_abilities: o.objects("extensionAbilities", |e| PackExtension {
213 name: e.str("name"),
214 forms: e.objects("forms", |f| PackForm {
215 name: f.str("name"),
216 kind: f.str("type"),
217 update_enabled: f.bool("updateEnabled"),
218 update_duration: f.int("updateDuration"),
219 scheduled_update_time: f.str("scheduledUpdateTime"),
220 default_dimension: f.str("defaultDimension"),
221 support_dimensions: f.strings("supportDimensions"),
222 }),
223 }),
224 }
225}