Skip to main content

asc_hap/
module.rs

1//! `module.json`: the compiled module descriptor at the root of a HAP/HSP.
2//!
3//! This is the packed form, not the developer's `module.json5`: the `app`
4//! object is merged in from `app.json5`, resource references are spelled
5//! `$string:name` with a numeric sibling (`labelId`), and build facts such
6//! as `virtualMachine` and `compileSdkVersion` are added.
7
8use std::fmt;
9
10use serde_json::Value;
11
12use crate::HapError;
13use crate::value::{self, Findings, Line, Note, Obj};
14
15/// A parsed `module.json`.
16#[derive(Debug, Clone, PartialEq)]
17pub struct ModuleJson {
18    pub app: App,
19    pub module: Module,
20    /// Fields that were present but wrongly typed, and so skipped.
21    pub notes: Vec<Note>,
22    /// JSON paths of fields this model has no place for; read them from
23    /// [`ModuleJson::raw`].
24    pub unmodelled: Vec<String>,
25    raw: Value,
26}
27
28/// `minAPIVersion` / `targetAPIVersion` as written.
29///
30/// Old packages write the API level itself (`9`); newer ones write an
31/// eight-digit number whose last three digits are the level
32/// (`50001013` is level 13).
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub struct ApiVersion(pub i64);
35
36impl ApiVersion {
37    /// The API level, as `pack.info` states it in `apiVersion.compatible`.
38    pub fn level(self) -> i64 {
39        self.0 % 1000
40    }
41}
42
43/// `50001013(13)`, or just `9` when the value is the level.
44impl fmt::Display for ApiVersion {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        if self.level() == self.0 { write!(f, "{}", self.0) } else { write!(f, "{}({})", self.0, self.level()) }
47    }
48}
49
50/// The `app` object: bundle-wide facts.
51#[derive(Debug, Clone, Default, PartialEq)]
52pub struct App {
53    pub bundle_name: Option<String>,
54    /// `app`, `atomicService`, `shared`, `appService`…
55    pub bundle_type: Option<String>,
56    pub version_code: Option<i64>,
57    pub version_name: Option<String>,
58    pub min_compatible_version_code: Option<i64>,
59    pub vendor: Option<String>,
60    pub debug: Option<bool>,
61    pub min_api_version: Option<ApiVersion>,
62    pub target_api_version: Option<ApiVersion>,
63    pub target_minor_api_version: Option<i64>,
64    pub target_patch_api_version: Option<i64>,
65    pub min_minor_api_version: Option<i64>,
66    pub min_patch_api_version: Option<i64>,
67    pub api_release_type: Option<String>,
68    pub compile_sdk_type: Option<String>,
69    pub compile_sdk_version: Option<String>,
70    /// `debug` or `release`.
71    pub build_mode: Option<String>,
72    pub label: Option<String>,
73    pub description: Option<String>,
74    pub environments: Vec<NameValue>,
75    /// `multiAppMode`: whether the bundle may be installed several times.
76    pub multi_app_mode: Option<MultiAppMode>,
77    /// `GWPAsanEnabled`: sampled heap checking in release builds.
78    pub gwp_asan_enabled: Option<bool>,
79}
80
81/// `multiAppMode` of the `app` object.
82#[derive(Debug, Clone, Default, PartialEq, Eq)]
83pub struct MultiAppMode {
84    /// `multiAppModeType`: `appClone` or `multiInstance`.
85    pub kind: Option<String>,
86    pub max_count: Option<i64>,
87}
88
89/// A `name` / `value` pair (`appEnvironments`).
90#[derive(Debug, Clone, Default, PartialEq, Eq)]
91pub struct NameValue {
92    pub name: Option<String>,
93    pub value: Option<String>,
94}
95
96/// A `metadata` entry: a literal `value`, a `$profile:` `resource`, or both.
97#[derive(Debug, Clone, Default, PartialEq, Eq)]
98pub struct Metadata {
99    pub name: Option<String>,
100    pub value: Option<String>,
101    pub resource: Option<String>,
102}
103
104/// The `module` object.
105#[derive(Debug, Clone, Default, PartialEq)]
106pub struct Module {
107    pub name: Option<String>,
108    /// `entry`, `feature`, `har` or `shared`.
109    pub kind: Option<String>,
110    /// The module's package name: its key in `pkgContextInfo.json`.
111    pub package_name: Option<String>,
112    /// Source of the module's `AbilityStage`.
113    pub src_entry: Option<String>,
114    pub main_element: Option<String>,
115    pub description: Option<String>,
116    pub process: Option<String>,
117    pub device_types: Vec<String>,
118    pub virtual_machine: Option<String>,
119    pub compile_mode: Option<String>,
120    pub installation_free: Option<bool>,
121    pub delivery_with_install: Option<bool>,
122    pub compress_native_libs: Option<bool>,
123    pub isolation_mode: Option<String>,
124    /// `$profile:` reference to the page list.
125    pub pages: Option<String>,
126    /// `$profile:` reference to the navigation route table.
127    pub router_map: Option<String>,
128    /// `$profile:` reference to the startup task configuration.
129    pub app_startup: Option<String>,
130    pub target_module_name: Option<String>,
131    pub target_priority: Option<i64>,
132    pub metadata: Vec<Metadata>,
133    pub dependencies: Vec<Dependency>,
134    pub proxy_data: Vec<ProxyData>,
135    /// URL schemes the module may probe with `canOpenLink`.
136    pub query_schemes: Vec<String>,
137    pub abilities: Vec<Ability>,
138    pub extension_abilities: Vec<Ability>,
139    pub request_permissions: Vec<RequestPermission>,
140    pub define_permissions: Vec<DefinePermission>,
141    pub test_runner: Option<TestRunner>,
142    pub atomic_service: Option<AtomicService>,
143}
144
145/// A shared package (HSP) the module depends on.
146#[derive(Debug, Clone, Default, PartialEq, Eq)]
147pub struct Dependency {
148    pub bundle_name: Option<String>,
149    pub module_name: Option<String>,
150    pub version_code: Option<i64>,
151}
152
153/// A `proxyData` entry: data shared through the data-share proxy.
154#[derive(Debug, Clone, Default, PartialEq, Eq)]
155pub struct ProxyData {
156    pub uri: Option<String>,
157    pub required_read_permission: Option<String>,
158    pub required_write_permission: Option<String>,
159    pub metadata: Option<Metadata>,
160}
161
162/// Which array of the module an [`Ability`] comes from.
163#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
164pub enum AbilityKind {
165    /// `abilities`: a UIAbility.
166    #[default]
167    Ability,
168    /// `extensionAbilities`: form, service, data share, backup…
169    Extension,
170}
171
172impl AbilityKind {
173    /// The record word of the summary line: `ability` or `extension`.
174    pub fn as_str(self) -> &'static str {
175        match self {
176            AbilityKind::Ability => "ability",
177            AbilityKind::Extension => "extension",
178        }
179    }
180}
181
182/// An entry of `abilities` or `extensionAbilities`.
183#[derive(Debug, Clone, Default, PartialEq)]
184pub struct Ability {
185    pub kind: AbilityKind,
186    pub name: Option<String>,
187    /// Source file relative to the module (`./ets/entryability/EntryAbility.ets`);
188    /// see [`ModuleJson::record_names`].
189    pub src_entry: Option<String>,
190    /// `type` of an extension ability (`form`, `service`, `dataShare`…).
191    pub extension_type: Option<String>,
192    /// As written; `None` when the field is absent.
193    pub exported: Option<bool>,
194    /// Permissions a caller must hold.
195    pub permissions: Vec<String>,
196    pub skills: Vec<Skill>,
197    pub launch_type: Option<String>,
198    pub process: Option<String>,
199    pub label: Option<String>,
200    pub description: Option<String>,
201    pub background_modes: Vec<String>,
202    pub continuable: Option<bool>,
203    pub metadata: Vec<Metadata>,
204    /// Data URI served by a data extension.
205    pub uri: Option<String>,
206    pub read_permission: Option<String>,
207    pub write_permission: Option<String>,
208    pub extension_process_mode: Option<String>,
209    pub data_group_ids: Vec<String>,
210    pub remove_mission_after_terminate: Option<bool>,
211    /// The entry as written, for fields not modelled here.
212    pub raw: Value,
213}
214
215impl Ability {
216    /// The `exported` flag in effect: an absent field means not exported.
217    pub fn is_exported(&self) -> bool {
218        self.exported == Some(true)
219    }
220
221    /// URL patterns of every skill URI that names a scheme, in document
222    /// order without repeats.
223    pub fn deep_links(&self) -> Vec<String> {
224        let mut links: Vec<String> = Vec::new();
225        for link in self.skills.iter().flat_map(|s| &s.uris).flat_map(SkillUri::patterns) {
226            if !links.contains(&link) {
227                links.push(link);
228            }
229        }
230        links
231    }
232
233    fn display_name(&self) -> &str {
234        self.name.as_deref().unwrap_or("-")
235    }
236
237    fn line(&self) -> String {
238        let reason = if self.exported.is_some() { "explicit" } else { "default" };
239        let mut line = Line::new(self.kind.as_str()).word(self.display_name());
240        if self.kind == AbilityKind::Extension || self.extension_type.is_some() {
241            line = line.opt("type", self.extension_type.as_deref());
242        }
243        line = line
244            .raw("exported", format_args!("{} ({reason})", self.is_exported()))
245            .list("permissions", &self.permissions)
246            .raw("skills", self.skills.len())
247            .opt("srcEntry", self.src_entry.as_deref());
248        if self.uri.is_some() || self.read_permission.is_some() || self.write_permission.is_some() {
249            line = line
250                .opt("uri", self.uri.as_deref())
251                .opt("readPermission", self.read_permission.as_deref())
252                .opt("writePermission", self.write_permission.as_deref());
253        }
254        line.opt_present("launchType", self.launch_type.as_deref())
255            .opt_present("process", self.process.as_deref())
256            .opt_present("extensionProcessMode", self.extension_process_mode.as_deref())
257            .flag_present("continuable", self.continuable)
258            .list_present("backgroundModes", &self.background_modes)
259            .list_present("dataGroupIds", &self.data_group_ids)
260            .flag_present("removeMissionAfterTerminate", self.remove_mission_after_terminate)
261            .end()
262    }
263}
264
265/// One entry of `skills`: the implicit wants an ability accepts.
266#[derive(Debug, Clone, Default, PartialEq, Eq)]
267pub struct Skill {
268    pub actions: Vec<String>,
269    pub entities: Vec<String>,
270    pub uris: Vec<SkillUri>,
271    pub permissions: Vec<String>,
272    /// App Linking: the `https` hosts are verified against the bundle.
273    pub domain_verify: Option<bool>,
274}
275
276/// One entry of a skill's `uris`.
277#[derive(Debug, Clone, Default, PartialEq, Eq)]
278pub struct SkillUri {
279    pub scheme: Option<String>,
280    pub host: Option<String>,
281    pub port: Option<String>,
282    pub path: Option<String>,
283    pub path_start_with: Option<String>,
284    pub path_regex: Option<String>,
285    /// MIME type of the data.
286    pub mime_type: Option<String>,
287    /// Uniform type descriptor of the data.
288    pub utd: Option<String>,
289    pub max_file_supported: Option<i64>,
290    pub link_feature: Option<String>,
291}
292
293impl SkillUri {
294    /// The URL patterns this entry spells, one per path form present:
295    /// `scheme://host:port/path`, `…/prefix*` for `pathStartWith` and
296    /// `…/regex:<re>` for `pathRegex`. A missing host prints as `*`, and
297    /// so does everything after a bare scheme (`myapp://*`). Empty
298    /// when the entry names no scheme.
299    pub fn patterns(&self) -> Vec<String> {
300        let Some(scheme) = &self.scheme else {
301            return Vec::new();
302        };
303        let paths: Vec<String> = [
304            self.path.as_ref().map(|p| p.trim_start_matches('/').to_owned()),
305            self.path_start_with.as_ref().map(|p| format!("{}*", p.trim_start_matches('/'))),
306            self.path_regex.as_ref().map(|p| format!("regex:{p}")),
307        ]
308        .into_iter()
309        .flatten()
310        .collect();
311        let authority = match (&self.host, &self.port) {
312            (Some(host), Some(port)) => format!("{host}:{port}"),
313            (Some(host), None) => host.clone(),
314            (None, Some(port)) => format!("*:{port}"),
315            (None, None) if paths.is_empty() => return vec![format!("{scheme}://*")],
316            (None, None) => "*".to_owned(),
317        };
318        if paths.is_empty() {
319            return vec![format!("{scheme}://{authority}")];
320        }
321        paths.iter().map(|path| format!("{scheme}://{authority}/{path}")).collect()
322    }
323
324    fn line(&self, owner: &str, skill: usize) -> String {
325        Line::new("uri")
326            .word(owner)
327            .raw("skill", skill)
328            .opt_present("scheme", self.scheme.as_deref())
329            .opt_present("host", self.host.as_deref())
330            .opt_present("port", self.port.as_deref())
331            .opt_present("path", self.path.as_deref())
332            .opt_present("pathStartWith", self.path_start_with.as_deref())
333            .opt_present("pathRegex", self.path_regex.as_deref())
334            .opt_present("type", self.mime_type.as_deref())
335            .opt_present("utd", self.utd.as_deref())
336            .opt_present("maxFileSupported", self.max_file_supported.map(|n| n.to_string()).as_deref())
337            .opt_present("linkFeature", self.link_feature.as_deref())
338            .end()
339    }
340}
341
342/// An entry of `requestPermissions`.
343#[derive(Debug, Clone, Default, PartialEq, Eq)]
344pub struct RequestPermission {
345    pub name: Option<String>,
346    pub reason: Option<String>,
347    /// `usedScene.abilities`.
348    pub used_abilities: Vec<String>,
349    /// `usedScene.when`: `inuse` or `always`.
350    pub used_when: Option<String>,
351}
352
353/// An entry of `definePermissions`.
354#[derive(Debug, Clone, Default, PartialEq, Eq)]
355pub struct DefinePermission {
356    pub name: Option<String>,
357    /// `system_grant` or `user_grant`.
358    pub grant_mode: Option<String>,
359    /// `normal`, `system_basic` or `system_core`.
360    pub available_level: Option<String>,
361    pub provision_enable: Option<bool>,
362    pub distributed_scene_enable: Option<bool>,
363    pub label: Option<String>,
364    pub description: Option<String>,
365}
366
367/// `testRunner`.
368#[derive(Debug, Clone, Default, PartialEq, Eq)]
369pub struct TestRunner {
370    pub name: Option<String>,
371    pub src_path: Option<String>,
372}
373
374/// `atomicService`.
375#[derive(Debug, Clone, Default, PartialEq, Eq)]
376pub struct AtomicService {
377    /// `preloads[].moduleName`.
378    pub preloads: Vec<String>,
379}
380
381/// Keys that only affect how a component looks.
382const PRESENTATION_KEYS: &[&str] = &[
383    "icon",
384    "startWindow",
385    "startWindowIcon",
386    "startWindowBackground",
387    "orientation",
388    "supportWindowMode",
389    "maxWindowRatio",
390    "minWindowRatio",
391    "maxWindowWidth",
392    "minWindowWidth",
393    "maxWindowHeight",
394    "minWindowHeight",
395];
396
397impl ModuleJson {
398    /// Read a `module.json`.
399    ///
400    /// # Errors
401    ///
402    /// Only when `bytes` is not JSON or its root is not an object. Missing,
403    /// unknown and wrongly typed fields never fail the document: see
404    /// [`ModuleJson::notes`] and [`ModuleJson::unmodelled`].
405    pub fn parse(bytes: &[u8]) -> Result<Self, HapError> {
406        let map = value::parse_object(bytes)?;
407        let mut findings = Findings::default();
408        let mut root = Obj::root(&map, &mut findings);
409        let app = root.object("app", read_app).unwrap_or_default();
410        let module = root.object("module", read_module).unwrap_or_default();
411        root.finish();
412        Ok(ModuleJson { app, module, notes: findings.notes, unmodelled: findings.unmodelled, raw: Value::Object(map) })
413    }
414
415    /// The whole document as parsed.
416    pub fn raw(&self) -> &Value {
417        &self.raw
418    }
419
420    /// The document pretty-printed, for `getmanifest`.
421    pub fn pretty(&self) -> String {
422        value::pretty(&self.raw)
423    }
424
425    /// Abilities, then extension abilities, in document order.
426    pub fn components(&self) -> impl Iterator<Item = &Ability> {
427        self.module.abilities.iter().chain(&self.module.extension_abilities)
428    }
429
430    /// One fact per line, the first word naming the record: `bundle`,
431    /// `app-env`, `module`, `module-metadata`, `dependency`, `query-scheme`,
432    /// `proxy-data`, `request-permission`, `define-permission`,
433    /// `test-runner`, `atomic-service`, then per component `ability` or
434    /// `extension` followed by its `skill`, `uri`, `deeplink` and
435    /// `metadata` lines (each carrying the component name), and finally
436    /// `unmodelled` and `note`.
437    ///
438    /// Absent values print as `-`; values with whitespace print as JSON
439    /// string literals. `exported=` is always printed, with `(explicit)`
440    /// or `(default)` telling whether the document said so.
441    pub fn summary_lines(&self) -> Vec<String> {
442        let app = &self.app;
443        let module = &self.module;
444        let mut bundle = Line::new("bundle")
445            .word(app.bundle_name.as_deref().unwrap_or("-"))
446            .opt("bundleType", app.bundle_type.as_deref())
447            .num("versionCode", app.version_code)
448            .opt("versionName", app.version_name.as_deref())
449            .opt("vendor", app.vendor.as_deref())
450            .flag("debug", app.debug)
451            .num("minAPIVersion", app.min_api_version)
452            .num("targetAPIVersion", app.target_api_version)
453            .opt("apiReleaseType", app.api_release_type.as_deref())
454            .opt("compileSdkType", app.compile_sdk_type.as_deref())
455            .opt("compileSdkVersion", app.compile_sdk_version.as_deref())
456            .opt("buildMode", app.build_mode.as_deref());
457        if let Some(mode) = &app.multi_app_mode {
458            bundle = bundle.opt("multiAppMode", mode.kind.as_deref()).num("multiAppMaxCount", mode.max_count);
459        }
460        let mut out = vec![bundle.flag_present("GWPAsanEnabled", app.gwp_asan_enabled).end()];
461        out.extend(
462            app.environments.iter().map(|e| Line::new("app-env").opt("name", e.name.as_deref()).opt("value", e.value.as_deref()).end()),
463        );
464        out.push(
465            Line::new("module")
466                .word(module.name.as_deref().unwrap_or("-"))
467                .opt("type", module.kind.as_deref())
468                .opt("packageName", module.package_name.as_deref())
469                .opt("mainElement", module.main_element.as_deref())
470                .opt("srcEntry", module.src_entry.as_deref())
471                .list("deviceTypes", &module.device_types)
472                .opt("virtualMachine", module.virtual_machine.as_deref())
473                .opt("compileMode", module.compile_mode.as_deref())
474                .flag("installationFree", module.installation_free)
475                .flag("deliveryWithInstall", module.delivery_with_install)
476                .opt("process", module.process.as_deref())
477                .opt("pages", module.pages.as_deref())
478                .opt_present("routerMap", module.router_map.as_deref())
479                .opt_present("appStartup", module.app_startup.as_deref())
480                .flag_present("compressNativeLibs", module.compress_native_libs)
481                .opt_present("isolationMode", module.isolation_mode.as_deref())
482                .opt_present("targetModuleName", module.target_module_name.as_deref())
483                .opt_present("targetPriority", module.target_priority.map(|n| n.to_string()).as_deref())
484                .end(),
485        );
486        out.extend(module.metadata.iter().map(|m| metadata_line(Line::new("module-metadata"), m)));
487        out.extend(module.dependencies.iter().map(|d| {
488            Line::new("dependency")
489                .opt("bundleName", d.bundle_name.as_deref())
490                .opt("moduleName", d.module_name.as_deref())
491                .num("versionCode", d.version_code)
492                .end()
493        }));
494        out.extend(module.query_schemes.iter().map(|s| Line::new("query-scheme").word(s).end()));
495        out.extend(module.proxy_data.iter().map(|p| {
496            let line = Line::new("proxy-data")
497                .opt("uri", p.uri.as_deref())
498                .opt("requiredReadPermission", p.required_read_permission.as_deref())
499                .opt("requiredWritePermission", p.required_write_permission.as_deref());
500            match &p.metadata {
501                Some(m) => line.opt("metadataName", m.name.as_deref()).opt("metadataResource", m.resource.as_deref()).end(),
502                None => line.end(),
503            }
504        }));
505        out.extend(module.request_permissions.iter().map(|p| {
506            Line::new("request-permission")
507                .word(p.name.as_deref().unwrap_or("-"))
508                .opt("reason", p.reason.as_deref())
509                .opt("when", p.used_when.as_deref())
510                .list("abilities", &p.used_abilities)
511                .end()
512        }));
513        out.extend(module.define_permissions.iter().map(|p| {
514            Line::new("define-permission")
515                .word(p.name.as_deref().unwrap_or("-"))
516                .opt("grantMode", p.grant_mode.as_deref())
517                .opt("availableLevel", p.available_level.as_deref())
518                .flag("provisionEnable", p.provision_enable)
519                .flag("distributedSceneEnable", p.distributed_scene_enable)
520                .end()
521        }));
522        if let Some(t) = &module.test_runner {
523            out.push(Line::new("test-runner").opt("name", t.name.as_deref()).opt("srcPath", t.src_path.as_deref()).end());
524        }
525        if let Some(a) = &module.atomic_service {
526            out.push(Line::new("atomic-service").list("preloads", &a.preloads).end());
527        }
528        for component in self.components() {
529            let name = component.display_name();
530            out.push(component.line());
531            for (i, skill) in component.skills.iter().enumerate() {
532                let number = i + 1;
533                out.push(
534                    Line::new("skill")
535                        .word(name)
536                        .raw("skill", number)
537                        .list("actions", &skill.actions)
538                        .list("entities", &skill.entities)
539                        .raw("uris", skill.uris.len())
540                        .list("permissions", &skill.permissions)
541                        .flag_present("domainVerify", skill.domain_verify)
542                        .end(),
543                );
544                out.extend(skill.uris.iter().map(|u| u.line(name, number)));
545            }
546            out.extend(component.deep_links().iter().map(|l| Line::new("deeplink").word(name).word(l).end()));
547            out.extend(component.metadata.iter().map(|m| metadata_line(Line::new("metadata").word(name), m)));
548        }
549        out.extend(self.unmodelled.iter().map(|path| Line::new("unmodelled").word(path).end()));
550        out.extend(self.notes.iter().map(|note| format!("note {note}")));
551        out
552    }
553
554    /// [`ModuleJson::summary_lines`] joined, each line newline-terminated.
555    pub fn summary(&self) -> String {
556        crate::join_lines(&self.summary_lines())
557    }
558}
559
560fn metadata_line(line: Line, m: &Metadata) -> String {
561    line.opt("name", m.name.as_deref()).opt_present("value", m.value.as_deref()).opt_present("resource", m.resource.as_deref()).end()
562}
563
564fn read_app(o: &mut Obj<'_, '_>) -> App {
565    o.ignore(&["icon"]);
566    App {
567        bundle_name: o.str("bundleName"),
568        bundle_type: o.str("bundleType"),
569        version_code: o.int("versionCode"),
570        version_name: o.str("versionName"),
571        min_compatible_version_code: o.int("minCompatibleVersionCode"),
572        vendor: o.str("vendor"),
573        debug: o.bool("debug"),
574        min_api_version: o.int("minAPIVersion").map(ApiVersion),
575        target_api_version: o.int("targetAPIVersion").map(ApiVersion),
576        target_minor_api_version: o.int("targetMinorAPIVersion"),
577        target_patch_api_version: o.int("targetPatchAPIVersion"),
578        min_minor_api_version: o.int("minMinorAPIVersion"),
579        min_patch_api_version: o.int("minPatchAPIVersion"),
580        api_release_type: o.str("apiReleaseType"),
581        compile_sdk_type: o.str("compileSdkType"),
582        compile_sdk_version: o.text("compileSdkVersion"),
583        build_mode: o.str("buildMode"),
584        label: o.str("label"),
585        description: o.str("description"),
586        environments: o.objects("appEnvironments", |e| NameValue { name: e.str("name"), value: e.text("value") }),
587        multi_app_mode: o.object("multiAppMode", |m| MultiAppMode { kind: m.str("multiAppModeType"), max_count: m.int("maxCount") }),
588        gwp_asan_enabled: o.bool("GWPAsanEnabled"),
589    }
590}
591
592fn read_metadata(o: &mut Obj<'_, '_>) -> Metadata {
593    Metadata { name: o.str("name"), value: o.text("value"), resource: o.str("resource") }
594}
595
596fn read_module(o: &mut Obj<'_, '_>) -> Module {
597    Module {
598        name: o.str("name"),
599        kind: o.str("type"),
600        package_name: o.str("packageName"),
601        src_entry: o.str("srcEntry"),
602        main_element: o.str("mainElement"),
603        description: o.str("description"),
604        process: o.str("process"),
605        device_types: o.strings("deviceTypes"),
606        virtual_machine: o.str("virtualMachine"),
607        compile_mode: o.str("compileMode"),
608        installation_free: o.bool("installationFree"),
609        delivery_with_install: o.bool("deliveryWithInstall"),
610        compress_native_libs: o.bool("compressNativeLibs"),
611        isolation_mode: o.str("isolationMode"),
612        pages: o.str("pages"),
613        router_map: o.str("routerMap"),
614        app_startup: o.str("appStartup"),
615        target_module_name: o.str("targetModuleName"),
616        target_priority: o.int("targetPriority"),
617        metadata: o.objects("metadata", read_metadata),
618        dependencies: o.objects("dependencies", |d| Dependency {
619            bundle_name: d.str("bundleName"),
620            module_name: d.str("moduleName"),
621            version_code: d.int("versionCode"),
622        }),
623        proxy_data: o.objects("proxyData", |p| ProxyData {
624            uri: p.str("uri"),
625            required_read_permission: p.str("requiredReadPermission"),
626            required_write_permission: p.str("requiredWritePermission"),
627            metadata: p.object("metadata", read_metadata),
628        }),
629        query_schemes: o.strings("querySchemes"),
630        abilities: o.objects("abilities", |a| read_ability(a, AbilityKind::Ability)),
631        extension_abilities: o.objects("extensionAbilities", |a| read_ability(a, AbilityKind::Extension)),
632        request_permissions: o.objects("requestPermissions", |p| {
633            let scene = p.object("usedScene", |s| (s.strings("abilities"), s.str("when")));
634            let (used_abilities, used_when) = scene.unwrap_or_default();
635            RequestPermission { name: p.str("name"), reason: p.str("reason"), used_abilities, used_when }
636        }),
637        define_permissions: o.objects("definePermissions", |p| DefinePermission {
638            name: p.str("name"),
639            grant_mode: p.str("grantMode"),
640            available_level: p.str("availableLevel"),
641            provision_enable: p.bool("provisionEnable"),
642            distributed_scene_enable: p.bool("distributedSceneEnable"),
643            label: p.str("label"),
644            description: p.str("description"),
645        }),
646        test_runner: o.object("testRunner", |t| TestRunner { name: t.str("name"), src_path: t.str("srcPath") }),
647        atomic_service: o.object("atomicService", |a| AtomicService {
648            preloads: a.objects("preloads", |p| p.str("moduleName")).into_iter().flatten().collect(),
649        }),
650    }
651}
652
653fn read_ability(o: &mut Obj<'_, '_>, kind: AbilityKind) -> Ability {
654    o.ignore(PRESENTATION_KEYS);
655    Ability {
656        kind,
657        name: o.str("name"),
658        src_entry: o.str("srcEntry"),
659        extension_type: o.str("type"),
660        exported: o.bool("exported"),
661        permissions: o.strings("permissions"),
662        skills: o.objects("skills", read_skill),
663        launch_type: o.str("launchType"),
664        process: o.str("process"),
665        label: o.str("label"),
666        description: o.str("description"),
667        background_modes: o.strings("backgroundModes"),
668        continuable: o.bool("continuable"),
669        metadata: o.objects("metadata", read_metadata),
670        uri: o.str("uri"),
671        read_permission: o.str("readPermission"),
672        write_permission: o.str("writePermission"),
673        extension_process_mode: o.str("extensionProcessMode"),
674        data_group_ids: o.strings("dataGroupIds"),
675        remove_mission_after_terminate: o.bool("removeMissionAfterTerminate"),
676        raw: o.to_value(),
677    }
678}
679
680fn read_skill(o: &mut Obj<'_, '_>) -> Skill {
681    Skill {
682        actions: o.strings("actions"),
683        entities: o.strings("entities"),
684        uris: o.objects("uris", |u| SkillUri {
685            scheme: u.str("scheme"),
686            host: u.str("host"),
687            port: u.text("port"),
688            path: u.str("path"),
689            path_start_with: u.str("pathStartWith"),
690            path_regex: u.str("pathRegex"),
691            mime_type: u.str("type"),
692            utd: u.str("utd"),
693            max_file_supported: u.int("maxFileSupported"),
694            link_feature: u.str("linkFeature"),
695        }),
696        permissions: o.strings("permissions"),
697        domain_verify: o.bool("domainVerify"),
698    }
699}