# How the Macro Conversion Service Handles Different Document Formats

> Discover how the Macro conversion service expertly handles diverse document formats. Learn about file extension detection, LibreOffice filter selection, and secure sandboxed conversions.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: how-to-guide
- Published: 2026-08-18

---

**The Macro conversion service handles different document formats by detecting file extensions, mapping them to a FileType enum, selecting appropriate LibreOffice filters via `get_lok_filter_from_file_types`, and executing conversions in sandboxed child processes with a 30-second timeout.**

The conversion service in the macro-inc/macro repository provides a robust, format-agnostic pipeline for transforming documents between types like PDF, DOCX, PPTX, and ODT. By leveraging LibreOffice's universal conversion engine through a Rust-based architecture, the service abstracts format complexities behind a simple S3-object-based API. This article explores how the conversion service handles different document formats by examining the source code in [`services/convert_service/src/process/convert.rs`](https://github.com/macro-inc/macro/blob/main/services/convert_service/src/process/convert.rs).

## File Type Detection and the FileType Enum

The conversion process begins with extracting file extensions from S3 object keys. In [`services/convert_service/src/process/convert.rs`](https://github.com/macro-inc/macro/blob/main/services/convert_service/src/process/convert.rs) (lines 38-51), the service parses the `from_key` and `to_key` fields from the `ConvertQueueMessage` to identify the source and target formats. The code splits the filename at the extension and passes it to `FileType::from_str`, which returns a strongly-typed enum variant representing formats like PDF, DOCX, PPTX, ODT, and others.

```rust
let from_file_type = FileType::from_str(
    req.from_key.split('.').next_back().unwrap()
)?;
let to_file_type = FileType::from_str(
    req.to_key.split('.').next_back().unwrap()
)?;

```

## Mapping Formats to LibreOffice Filters

Once the `FileType` values are determined, the service translates these abstract types into concrete LibreOfficeKit (LOK) filter strings. On line 52 of [`convert.rs`](https://github.com/macro-inc/macro/blob/main/convert.rs), the code calls `get_lok_filter_from_file_types`, a helper function that accepts references to both the source and target `FileType` values. This function returns the specific filter name that instructs LibreOffice how to render the source document and which export filter to apply for the destination format, enabling seamless conversion between any supported pair without code modifications.

```rust
let filter = get_lok_filter_from_file_types(&from_file_type, &to_file_type)?;
convert(job_id, &req, &s3_client).await?;

```

## Sandboxed Conversion Execution

The actual conversion occurs inside a forked child process to ensure security and stability. Lines 75-84 in [`convert.rs`](https://github.com/macro-inc/macro/blob/main/convert.rs) handle the process spawning, where the service executes the LibreOffice headless binary (`lok`) via the `rs_libreoffice_bindings` crate. Inside this sandboxed environment, the service creates an `Office` object using `Office::new`, loads the source document via `office.load_document`, and saves it to the target format using `document.save_as` with the previously determined filter.

The parent process monitors the child with a strict **30-second timeout** (enforced in lines 22-33 and 30-44), terminating unresponsive conversions to prevent resource exhaustion.

## Error Handling and Resource Cleanup

If the child process exits with a non-zero status, the parent logs the failure (lines 66-73) and aborts the operation. Regardless of success or failure, the service ensures no temporary files remain on disk by calling `cleanup_folder`. Finally, the resulting byte stream is uploaded back to the specified S3 bucket, completing the conversion cycle while maintaining clean workspace hygiene.

## API Interface and Job Submission

The HTTP endpoint defined in [`services/convert_service/src/api/convert.rs`](https://github.com/macro-inc/macro/blob/main/services/convert_service/src/api/convert.rs) accepts JSON payloads that map directly to `ConvertQueueMessage` structures. The handler validates incoming requests containing `from_bucket`, `from_key`, `to_bucket`, and `to_key` fields before enqueueing them for processing.

*Submitting a conversion job (JSON payload):*

```json
{
  "from_bucket": "uploads",
  "from_key": "example.docx",
  "to_bucket": "converted",
  "to_key": "example.pdf"
}

```

*Posting the job using Rust and reqwest:*

```rust
use reqwest::Client;
use serde_json::json;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = Client::new();
    let payload = json!({
        "from_bucket": "uploads",
        "from_key":   "example.docx",
        "to_bucket":  "converted",
        "to_key":     "example.pdf"
    });

    client
        .post("http://localhost:8080/convert")
        .json(&payload)
        .send()
        .await?
        .error_for_status()?;

    println!("Conversion job queued");
    Ok(())
}

```

## Summary

- The conversion service extracts file extensions from S3 keys and parses them into the `FileType` enum to identify source and target formats.
- The `get_lok_filter_from_file_types` helper selects the appropriate LibreOfficeKit filter for the specific format pair.
- Conversions execute inside sandboxed child processes with a **30-second timeout** to ensure system stability.
- The parent process monitors exit codes, handles errors via logging, and ensures cleanup of temporary files regardless of conversion success.
- The API accepts simple JSON payloads describing S3 object locations, making the service agnostic to specific document formats.

## Frequently Asked Questions

### What document formats does the Macro conversion service support?

The service supports any format defined in the `FileType` enum, including PDF, DOCX, PPTX, ODT, and others. Because the architecture delegates rendering to LibreOffice's universal engine, adding new formats typically requires only extending the enum and filter mappings without modifying the core conversion logic in [`convert.rs`](https://github.com/macro-inc/macro/blob/main/convert.rs).

### How does the service prevent conversion jobs from hanging indefinitely?

The parent process enforces a **30-second timeout** on all child processes that execute LibreOffice operations. If the conversion exceeds this limit, the parent terminates the child process, logs the failure, and aborts the job to prevent resource exhaustion and ensure queue throughput.

### Is the conversion process secure against malicious documents?

Yes, the service forks a sandboxed child process to run LibreOffice via the `rs_libreoffice_bindings` crate. This isolation ensures that any crashes or security issues during document processing remain contained within the child process, protecting the host system and parent process from compromise.

### Can I convert between any two supported formats, or are there restrictions?

The service is fully format-agnostic. By combining the `FileType` detection with `get_lok_filter_from_file_types`, any permutation of supported source and target formats can be processed without code changes, provided LibreOffice supports the specific conversion path.