1use std::fmt;
9
10use serde_json::Value;
11
12use crate::HapError;
13use crate::value::{self, Findings, Line, Note, Obj};
14
15#[derive(Debug, Clone, PartialEq)]
17#[non_exhaustive]
18pub struct ModuleJson {
19 pub app: App,
21 pub module: Module,
23 pub notes: Vec<Note>,
25 pub unmodelled: Vec<String>,
28 raw: Value,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub struct ApiVersion(pub i64);
38
39impl ApiVersion {
40 pub fn level(self) -> i64 {
42 self.0 % 1000
43 }
44}
45
46impl fmt::Display for ApiVersion {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 if self.level() == self.0 { write!(f, "{}", self.0) } else { write!(f, "{}({})", self.0, self.level()) }
50 }
51}
52
53#[derive(Debug, Clone, Default, PartialEq)]
55#[non_exhaustive]
56pub struct App {
57 pub bundle_name: Option<String>,
59 pub bundle_type: Option<String>,
61 pub version_code: Option<i64>,
63 pub version_name: Option<String>,
65 pub min_compatible_version_code: Option<i64>,
67 pub vendor: Option<String>,
69 pub debug: Option<bool>,
71 pub min_api_version: Option<ApiVersion>,
73 pub target_api_version: Option<ApiVersion>,
75 pub target_minor_api_version: Option<i64>,
77 pub target_patch_api_version: Option<i64>,
79 pub min_minor_api_version: Option<i64>,
81 pub min_patch_api_version: Option<i64>,
83 pub api_release_type: Option<String>,
85 pub compile_sdk_type: Option<String>,
87 pub compile_sdk_version: Option<String>,
89 pub build_mode: Option<String>,
91 pub label: Option<String>,
93 pub description: Option<String>,
95 pub environments: Vec<NameValue>,
97 pub multi_app_mode: Option<MultiAppMode>,
99 pub gwp_asan_enabled: Option<bool>,
101}
102
103#[derive(Debug, Clone, Default, PartialEq, Eq)]
105#[non_exhaustive]
106pub struct MultiAppMode {
107 pub kind: Option<String>,
109 pub max_count: Option<i64>,
111}
112
113#[derive(Debug, Clone, Default, PartialEq, Eq)]
115#[non_exhaustive]
116pub struct NameValue {
117 pub name: Option<String>,
119 pub value: Option<String>,
121}
122
123#[derive(Debug, Clone, Default, PartialEq, Eq)]
125#[non_exhaustive]
126pub struct Metadata {
127 pub name: Option<String>,
129 pub value: Option<String>,
131 pub resource: Option<String>,
133}
134
135#[derive(Debug, Clone, Default, PartialEq)]
137#[non_exhaustive]
138pub struct Module {
139 pub name: Option<String>,
141 pub kind: Option<String>,
143 pub package_name: Option<String>,
145 pub src_entry: Option<String>,
147 pub main_element: Option<String>,
149 pub description: Option<String>,
151 pub process: Option<String>,
153 pub device_types: Vec<String>,
155 pub virtual_machine: Option<String>,
157 pub compile_mode: Option<String>,
159 pub installation_free: Option<bool>,
161 pub delivery_with_install: Option<bool>,
163 pub compress_native_libs: Option<bool>,
165 pub isolation_mode: Option<String>,
167 pub pages: Option<String>,
169 pub router_map: Option<String>,
171 pub app_startup: Option<String>,
173 pub target_module_name: Option<String>,
175 pub target_priority: Option<i64>,
177 pub metadata: Vec<Metadata>,
179 pub dependencies: Vec<Dependency>,
181 pub proxy_data: Vec<ProxyData>,
183 pub query_schemes: Vec<String>,
185 pub abilities: Vec<Ability>,
187 pub extension_abilities: Vec<Ability>,
189 pub request_permissions: Vec<RequestPermission>,
191 pub define_permissions: Vec<DefinePermission>,
193 pub test_runner: Option<TestRunner>,
195 pub atomic_service: Option<AtomicService>,
197}
198
199#[derive(Debug, Clone, Default, PartialEq, Eq)]
201#[non_exhaustive]
202pub struct Dependency {
203 pub bundle_name: Option<String>,
205 pub module_name: Option<String>,
207 pub version_code: Option<i64>,
209}
210
211#[derive(Debug, Clone, Default, PartialEq, Eq)]
213#[non_exhaustive]
214pub struct ProxyData {
215 pub uri: Option<String>,
217 pub required_read_permission: Option<String>,
219 pub required_write_permission: Option<String>,
221 pub metadata: Option<Metadata>,
223}
224
225#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
227#[non_exhaustive]
228pub enum AbilityKind {
229 #[default]
231 Ability,
232 Extension,
234}
235
236impl AbilityKind {
237 pub fn as_str(self) -> &'static str {
239 match self {
240 AbilityKind::Ability => "ability",
241 AbilityKind::Extension => "extension",
242 }
243 }
244}
245
246#[derive(Debug, Clone, Default, PartialEq)]
248#[non_exhaustive]
249pub struct Ability {
250 pub kind: AbilityKind,
252 pub name: Option<String>,
254 pub src_entry: Option<String>,
257 pub extension_type: Option<String>,
259 pub exported: Option<bool>,
261 pub permissions: Vec<String>,
263 pub skills: Vec<Skill>,
265 pub launch_type: Option<String>,
267 pub process: Option<String>,
269 pub label: Option<String>,
271 pub description: Option<String>,
273 pub background_modes: Vec<String>,
275 pub continuable: Option<bool>,
277 pub metadata: Vec<Metadata>,
279 pub uri: Option<String>,
281 pub read_permission: Option<String>,
283 pub write_permission: Option<String>,
285 pub extension_process_mode: Option<String>,
287 pub data_group_ids: Vec<String>,
289 pub remove_mission_after_terminate: Option<bool>,
291 pub raw: Value,
293}
294
295impl Ability {
296 pub fn is_exported(&self) -> bool {
298 self.exported == Some(true)
299 }
300
301 pub fn deep_links(&self) -> Vec<String> {
304 let mut links: Vec<String> = Vec::new();
305 for link in self.skills.iter().flat_map(|s| &s.uris).flat_map(SkillUri::patterns) {
306 if !links.contains(&link) {
307 links.push(link);
308 }
309 }
310 links
311 }
312
313 fn display_name(&self) -> &str {
314 self.name.as_deref().unwrap_or("-")
315 }
316
317 fn line(&self) -> String {
318 let reason = if self.exported.is_some() { "explicit" } else { "default" };
319 let mut line = Line::new(self.kind.as_str()).word(self.display_name());
320 if self.kind == AbilityKind::Extension || self.extension_type.is_some() {
321 line = line.opt("type", self.extension_type.as_deref());
322 }
323 line = line
324 .raw("exported", format_args!("{} ({reason})", self.is_exported()))
325 .list("permissions", &self.permissions)
326 .raw("skills", self.skills.len())
327 .opt("srcEntry", self.src_entry.as_deref());
328 if self.uri.is_some() || self.read_permission.is_some() || self.write_permission.is_some() {
329 line = line
330 .opt("uri", self.uri.as_deref())
331 .opt("readPermission", self.read_permission.as_deref())
332 .opt("writePermission", self.write_permission.as_deref());
333 }
334 line.opt_present("launchType", self.launch_type.as_deref())
335 .opt_present("process", self.process.as_deref())
336 .opt_present("extensionProcessMode", self.extension_process_mode.as_deref())
337 .flag_present("continuable", self.continuable)
338 .list_present("backgroundModes", &self.background_modes)
339 .list_present("dataGroupIds", &self.data_group_ids)
340 .flag_present("removeMissionAfterTerminate", self.remove_mission_after_terminate)
341 .end()
342 }
343}
344
345#[derive(Debug, Clone, Default, PartialEq, Eq)]
347#[non_exhaustive]
348pub struct Skill {
349 pub actions: Vec<String>,
351 pub entities: Vec<String>,
353 pub uris: Vec<SkillUri>,
355 pub permissions: Vec<String>,
357 pub domain_verify: Option<bool>,
359}
360
361#[derive(Debug, Clone, Default, PartialEq, Eq)]
363#[non_exhaustive]
364pub struct SkillUri {
365 pub scheme: Option<String>,
367 pub host: Option<String>,
369 pub port: Option<String>,
371 pub path: Option<String>,
373 pub path_start_with: Option<String>,
375 pub path_regex: Option<String>,
377 pub mime_type: Option<String>,
379 pub utd: Option<String>,
381 pub max_file_supported: Option<i64>,
383 pub link_feature: Option<String>,
385}
386
387impl SkillUri {
388 pub fn patterns(&self) -> Vec<String> {
394 let Some(scheme) = &self.scheme else {
395 return Vec::new();
396 };
397 let paths: Vec<String> = [
398 self.path.as_ref().map(|p| p.trim_start_matches('/').to_owned()),
399 self.path_start_with.as_ref().map(|p| format!("{}*", p.trim_start_matches('/'))),
400 self.path_regex.as_ref().map(|p| format!("regex:{p}")),
401 ]
402 .into_iter()
403 .flatten()
404 .collect();
405 let authority = match (&self.host, &self.port) {
406 (Some(host), Some(port)) => format!("{host}:{port}"),
407 (Some(host), None) => host.clone(),
408 (None, Some(port)) => format!("*:{port}"),
409 (None, None) if paths.is_empty() => return vec![format!("{scheme}://*")],
410 (None, None) => "*".to_owned(),
411 };
412 if paths.is_empty() {
413 return vec![format!("{scheme}://{authority}")];
414 }
415 paths.iter().map(|path| format!("{scheme}://{authority}/{path}")).collect()
416 }
417
418 fn line(&self, owner: &str, skill: usize) -> String {
419 Line::new("uri")
420 .word(owner)
421 .raw("skill", skill)
422 .opt_present("scheme", self.scheme.as_deref())
423 .opt_present("host", self.host.as_deref())
424 .opt_present("port", self.port.as_deref())
425 .opt_present("path", self.path.as_deref())
426 .opt_present("pathStartWith", self.path_start_with.as_deref())
427 .opt_present("pathRegex", self.path_regex.as_deref())
428 .opt_present("type", self.mime_type.as_deref())
429 .opt_present("utd", self.utd.as_deref())
430 .opt_present("maxFileSupported", self.max_file_supported.map(|n| n.to_string()).as_deref())
431 .opt_present("linkFeature", self.link_feature.as_deref())
432 .end()
433 }
434}
435
436#[derive(Debug, Clone, Default, PartialEq, Eq)]
438#[non_exhaustive]
439pub struct RequestPermission {
440 pub name: Option<String>,
442 pub reason: Option<String>,
444 pub used_abilities: Vec<String>,
446 pub used_when: Option<String>,
448}
449
450#[derive(Debug, Clone, Default, PartialEq, Eq)]
452#[non_exhaustive]
453pub struct DefinePermission {
454 pub name: Option<String>,
456 pub grant_mode: Option<String>,
458 pub available_level: Option<String>,
460 pub provision_enable: Option<bool>,
462 pub distributed_scene_enable: Option<bool>,
464 pub label: Option<String>,
466 pub description: Option<String>,
468}
469
470#[derive(Debug, Clone, Default, PartialEq, Eq)]
472#[non_exhaustive]
473pub struct TestRunner {
474 pub name: Option<String>,
476 pub src_path: Option<String>,
478}
479
480#[derive(Debug, Clone, Default, PartialEq, Eq)]
482#[non_exhaustive]
483pub struct AtomicService {
484 pub preloads: Vec<String>,
486}
487
488const PRESENTATION_KEYS: &[&str] = &[
490 "icon",
491 "startWindow",
492 "startWindowIcon",
493 "startWindowBackground",
494 "orientation",
495 "supportWindowMode",
496 "maxWindowRatio",
497 "minWindowRatio",
498 "maxWindowWidth",
499 "minWindowWidth",
500 "maxWindowHeight",
501 "minWindowHeight",
502];
503
504impl ModuleJson {
505 pub fn parse(bytes: &[u8]) -> Result<Self, HapError> {
513 let map = value::parse_object(bytes)?;
514 let mut findings = Findings::default();
515 let mut root = Obj::root(&map, &mut findings);
516 let app = root.object("app", read_app).unwrap_or_default();
517 let module = root.object("module", read_module).unwrap_or_default();
518 root.finish();
519 Ok(ModuleJson { app, module, notes: findings.notes, unmodelled: findings.unmodelled, raw: Value::Object(map) })
520 }
521
522 pub fn raw(&self) -> &Value {
524 &self.raw
525 }
526
527 pub fn pretty(&self) -> String {
529 value::pretty(&self.raw)
530 }
531
532 pub fn components(&self) -> impl Iterator<Item = &Ability> {
534 self.module.abilities.iter().chain(&self.module.extension_abilities)
535 }
536
537 pub fn summary_lines(&self) -> Vec<String> {
549 let app = &self.app;
550 let module = &self.module;
551 let mut bundle = Line::new("bundle")
552 .word(app.bundle_name.as_deref().unwrap_or("-"))
553 .opt("bundleType", app.bundle_type.as_deref())
554 .num("versionCode", app.version_code)
555 .opt("versionName", app.version_name.as_deref())
556 .opt("vendor", app.vendor.as_deref())
557 .flag("debug", app.debug)
558 .num("minAPIVersion", app.min_api_version)
559 .num("targetAPIVersion", app.target_api_version)
560 .opt("apiReleaseType", app.api_release_type.as_deref())
561 .opt("compileSdkType", app.compile_sdk_type.as_deref())
562 .opt("compileSdkVersion", app.compile_sdk_version.as_deref())
563 .opt("buildMode", app.build_mode.as_deref());
564 if let Some(mode) = &app.multi_app_mode {
565 bundle = bundle.opt("multiAppMode", mode.kind.as_deref()).num("multiAppMaxCount", mode.max_count);
566 }
567 let mut out = vec![bundle.flag_present("GWPAsanEnabled", app.gwp_asan_enabled).end()];
568 out.extend(
569 app.environments.iter().map(|e| Line::new("app-env").opt("name", e.name.as_deref()).opt("value", e.value.as_deref()).end()),
570 );
571 out.push(
572 Line::new("module")
573 .word(module.name.as_deref().unwrap_or("-"))
574 .opt("type", module.kind.as_deref())
575 .opt("packageName", module.package_name.as_deref())
576 .opt("mainElement", module.main_element.as_deref())
577 .opt("srcEntry", module.src_entry.as_deref())
578 .list("deviceTypes", &module.device_types)
579 .opt("virtualMachine", module.virtual_machine.as_deref())
580 .opt("compileMode", module.compile_mode.as_deref())
581 .flag("installationFree", module.installation_free)
582 .flag("deliveryWithInstall", module.delivery_with_install)
583 .opt("process", module.process.as_deref())
584 .opt("pages", module.pages.as_deref())
585 .opt_present("routerMap", module.router_map.as_deref())
586 .opt_present("appStartup", module.app_startup.as_deref())
587 .flag_present("compressNativeLibs", module.compress_native_libs)
588 .opt_present("isolationMode", module.isolation_mode.as_deref())
589 .opt_present("targetModuleName", module.target_module_name.as_deref())
590 .opt_present("targetPriority", module.target_priority.map(|n| n.to_string()).as_deref())
591 .end(),
592 );
593 out.extend(module.metadata.iter().map(|m| metadata_line(Line::new("module-metadata"), m)));
594 out.extend(module.dependencies.iter().map(|d| {
595 Line::new("dependency")
596 .opt("bundleName", d.bundle_name.as_deref())
597 .opt("moduleName", d.module_name.as_deref())
598 .num("versionCode", d.version_code)
599 .end()
600 }));
601 out.extend(module.query_schemes.iter().map(|s| Line::new("query-scheme").word(s).end()));
602 out.extend(module.proxy_data.iter().map(|p| {
603 let line = Line::new("proxy-data")
604 .opt("uri", p.uri.as_deref())
605 .opt("requiredReadPermission", p.required_read_permission.as_deref())
606 .opt("requiredWritePermission", p.required_write_permission.as_deref());
607 match &p.metadata {
608 Some(m) => line.opt("metadataName", m.name.as_deref()).opt("metadataResource", m.resource.as_deref()).end(),
609 None => line.end(),
610 }
611 }));
612 out.extend(module.request_permissions.iter().map(|p| {
613 Line::new("request-permission")
614 .word(p.name.as_deref().unwrap_or("-"))
615 .opt("reason", p.reason.as_deref())
616 .opt("when", p.used_when.as_deref())
617 .list("abilities", &p.used_abilities)
618 .end()
619 }));
620 out.extend(module.define_permissions.iter().map(|p| {
621 Line::new("define-permission")
622 .word(p.name.as_deref().unwrap_or("-"))
623 .opt("grantMode", p.grant_mode.as_deref())
624 .opt("availableLevel", p.available_level.as_deref())
625 .flag("provisionEnable", p.provision_enable)
626 .flag("distributedSceneEnable", p.distributed_scene_enable)
627 .end()
628 }));
629 if let Some(t) = &module.test_runner {
630 out.push(Line::new("test-runner").opt("name", t.name.as_deref()).opt("srcPath", t.src_path.as_deref()).end());
631 }
632 if let Some(a) = &module.atomic_service {
633 out.push(Line::new("atomic-service").list("preloads", &a.preloads).end());
634 }
635 for component in self.components() {
636 let name = component.display_name();
637 out.push(component.line());
638 for (i, skill) in component.skills.iter().enumerate() {
639 let number = i + 1;
640 out.push(
641 Line::new("skill")
642 .word(name)
643 .raw("skill", number)
644 .list("actions", &skill.actions)
645 .list("entities", &skill.entities)
646 .raw("uris", skill.uris.len())
647 .list("permissions", &skill.permissions)
648 .flag_present("domainVerify", skill.domain_verify)
649 .end(),
650 );
651 out.extend(skill.uris.iter().map(|u| u.line(name, number)));
652 }
653 out.extend(component.deep_links().iter().map(|l| Line::new("deeplink").word(name).word(l).end()));
654 out.extend(component.metadata.iter().map(|m| metadata_line(Line::new("metadata").word(name), m)));
655 }
656 out.extend(self.unmodelled.iter().map(|path| Line::new("unmodelled").word(path).end()));
657 out.extend(self.notes.iter().map(|note| format!("note {note}")));
658 out
659 }
660
661 pub fn summary(&self) -> String {
663 crate::join_lines(&self.summary_lines())
664 }
665}
666
667fn metadata_line(line: Line, m: &Metadata) -> String {
668 line.opt("name", m.name.as_deref()).opt_present("value", m.value.as_deref()).opt_present("resource", m.resource.as_deref()).end()
669}
670
671fn read_app(o: &mut Obj<'_, '_>) -> App {
672 o.ignore(&["icon"]);
673 App {
674 bundle_name: o.str("bundleName"),
675 bundle_type: o.str("bundleType"),
676 version_code: o.int("versionCode"),
677 version_name: o.str("versionName"),
678 min_compatible_version_code: o.int("minCompatibleVersionCode"),
679 vendor: o.str("vendor"),
680 debug: o.bool("debug"),
681 min_api_version: o.int("minAPIVersion").map(ApiVersion),
682 target_api_version: o.int("targetAPIVersion").map(ApiVersion),
683 target_minor_api_version: o.int("targetMinorAPIVersion"),
684 target_patch_api_version: o.int("targetPatchAPIVersion"),
685 min_minor_api_version: o.int("minMinorAPIVersion"),
686 min_patch_api_version: o.int("minPatchAPIVersion"),
687 api_release_type: o.str("apiReleaseType"),
688 compile_sdk_type: o.str("compileSdkType"),
689 compile_sdk_version: o.text("compileSdkVersion"),
690 build_mode: o.str("buildMode"),
691 label: o.str("label"),
692 description: o.str("description"),
693 environments: o.objects("appEnvironments", |e| NameValue { name: e.str("name"), value: e.text("value") }),
694 multi_app_mode: o.object("multiAppMode", |m| MultiAppMode { kind: m.str("multiAppModeType"), max_count: m.int("maxCount") }),
695 gwp_asan_enabled: o.bool("GWPAsanEnabled"),
696 }
697}
698
699fn read_metadata(o: &mut Obj<'_, '_>) -> Metadata {
700 Metadata { name: o.str("name"), value: o.text("value"), resource: o.str("resource") }
701}
702
703fn read_module(o: &mut Obj<'_, '_>) -> Module {
704 Module {
705 name: o.str("name"),
706 kind: o.str("type"),
707 package_name: o.str("packageName"),
708 src_entry: o.str("srcEntry"),
709 main_element: o.str("mainElement"),
710 description: o.str("description"),
711 process: o.str("process"),
712 device_types: o.strings("deviceTypes"),
713 virtual_machine: o.str("virtualMachine"),
714 compile_mode: o.str("compileMode"),
715 installation_free: o.bool("installationFree"),
716 delivery_with_install: o.bool("deliveryWithInstall"),
717 compress_native_libs: o.bool("compressNativeLibs"),
718 isolation_mode: o.str("isolationMode"),
719 pages: o.str("pages"),
720 router_map: o.str("routerMap"),
721 app_startup: o.str("appStartup"),
722 target_module_name: o.str("targetModuleName"),
723 target_priority: o.int("targetPriority"),
724 metadata: o.objects("metadata", read_metadata),
725 dependencies: o.objects("dependencies", |d| Dependency {
726 bundle_name: d.str("bundleName"),
727 module_name: d.str("moduleName"),
728 version_code: d.int("versionCode"),
729 }),
730 proxy_data: o.objects("proxyData", |p| ProxyData {
731 uri: p.str("uri"),
732 required_read_permission: p.str("requiredReadPermission"),
733 required_write_permission: p.str("requiredWritePermission"),
734 metadata: p.object("metadata", read_metadata),
735 }),
736 query_schemes: o.strings("querySchemes"),
737 abilities: o.objects("abilities", |a| read_ability(a, AbilityKind::Ability)),
738 extension_abilities: o.objects("extensionAbilities", |a| read_ability(a, AbilityKind::Extension)),
739 request_permissions: o.objects("requestPermissions", |p| {
740 let scene = p.object("usedScene", |s| (s.strings("abilities"), s.str("when")));
741 let (used_abilities, used_when) = scene.unwrap_or_default();
742 RequestPermission { name: p.str("name"), reason: p.str("reason"), used_abilities, used_when }
743 }),
744 define_permissions: o.objects("definePermissions", |p| DefinePermission {
745 name: p.str("name"),
746 grant_mode: p.str("grantMode"),
747 available_level: p.str("availableLevel"),
748 provision_enable: p.bool("provisionEnable"),
749 distributed_scene_enable: p.bool("distributedSceneEnable"),
750 label: p.str("label"),
751 description: p.str("description"),
752 }),
753 test_runner: o.object("testRunner", |t| TestRunner { name: t.str("name"), src_path: t.str("srcPath") }),
754 atomic_service: o.object("atomicService", |a| AtomicService {
755 preloads: a.objects("preloads", |p| p.str("moduleName")).into_iter().flatten().collect(),
756 }),
757 }
758}
759
760fn read_ability(o: &mut Obj<'_, '_>, kind: AbilityKind) -> Ability {
761 o.ignore(PRESENTATION_KEYS);
762 Ability {
763 kind,
764 name: o.str("name"),
765 src_entry: o.str("srcEntry"),
766 extension_type: o.str("type"),
767 exported: o.bool("exported"),
768 permissions: o.strings("permissions"),
769 skills: o.objects("skills", read_skill),
770 launch_type: o.str("launchType"),
771 process: o.str("process"),
772 label: o.str("label"),
773 description: o.str("description"),
774 background_modes: o.strings("backgroundModes"),
775 continuable: o.bool("continuable"),
776 metadata: o.objects("metadata", read_metadata),
777 uri: o.str("uri"),
778 read_permission: o.str("readPermission"),
779 write_permission: o.str("writePermission"),
780 extension_process_mode: o.str("extensionProcessMode"),
781 data_group_ids: o.strings("dataGroupIds"),
782 remove_mission_after_terminate: o.bool("removeMissionAfterTerminate"),
783 raw: o.to_value(),
784 }
785}
786
787fn read_skill(o: &mut Obj<'_, '_>) -> Skill {
788 Skill {
789 actions: o.strings("actions"),
790 entities: o.strings("entities"),
791 uris: o.objects("uris", |u| SkillUri {
792 scheme: u.str("scheme"),
793 host: u.str("host"),
794 port: u.text("port"),
795 path: u.str("path"),
796 path_start_with: u.str("pathStartWith"),
797 path_regex: u.str("pathRegex"),
798 mime_type: u.str("type"),
799 utd: u.str("utd"),
800 max_file_supported: u.int("maxFileSupported"),
801 link_feature: u.str("linkFeature"),
802 }),
803 permissions: o.strings("permissions"),
804 domain_verify: o.bool("domainVerify"),
805 }
806}