Skip to content

Embed and extract attachments with gobl convert and the API - #68

Merged
samlown merged 2 commits into
convert-export-optionsfrom
convert-attachments
Oct 15, 2026
Merged

samlown merged 2 commits into
convert-export-optionsfrom
convert-attachments

Conversation

@samlown

@samlown samlown commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Exposes the GOBL convert attachments in the CLI and the API. Builds on #67; merge that first.

CLI

  • gobl convert --to … --attach file.pdf embeds a file in the exported document, and may be repeated. The MIME type is taken from the extension. --attach requires --to.
  • gobl convert in.xml out.json --attachments DIR saves the files embedded in an imported document to DIR, replacing existing files. Names come from the document, so only their base is used (../../x.pdf and C:\\x.pdf become x.pdf), and names repeated in the document are numbered (x-2.pdf). Files without a name use their ID, or attachment-N.
  • Formats that can't embed files are skipped, as in the register. For example, --to cii+fr-facturx-v1+basic,cii+en16931-v2017 falls back to EN 16931.

API

  • POST /v0/export also takes a multipart/form-data body: the GOBL document in a document field or file, and each file to embed in an attachment part. JSON bodies work as before. Bad multipart bodies, or ones without a document, return 400.
    curl -F document=@invoice.json -F attachment=@invoice.pdf "https://gobl.dev/v0/export?format=ubl+peppol+bis-v3"
  • POST /v0/import still returns the envelope alone, so existing clients are unaffected. It adds a GOBL-Attachments header with the number of embedded files. With ?attachments=true, it returns {"format", "envelope", "attachments"}, each attachment with id, name, description, mime and base64 data.
  • The OpenAPI spec documents the parameter, the header, the multipart body, and the Attachment and ImportResult schemas.

Internals

  • ops.ExportOptions.Attachments passes files to convert.Export. ops.NewAttachment removes directories from the name and fills in the MIME type from the extension when it's missing or application/octet-stream.
  • Request bodies are limited in line with Peppol, whose access points must accept messages of up to 100 MB (Class A). /v0/import accepts up to 100 MB, and /v0/export up to 75 MB including attachments, because base64 grows embedded files by a third and the exported document should stay within 100 MB. Larger requests get 413 with a GOBL error, using http.MaxBytesReader. A small wrapper records when the limit is hit, because GOBL errors don't keep the cause for errors.As. The README and OpenAPI document the limits. The CLI has no limits.
  • Multipart parts over 32 MB are stored temporarily on disk while the request is handled.

Tests

  • CLI: export with --attach and import with --attachments; a missing file; the flag combinations; unsafe and repeated names.
  • ops: an export–import round trip; refusal by FacturaE, which can't embed files; MIME and name handling.
  • API: body limits for import, JSON export and multipart export, lowered through a test-only hook.
  • API: multipart export with the document as a field and as a file; import with and without ?attachments=true; no attachments; an invalid parameter; malformed or incomplete multipart bodies.

I also checked by hand with the built binary that a PDF and a CSV are embedded with the right MIME types and saved back unchanged.

🤖 Generated with Claude Code

gobl convert --attach embeds files on export, and --attachments saves
the files embedded in an imported document. /v0/export takes a multipart
body with the document and attachment parts, and /v0/import counts
embedded files in GOBL-Attachments, returning them with
?attachments=true.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@codecov-commenter

codecov-commenter commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.65248% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.83%. Comparing base (0a7c7ce) to head (a512d5c).

Files with missing lines Patch % Lines
api/convert.go 87.17% 5 Missing and 5 partials ⚠️
cmd/gobl/convert.go 87.23% 3 Missing and 3 partials ⚠️
Additional details and impacted files
@@                    Coverage Diff                     @@
##           convert-export-options      #68      +/-   ##
==========================================================
+ Coverage                   64.68%   65.83%   +1.14%     
==========================================================
  Files                          53       53              
  Lines                        2577     2710     +133     
==========================================================
+ Hits                         1667     1784     +117     
- Misses                        812      820       +8     
- Partials                       98      106       +8     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

/v0/import accepts up to 100 MB, as Peppol access points must, and
/v0/export up to 75 MB including attachments, so the exported document
stays within 100 MB after base64 encoding. Larger requests get 413.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@samlown
samlown merged commit 767ea68 into convert-export-options Oct 15, 2026
2 checks passed
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.

2 participants