Skip to content

feat(cli): UTExportedTypeDeclarations support for file associations#38

Open
tomerqodo wants to merge 2 commits intocoderabbit_full_base_featcli_utexportedtypedeclarations_support_for_file_associations_pr12from
coderabbit_full_head_featcli_utexportedtypedeclarations_support_for_file_associations_pr12
Open

feat(cli): UTExportedTypeDeclarations support for file associations#38
tomerqodo wants to merge 2 commits intocoderabbit_full_base_featcli_utexportedtypedeclarations_support_for_file_associations_pr12from
coderabbit_full_head_featcli_utexportedtypedeclarations_support_for_file_associations_pr12

Conversation

@tomerqodo
Copy link
Copy Markdown

@tomerqodo tomerqodo commented Jan 29, 2026

Benchmark PR from agentic-review-benchmarks#12

Summary by CodeRabbit

Release Notes

  • New Features

    • File associations now support defining custom exported types on macOS, enabling proper system-level file type integration
    • Added content type support for file associations on macOS, improving system file handling and recognition
  • Documentation

    • Updated documentation and examples for enhanced file association configuration

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 29, 2026

Walkthrough

This PR introduces support for defining custom file types and content types in file associations on macOS. New configuration fields (contentTypes, exportedType) and a public ExportedFileAssociation type enable mapping to macOS's UTExportedTypeDeclarations and LSItemContentTypes properties. The bundler generates corresponding Info.plist entries conditionally based on configuration.

Changes

Cohort / File(s) Summary
Changelog Files
.changes/file-association-*.md
Documentation of new features for file association content types and exported type declarations across tauri-cli and tauri-utils packages.
Configuration Schemas
crates/tauri-cli/config.schema.json, crates/tauri-schema-generator/schemas/config.schema.json
Added contentTypes (array of strings) and exportedType fields to FileAssociation; introduced new public ExportedFileAssociation definition with identifier and conformsTo fields. Updated FileAssociation description from "File associations to application." to "File types to associate with the application."
Core Config Types
crates/tauri-utils/src/config.rs
Introduced public ExportedFileAssociation struct with identifier and conforms_to fields. Extended FileAssociation with optional content_types and exported_type fields. Updated BundleConfig documentation.
Bundler Implementation
crates/tauri-bundler/src/bundle/macos/app.rs
Added logic to generate UTExportedTypeDeclarations in Info.plist from file associations. Conditional insertion of CFBundleTypeExtensions, addition of LSItemContentTypes to CFBundleDocumentTypes. Changed CFBundleTypeName fallback to explicit expect for required file association name.
Example Updates
examples/file-associations/README.md, examples/file-associations/src-tauri/Cargo.toml, examples/file-associations/src-tauri/tauri.conf.json
Added "Associations" documentation section. Enabled "protocol-asset" feature in tauri dependency. Added two new file associations with exportedType declarations for .taurijson and .taurid extensions. Updated schema reference in config.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 New types hop into place, exportedType finds its space,
Content types declared with care, macOS compatibility's fair,
File associations now shine bright, configuration done just right! ✨

🚥 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 clearly and specifically describes the main feature addition: UTExportedTypeDeclarations support for file associations, which aligns with the core changes 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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@crates/tauri-bundler/src/bundle/macos/app.rs`:
- Around line 328-339: The condition around inserting CFBundleTypeExtensions is
inverted: currently it inserts an array when association.ext is empty and skips
when non-empty; change the guard to only insert when association.ext is
non-empty (i.e., replace if association.ext.is_empty() with if
!association.ext.is_empty()) so the dict.insert call for
"CFBundleTypeExtensions" uses the real extensions from
association.ext.iter().map(...).collect(); keep the existing dict.insert and
mapping logic but flip the boolean check to correctly register file extensions.
- Around line 349-355: The code currently calls association.name.expect(...)
which will panic for valid configs because name is optional; update the logic
around CFBundleTypeName construction in app.rs to avoid hard panics by using a
fallback (e.g., use the first extension from association.extensions when name is
None) or return a structured error; locate the block that sets
"CFBundleTypeName" and replace the expect usage on association.name with safe
handling (map/if-let or similar) that either uses the first extension as a
string fallback or propagates an error from the surrounding function instead of
panicking.
- Around line 281-289: Replace the current wiring of UTTypeConformsTo (which
uses association.content_types) to instead use the exported type's conforms_to
field when present: in the block around dict insertion for "UTTypeConformsTo"
(near where association.description and association.content_types are handled),
check the exported_type.conforms_to and insert those values into dict as an
Array for "UTTypeConformsTo"; only fall back to association.content_types if
exported_type.conforms_to is absent, so you don't duplicate LSItemContentTypes
semantics.

In `@crates/tauri-schema-generator/schemas/config.schema.json`:
- Around line 2436-2444: Update the description text for the "contentTypes"
schema to use the config-surface camelCase field names instead of Rust-style
snake_case: replace references to Self::exported_type with exportedType and
ExportedFileAssociation::conforms_to with ExportedFileAssociation::conformsTo
(or just conformsTo when referring to the config field) so the docs match the
JSON config names and avoid confusion.
🧹 Nitpick comments (1)
examples/file-associations/README.md (1)

11-13: Consider updating the build command.

The --features tauri/protocol-asset flag is now redundant since the feature is enabled by default in Cargo.toml. Consider simplifying to:

-   cargo build --features tauri/protocol-asset
+   cargo build
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1e45c3 and 951c364.

📒 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 (16)
examples/file-associations/src-tauri/Cargo.toml (1)

14-14: LGTM! Feature addition aligns with file associations functionality.

The protocol-asset feature is correctly enabled for the file associations example. Note that the README (line 12) still instructs users to pass --features tauri/protocol-asset, which is now redundant since the feature is enabled by default in Cargo.toml.

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

1180-1185: LGTM! Well-documented content_types field.

The field is properly documented with macOS-specific context, includes the appropriate serde alias for JSON kebab-case compatibility, and correctly references related types.


1199-1217: LGTM! Clean implementation of exported type declarations.

The exported_type field and ExportedFileAssociation struct are well-designed:

  • Required identifier field for the UTTypeIdentifier.
  • Optional conforms_to with helpful examples in documentation.
  • Proper serde attributes following existing patterns in the codebase.

1383-1384: LGTM! Improved documentation clarity.

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

1-5: LGTM! Changeset accurately documents the feature addition.

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

1-6: LGTM! Changeset correctly documents CLI feature for content type support.

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

1-6: LGTM! Changeset correctly documents CLI feature for exported type declarations.

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

14-19: LGTM! Clear documentation of the new file associations.

The new section clearly explains the supported file types and the macOS-specific exportedType handling for custom extensions.

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

2-53: LGTM for the updated example config.

The new exportedType examples and assetProtocol enablement align with the intended macOS file-association enhancements.

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

2162-2169: Clearer file-association description.

Nice improvement to the wording here.


2485-2494: Schema shape for exportedType looks good.


2576-2599: ExportedFileAssociation definition is well-scoped.

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

2161-2162: LGTM!

The updated description "File types to associate with the application." is clearer and more accurate than the previous wording.


2436-2445: LGTM!

The contentTypes property is correctly defined as an optional array of strings, properly mapping to macOS's LSItemContentTypes. The schema structure follows the established patterns in this file.


2484-2495: LGTM!

The exportedType property correctly uses the anyOf pattern with a $ref to ExportedFileAssociation and null, which is consistent with other optional object properties in this schema. The description clearly explains the macOS UTExportedTypeDeclarations mapping.


2576-2599: LGTM!

The ExportedFileAssociation definition is well-structured:

  • identifier is correctly marked as required (mandatory for UTTypeIdentifier)
  • conformsTo is properly optional with valid example UTI types in the description
  • additionalProperties: false ensures strict schema validation

The definition correctly models macOS's UTExportedTypeDeclarations entry structure.

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

Comment on lines +281 to +289
if let Some(description) = &association.description {
dict.insert("UTTypeDescription".into(), description.clone().into());
}
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 | 🟠 Major

Use the exported type’s conforms_to for UTTypeConformsTo.

UTTypeConformsTo should reflect the exported type definition; wiring it to association.content_types ignores exported_type.conforms_to and duplicates LSItemContentTypes semantics.

🛠️ Suggested fix
-          if let Some(content_types) = &association.content_types {
-            dict.insert(
-              "UTTypeConformsTo".into(),
-              plist::Value::Array(content_types.iter().map(|s| s.clone().into()).collect()),
-            );
-          }
+          if let Some(conforms_to) = &exported_type.conforms_to {
+            dict.insert(
+              "UTTypeConformsTo".into(),
+              plist::Value::Array(conforms_to.iter().map(|s| s.clone().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 let Some(description) = &association.description {
dict.insert("UTTypeDescription".into(), description.clone().into());
}
if let Some(content_types) = &association.content_types {
dict.insert(
"UTTypeConformsTo".into(),
plist::Value::Array(content_types.iter().map(|s| s.clone().into()).collect()),
);
}
if let Some(description) = &association.description {
dict.insert("UTTypeDescription".into(), description.clone().into());
}
if let Some(conforms_to) = &exported_type.conforms_to {
dict.insert(
"UTTypeConformsTo".into(),
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 281 - 289, Replace
the current wiring of UTTypeConformsTo (which uses association.content_types) to
instead use the exported type's conforms_to field when present: in the block
around dict insertion for "UTTypeConformsTo" (near where association.description
and association.content_types are handled), check the exported_type.conforms_to
and insert those values into dict as an Array for "UTTypeConformsTo"; only fall
back to association.content_types if exported_type.conforms_to is absent, so you
don't duplicate LSItemContentTypes semantics.

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 | 🟠 Major

CFBundleTypeExtensions insertion condition is inverted.

The current guard inserts an empty array when ext is empty and omits real extensions when present, which can break file association registration.

🛠️ Suggested 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
condition around inserting CFBundleTypeExtensions is inverted: currently it
inserts an array when association.ext is empty and skips when non-empty; change
the guard to only insert when association.ext is non-empty (i.e., replace if
association.ext.is_empty() with if !association.ext.is_empty()) so the
dict.insert call for "CFBundleTypeExtensions" uses the real extensions from
association.ext.iter().map(...).collect(); keep the existing dict.insert and
mapping logic but flip the boolean check to correctly register file extensions.

Comment on lines 349 to 355
"CFBundleTypeName".into(),
association
.name
.as_ref()
.unwrap_or(&association.ext[0].0)
.expect("File association must have a name")
.to_string()
.into(),
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 | 🟠 Major

Avoid a hard panic when name is omitted.

name is optional in the schema; expect will panic for valid configs. Consider falling back to the first extension (previous behavior) or returning a structured error.

🛠️ Suggested fix (fallback to extension)
-              association
-                .name
-                .as_ref()
-                .expect("File association must have a name")
-                .to_string()
-                .into(),
+              association
+                .name
+                .as_deref()
+                .or_else(|| association.ext.first().map(|s| s.as_str()))
+                .unwrap_or("unknown")
+                .to_string()
+                .into(),
🤖 Prompt for AI Agents
In `@crates/tauri-bundler/src/bundle/macos/app.rs` around lines 349 - 355, The
code currently calls association.name.expect(...) which will panic for valid
configs because name is optional; update the logic around CFBundleTypeName
construction in app.rs to avoid hard panics by using a fallback (e.g., use the
first extension from association.extensions when name is None) or return a
structured error; locate the block that sets "CFBundleTypeName" and replace the
expect usage on association.name with safe handling (map/if-let or similar) that
either uses the first extension as a string fallback or propagates an error from
the surrounding function instead of panicking.

Comment on lines +2436 to +2444
"contentTypes": {
"description": "Declare support to a file with the given content type. Maps to `LSItemContentTypes` on macOS.\n\n This allows supporting any file format declared by another application that conforms to this type.\n Declaration of new types can be done with [`Self::exported_type`] and linking to certain content types are done via [`ExportedFileAssociation::conforms_to`].",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
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 | 🟡 Minor

Align docs with JSON field names.

The description references exported_type / conforms_to (Rust-style). Consider using exportedType / conformsTo to match the config surface and avoid confusion.

🤖 Prompt for AI Agents
In `@crates/tauri-schema-generator/schemas/config.schema.json` around lines 2436 -
2444, Update the description text for the "contentTypes" schema to use the
config-surface camelCase field names instead of Rust-style snake_case: replace
references to Self::exported_type with exportedType and
ExportedFileAssociation::conforms_to with ExportedFileAssociation::conformsTo
(or just conformsTo when referring to the config field) so the docs match the
JSON config names and avoid confusion.

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