MobDev.Plugin.Validator (mob_dev v0.5.14)

Copy Markdown View Source

Validates plugin manifests, in two stages (see MOB_PLUGINS.md).

Single-plugin (validate_plugin/3, behind mix mob.validate_plugin): a plugin author's pre-publish check — required fields, referenced files exist, mob_version satisfied by the installed mob, plus advisory warnings.

Cross-plugin (cross_validate/1, run by mob_dev when activating): the collision checks that only make sense across the set of activated plugins — no two may claim the same component atom, screen route, or migration namespace.

Every result is %{errors: [...], warnings: [...]}. Errors fail loud; warnings are advisory. Both stages are pure given their inputs (the only I/O is File.exists?/1 for path checks, isolated in validate_plugin/3).

Summary

Functions

Activation-time capability check across every activated plugin.

Cross-plugin collision validation across the activated set.

Runs activated_capability_errors/1 and raises a Mix.raise/1 (with a user-readable bullet list) when any plugin's source references a capability not in its manifest. No-op when every plugin is clean.

Collects the file paths a manifest references, relative to the plugin root.

Verifies every <uses-permission android:name="X"/> declared in AndroidManifest.xml fragments under the plugin's tree appears in manifest.android.permissions.

Single-plugin validation, run from the plugin's own project directory.

Verifies every import X in the plugin's Swift sources resolves to either a base iOS framework or one declared in manifest.ios.frameworks.

Types

result()

@type result() :: %{errors: [String.t()], warnings: [String.t()]}

Functions

activated_capability_errors(plugins)

@spec activated_capability_errors([{Path.t(), map() | nil}]) :: [String.t()]

Activation-time capability check across every activated plugin.

plugins is the MobDev.Plugin.activated/0 shape — a list of {plugin_dir, manifest} pairs. For each plugin, runs validate_swift_imports/2 and validate_android_permissions/2 and returns the flattened error list, each entry prefixed with the plugin's :name so the user can tell which plugin tripped.

Empty list means every activated plugin's source matches its manifest's declared capability surface. The build hooks this into iOS + Android builds via raise_on_capability_drift!/1, which raises a Mix.raise/1 with the full list when any drift is found.

cross_validate(plugins)

@spec cross_validate([{atom(), map() | nil}]) :: result()

Cross-plugin collision validation across the activated set.

plugins is a list of {name, manifest} for the activated plugins (tier-0 no-manifest plugins, i.e. manifest == nil, contribute nothing and are ignored).

raise_on_capability_drift!(plugins)

@spec raise_on_capability_drift!([{Path.t(), map() | nil}]) :: :ok

Runs activated_capability_errors/1 and raises a Mix.raise/1 (with a user-readable bullet list) when any plugin's source references a capability not in its manifest. No-op when every plugin is clean.

Lives in the validator (not NativeBuild) so the iOS-sim and iOS-device build paths can both call it as a one-liner, and so it stays unit-testable.

Also runs the Phase 2 signature gate (MobDev.Plugin.SignatureGate.raise_on_signature_drift!/1) at the top — fails fast on a tampered or untrusted plugin before any capability analysis runs. The unsigned-plugin banner is printed afterwards so it surfaces on every successful invocation.

referenced_paths(manifest)

@spec referenced_paths(map() | nil) :: [String.t()]

Collects the file paths a manifest references, relative to the plugin root.

Pure. Covers the concrete file declarations (nifs.native_dir, android.bridge_kt, android.jni_source, ios.swift_files). Component view_module/composable are type/function names, not paths, so they are not included here.

validate_android_permissions(manifest, plugin_dir)

@spec validate_android_permissions(map() | nil, Path.t()) :: [String.t()]

Verifies every <uses-permission android:name="X"/> declared in AndroidManifest.xml fragments under the plugin's tree appears in manifest.android.permissions.

Scope (deliberate): scans priv/native/android/**/*.xml for <uses-permission/> entries — declarations the plugin author explicitly wrote. Does not attempt to infer permissions from Kotlin/Java API usage (the static-analysis rabbit hole MOB_PLUGIN_SECURITY.md warns against). Returns [] when the plugin ships no AndroidManifest fragment.

validate_plugin(manifest, plugin_dir, installed_mob_version \\ nil)

@spec validate_plugin(map() | nil, Path.t(), String.t() | nil) :: result()

Single-plugin validation, run from the plugin's own project directory.

installed_mob_version is the version of :mob resolved in the plugin's deps (a string), or nil to skip the compatibility check.

validate_swift_imports(manifest, plugin_dir)

@spec validate_swift_imports(map() | nil, Path.t()) :: [String.t()]

Verifies every import X in the plugin's Swift sources resolves to either a base iOS framework or one declared in manifest.ios.frameworks.

See MOB_PLUGIN_SECURITY.md (Layer 2 — capability enforcement at compile time): the manifest is the contract; the plugin's source cannot reach for a framework that isn't manifest-declared. Catches drift at validate time rather than at link time, where the error points at the linker invocation and not at the manifest that produced it.

Returns a list of error strings (empty when the manifest passes). Skips plugins with no ios.swift_files. Files referenced by the manifest but missing on disk are flagged by add_path_errors/3, not here — this check only opens files that exist.