Skip to content

feat(cli): UTExportedTypeDeclarations support for file associations - #25

Open
tomerqodo wants to merge 2 commits into
coderabbit_combined_20260121_augment_sentry_coderabbit_1_base_featcli_utexportedtypedeclarations_support_for_file_associations_pr166from
coderabbit_combined_20260121_augment_sentry_coderabbit_1_head_featcli_utexportedtypedeclarations_support_for_file_associations_pr166
Open

feat(cli): UTExportedTypeDeclarations support for file associations#25
tomerqodo wants to merge 2 commits into
coderabbit_combined_20260121_augment_sentry_coderabbit_1_base_featcli_utexportedtypedeclarations_support_for_file_associations_pr166from
coderabbit_combined_20260121_augment_sentry_coderabbit_1_head_featcli_utexportedtypedeclarations_support_for_file_associations_pr166

Conversation

@tomerqodo

@tomerqodo tomerqodo commented Jan 22, 2026

Copy link
Copy Markdown

Benchmark PR from qodo-benchmark#166

Summary by CodeRabbit

  • New Features

    • Enhanced macOS file association support with content type mapping capabilities.
    • Added ability to declare custom exported file types on macOS with metadata configuration.
  • Documentation

    • Updated configuration schema with new file association options.
    • Added documentation and example demonstrating macOS file type declarations and content type configuration.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 22, 2026

Copy link
Copy Markdown

Walkthrough

This pull request introduces macOS file type definition support by adding two new file association properties: exported_type for UTExportedTypeDeclarations and content_types for LSItemContentTypes. Changes span configuration schemas, Rust structs, bundler implementation, and examples.

Changes

Cohort / File(s) Summary
Documentation & Changelog
.changes/file-association-*.md
Three new changelog entries announcing minor features: content-type support, exported-type CLI support, and new public API members on FileAssociation (exported_type and content_types).
Configuration Schemas
crates/tauri-cli/config.schema.json,
crates/tauri-schema-generator/schemas/config.schema.json
Added contentTypes (array of strings) and exportedType (ExportedFileAssociation or null) fields to FileAssociation schema. Introduced new ExportedFileAssociation definition with identifier (required) and conformsTo (optional array). Updated FileAssociation description.
Configuration Structures
crates/tauri-utils/src/config.rs
Extended public FileAssociation struct with content_types: Option<Vec<String>> and exported_type: Option<ExportedFileAssociation>. Added new public ExportedFileAssociation struct with identifier: String and conforms_to: Option<Vec<String>>.
macOS Bundler Implementation
crates/tauri-bundler/src/bundle/macos/app.rs
Builds UTExportedTypeDeclarations from exported file associations and inserts into Info.plist. Adds LSItemContentTypes to CFBundleDocumentTypes. Changes CFBundleTypeExtensions from conditional to only insert when no extensions present. Requires explicit name for file associations.
Example Updates
examples/file-associations/README.md,
examples/file-associations/src-tauri/Cargo.toml,
examples/file-associations/src-tauri/tauri.conf.json
Added documentation of file associations. Updated schema reference path. Enabled protocol-asset feature. Extended configuration with two new file associations (taurijson and taurid) demonstrating exportedType with conformsTo mappings to public.json and public.data.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Swift schemas bloom with types exported,
macOS files are now sorted,
Content types and declarations flow,
With bundler magic, Info.plist aglow!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature added: support for UTExportedTypeDeclarations in file associations, which is the primary focus across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/tauri-schema-generator/schemas/config.schema.json (1)

2423-2445: The ext requirement is unnecessarily restrictive, and a critical logic bug prevents proper extension handling.

macOS CFBundleDocumentTypes natively supports content-type-only declarations via LSItemContentTypes without requiring CFBundleTypeExtensions. However, the schema marks ext as required, preventing this use case.

Additionally, there is a logic bug in crates/tauri-bundler/src/bundle/macos/app.rs:328 where the condition is inverted: if association.ext.is_empty() should be if !association.ext.is_empty(). Currently, CFBundleTypeExtensions is only inserted when the array is empty, causing it to write nothing. This needs to be fixed regardless of the schema change.

To enable content-type-only associations for macOS, consider:

  1. Making ext optional in the schema (or use anyOf to require either ext or contentTypes/exportedType)
  2. Fixing the inverted condition in the bundler
  3. Handling the name field default gracefully when ext[0] is unavailable
🤖 Fix all issues with AI agents
In `@crates/tauri-bundler/src/bundle/macos/app.rs`:
- Around line 328-339: The current check uses if association.ext.is_empty()
which inserts CFBundleTypeExtensions when there are no extensions; invert the
condition to only call dict.insert for "CFBundleTypeExtensions" when
association.ext is non-empty (e.g., if !association.ext.is_empty()), leaving the
existing mapping logic (association.ext.iter().map(|ext|
ext.to_string().into()).collect()) unchanged so the actual extensions are
inserted into dict rather than an empty array.
- Around line 284-289: The code incorrectly uses association.content_types when
setting the "UTTypeConformsTo" plist key; instead use the
ExportedFileAssociation's conforms_to field. Update the dict.insert call that
creates the "UTTypeConformsTo" entry (currently mapping content_types ->
plist::Value::Array(...)) to read from association.conforms_to, mapping each
string into plist values as before; ensure you only set this key when
association.conforms_to.is_some() and preserve the same Array construction
logic.
🧹 Nitpick comments (2)
crates/tauri-cli/config.schema.json (1)

2436-2445: Consider using JSON field names in the description.

The text references Self::exported_type / ExportedFileAssociation::conforms_to; using exportedType / conformsTo would be clearer for JSON config users.

crates/tauri-bundler/src/bundle/macos/app.rs (1)

350-355: Consider returning an error instead of panicking.

Using .expect() will cause a panic at runtime if name is None. Since this function already returns crate::Result<()>, it would be more consistent and user-friendly to return a proper error.

♻️ Suggested improvement
             dict.insert(
               "CFBundleTypeName".into(),
               association
                 .name
-                .as_ref()
-                .expect("File association must have a name")
+                .as_ref()
+                .ok_or_else(|| crate::Error::GenericError("File association must have a name".into()))?
                 .to_string()
                 .into(),
             );
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b5ab6c and e1e8692.

📒 Files selected for processing (10)
  • .changes/file-association-content-type.md
  • .changes/file-association-exported-type-cli.md
  • .changes/file-association-exported-type.md
  • crates/tauri-bundler/src/bundle/macos/app.rs
  • crates/tauri-cli/config.schema.json
  • crates/tauri-schema-generator/schemas/config.schema.json
  • crates/tauri-utils/src/config.rs
  • examples/file-associations/README.md
  • examples/file-associations/src-tauri/Cargo.toml
  • examples/file-associations/src-tauri/tauri.conf.json
🔇 Additional comments (17)
.changes/file-association-exported-type-cli.md (1)

1-6: LGTM.

Changelog entry is clear and consistent with the new feature description.

examples/file-associations/src-tauri/Cargo.toml (1)

14-14: LGTM — feature flag matches example config.

.changes/file-association-exported-type.md (1)

1-5: Changelog entry looks good.

examples/file-associations/README.md (2)

11-13: LGTM — build command updated appropriately.


15-19: Docs update is clear and aligned with config changes.

.changes/file-association-content-type.md (1)

1-6: Changelog entry looks good.

examples/file-associations/src-tauri/tauri.conf.json (3)

2-2: LGTM — schema reference updated.


9-12: Looks good — asset protocol enabled in example config.


41-53: LGTM — exportedType associations are well-formed.

crates/tauri-cli/config.schema.json (3)

2161-2169: LGTM — schema description update is fine.


2485-2494: LGTM — exportedType schema looks correct.


2576-2599: Looks good — ExportedFileAssociation definition is clear.

crates/tauri-utils/src/config.rs (2)

1180-1185: LGTM!

The content_types field is well-documented, correctly typed as Option<Vec<String>>, and has the appropriate serde alias for kebab-case support.


1199-1217: LGTM!

The exported_type field and ExportedFileAssociation struct are well-defined with clear documentation explaining the macOS UTExportedTypeDeclarations mapping. The struct correctly includes deny_unknown_fields and proper serde aliases.

crates/tauri-schema-generator/schemas/config.schema.json (3)

2162-2162: Clearer file association wording.
The updated description makes the intent more explicit for config authors.


2485-2495: Good optional exportedType hook.
Making it nullable preserves backward compatibility while enabling the new export declaration.


2576-2599: ExportedFileAssociation schema looks consistent.
Required identifier plus optional conformsTo mirrors other config objects cleanly.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +284 to +289
if let Some(content_types) = &association.content_types {
dict.insert(
"UTTypeConformsTo".into(),
plist::Value::Array(content_types.iter().map(|s| s.clone().into()).collect()),
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: Wrong field used for UTTypeConformsTo.

association.content_types is meant for LSItemContentTypes in CFBundleDocumentTypes, not for UTTypeConformsTo in UTExportedTypeDeclarations. The ExportedFileAssociation struct has its own conforms_to field that should be used here.

🐛 Proposed fix
-          if let Some(content_types) = &association.content_types {
+          if let Some(conforms_to) = &exported_type.conforms_to {
             dict.insert(
               "UTTypeConformsTo".into(),
-              plist::Value::Array(content_types.iter().map(|s| s.clone().into()).collect()),
+              plist::Value::Array(conforms_to.iter().map(|s| s.clone().into()).collect()),
             );
           }
🤖 Prompt for AI Agents
In `@crates/tauri-bundler/src/bundle/macos/app.rs` around lines 284 - 289, The
code incorrectly uses association.content_types when setting the
"UTTypeConformsTo" plist key; instead use the ExportedFileAssociation's
conforms_to field. Update the dict.insert call that creates the
"UTTypeConformsTo" entry (currently mapping content_types ->
plist::Value::Array(...)) to read from association.conforms_to, mapping each
string into plist values as before; ensure you only set this key when
association.conforms_to.is_some() and preserve the same Array construction
logic.

Comment on lines +328 to +339
if association.ext.is_empty() {
dict.insert(
"CFBundleTypeExtensions".into(),
plist::Value::Array(
association
.ext
.iter()
.map(|ext| ext.to_string().into())
.collect(),
),
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: Inverted condition inserts empty array instead of actual extensions.

The condition if association.ext.is_empty() will insert CFBundleTypeExtensions only when there are no extensions, resulting in an empty array. The logic should be inverted to insert extensions when they exist.

🐛 Proposed fix
-            if association.ext.is_empty() {
+            if !association.ext.is_empty() {
               dict.insert(
                 "CFBundleTypeExtensions".into(),
                 plist::Value::Array(
                   association
                     .ext
                     .iter()
                     .map(|ext| ext.to_string().into())
                     .collect(),
                 ),
               );
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if association.ext.is_empty() {
dict.insert(
"CFBundleTypeExtensions".into(),
plist::Value::Array(
association
.ext
.iter()
.map(|ext| ext.to_string().into())
.collect(),
),
);
}
if !association.ext.is_empty() {
dict.insert(
"CFBundleTypeExtensions".into(),
plist::Value::Array(
association
.ext
.iter()
.map(|ext| ext.to_string().into())
.collect(),
),
);
}
🤖 Prompt for AI Agents
In `@crates/tauri-bundler/src/bundle/macos/app.rs` around lines 328 - 339, The
current check uses if association.ext.is_empty() which inserts
CFBundleTypeExtensions when there are no extensions; invert the condition to
only call dict.insert for "CFBundleTypeExtensions" when association.ext is
non-empty (e.g., if !association.ext.is_empty()), leaving the existing mapping
logic (association.ext.iter().map(|ext| ext.to_string().into()).collect())
unchanged so the actual extensions are inserted into dict rather than an empty
array.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant