Skip to content

refactor(converter): introduce FormatRegistry to centralize format dispatch - #434

Merged
unclesp1d3r merged 38 commits into
mainfrom
325-refactorconverter-introduce-formatregistry-to-centralize-format-dispatch-and-eliminate-scattered-switch-statements
Mar 20, 2026
Merged

refactor(converter): introduce FormatRegistry to centralize format dispatch#434
unclesp1d3r merged 38 commits into
mainfrom
325-refactorconverter-introduce-formatregistry-to-centralize-format-dispatch-and-eliminate-scattered-switch-statements

Conversation

@unclesp1d3r

Copy link
Copy Markdown
Member

Summary

  • Introduce FormatRegistry as the single source of truth for supported output formats, replacing scattered switch statements and constant definitions across 8+ locations
  • DefaultRegistry centralizes format names, aliases, file extensions, validation, and generation dispatch — adding a new format requires only registering a FormatHandler in newDefaultRegistry()
  • Normalize all format handler parameter signatures to accept Options uniformly, eliminating inconsistent parameter decomposition across handlers
  • Add alias resolution to processor.Transform() via DefaultRegistry.Canonical() so aliases (txt, htm, md, yml) work consistently across all code paths
  • Export StripMarkdownFormatting and RenderMarkdownToHTML for cross-package use by the processor
  • Replace silent .md file extension fallback with proper error propagation
  • Add nil-handler guard to Register() for fail-fast validation
  • Add comprehensive test coverage: 76 registry test cases (100% coverage), 12 new processor format/alias tests

Key Design Decisions

  • Handler coupling: FormatHandler.Generate() accepts *HybridGenerator (concrete type). This is a dispatch table, not a strategy pattern — acceptable since all handlers are internal. Deferred to interface/closure design if external format registration is needed later.
  • Register() panics: Follows database/sql driver pattern — init-time panics on duplicate names/aliases/nil handlers.
  • Canonical() passthrough: Returns lowercased input for unknown formats rather than erroring. Get() is the authoritative validation gate.

Files Changed

Area Files What
Core registry.go (new, 268 lines) FormatRegistry, FormatHandler interface, 5 handler implementations
Generator hybrid_generator.go Switch → handler dispatch via handlerForFormat()
CLI convert.go, shared_flags.go Remove format constants, use registry for validation/completion/extensions
Config validation.go ValidFormats derived from registry with slices.Clone()
Processor processor.go Alias resolution + text/html format support
Options options.go Format.Validate() delegates to registry
Docs AGENTS.md §5.9b, §12.5 FormatRegistry pattern, documentation accuracy guidelines
Tests registry_test.go, processor_test.go, core_test.go 88 new test cases

Test Plan

  • just ci-check passes (pre-commit, format, lint 0 issues, all 33 packages green)
  • registry_test.go: 76 test cases covering Get, Canonical, Register panics, ValidFormats, Extensions, handlerForFormat, DefaultRegistry content, handler dispatch
  • Processor: 12 new tests for text/html formats + md/yml/txt/htm alias resolution
  • Verify shell completions work: opndossier convert --format <TAB>
  • Verify file extension is correct for each format: opndossier convert testdata/config.xml -f json -o test

Closes #325

…ess code drift

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…erter registry

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…l formats

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…or consistency

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…prove function naming consistency

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…gistry for support checks

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…ormatting for consistency

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…spatch and eliminate scattered switch statements

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…r consistency

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…tements, centralize format handling with DefaultRegistry

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…ormat retrieval and improve test assertions

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
… format management

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…rForFormat function

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…teToWriter methods for improved output handling

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…to eliminate hardcoded switch statements

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…ps and enhance format descriptions

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…riginal registry

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…ved flexibility

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…ndler registration and retrieval

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…r format resolution

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…on and integration details

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…utput formats and aliases

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…case insensitivity

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…tRegistry

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
…and aliases in CoreProcessor

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Copilot AI review requested due to automatic review settings March 20, 2026 04:14
@dosubot dosubot Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Mar 20, 2026
@coderabbitai

coderabbitai Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • HTML and plain-text output formats added for export and transformation
    • Format aliases (md, yml, txt, htm) are canonicalized and supported
    • Dynamic shell completion now lists all supported formats
  • Bug Fixes

    • More reliable file-extension selection and format validation with clearer unsupported-format errors
    • Improved error propagation for text/HTML conversion failures
  • Documentation

    • Architecture and registry-pattern docs updated; added interface accuracy guidelines

Walkthrough

Introduces a centralized FormatRegistry and FormatHandler API; replaces scattered format switches with registry-driven lookups across CLI, generator, and processor layers; adds text/html formats and exported converter helpers; updates tests, config validation, and documentation.

Changes

Cohort / File(s) Summary
Registry Foundation
internal/converter/registry.go, internal/converter/registry_test.go
Add FormatHandler interface, FormatRegistry implementation with alias/canonical resolution, extensions/validation helpers, DefaultRegistry prepopulated for markdown/json/yaml/text/html, and comprehensive tests for registration/lookup/dispatch.
Generator & Options
internal/converter/hybrid_generator.go, internal/converter/options.go
Replace switch-based dispatch with registry lookup (handlerForFormat) and delegate generation to handler methods; Format.Validate() defers to registry lookup. Adjust generator helper signatures to accept Options.
Plaintext & HTML Helpers
internal/converter/plaintext.go, internal/converter/html.go, internal/converter/plaintext_test.go, internal/converter/html_test.go
Promote helpers to exported APIs: StripMarkdownFormatting(markdown) (string, error) and RenderMarkdownToHTML(markdown) (string, error); update tests to use exported functions and handle errors.
CLI Integration & Flags
cmd/convert.go, cmd/shared_flags.go, cmd/audit_handler_test.go, cmd/shared_flags_test.go
Remove hardcoded format constants/aliases; derive file extensions, normalization, validation, and shell completions from converter.DefaultRegistry; update related tests and completion descriptions.
Config Validation
internal/config/validation.go
ValidFormats now clones converter.DefaultRegistry.ValidFormatsWithAliases() and appends "" to allow empty values.
Processor Dispatch & Tests
internal/processor/processor.go, internal/processor/processor_test.go, internal/processor/core_test.go, internal/processor/README.md
CoreProcessor.Transform resolves canonical formats via registry and supports text and html (via converter helpers); tests extended for aliases and new formats; README updated to document aliases and behaviors.
Docs & Guidance
AGENTS.md, docs/development/architecture.md, docs/solutions/*, internal/processor/README.md
Add FormatRegistry pattern docs, update architecture and CLI docs to include HTML/text formats, add documentation-accuracy guideline and remediation/verification guidance for interface drift.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI (cmd/convert)
  participant Registry as FormatRegistry (internal/converter)
  participant Generator as HybridGenerator
  participant Handler as FormatHandler
  participant FS as File/Writer

  CLI->>Registry: Get(format)
  Registry-->>CLI: handler or ErrUnsupportedFormat
  CLI->>Generator: Generate(data, opts, format)
  Generator->>Registry: handlerForFormat(format)
  Registry-->>Generator: handler
  Generator->>Handler: Generate(data, opts)
  Handler-->>Generator: output
  Generator-->>CLI: output
  CLI->>Handler: FileExtension()
  Handler-->>CLI: ".ext"
  CLI->>FS: write file
  FS-->>CLI: success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #264: Adds text/html generation helpers and converter changes that are integrated and exercised by this registry refactor.
  • #183: Earlier HybridGenerator/converter refactor that this registry work reorganizes into handler-driven dispatch.
  • #184: Related migration of generation internals; overlaps on HybridGenerator surface and format handling.

Suggested labels

go, type:feature, testing, priority:normal

Poem

✨ A registry now holds the formats’ key,
No scattered switches—one truth we see.
Markdown, JSON, YAML, text, and HTML play,
Handlers answer, generate, and write away.
Tests and docs sing: dispatch aligned, hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows conventional commit format (type: scope) and accurately reflects the main change: introducing FormatRegistry to centralize format dispatch.
Description check ✅ Passed The PR description is comprehensive, covering summary, key design decisions, files changed, test plan, and closes a linked issue. It aligns well with the provided template structure.
Linked Issues check ✅ Passed The PR fully implements all acceptance criteria from #325: FormatRegistry and FormatHandler interfaces defined, all five built-in formats migrated to handlers, validation/dispatch/normalization replaced with registry calls, processor.Transform() updated for text/html formats, comprehensive test coverage (76 registry tests + 12 processor tests), and CI checks passing.
Out of Scope Changes check ✅ Passed All changes are directly aligned with #325 objectives: registry implementation, handler dispatch migration, CLI updates, processor format support, documentation updates, and test additions. No unrelated or scope-creeping changes detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 325-refactorconverter-introduce-formatregistry-to-centralize-format-dispatch-and-eliminate-scattered-switch-statements
📝 Coding Plan
  • Generate coding plan for human review comments

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

mergify Bot added a commit that referenced this pull request Mar 20, 2026
@coderabbitai coderabbitai Bot added documentation Improvements or additions to documentation and removed go Pull requests that update go code type:feature New feature implementation breaking_change Significant changes that disrupt backward compatibility. priority:normal Normal priority issue testing Test infrastructure and test-related issues labels Mar 20, 2026
@mergify

mergify Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

This pull request spent 3 minutes 12 seconds in the queue, with no time running CI.

Required conditions to merge
  • check-success = Build
  • check-success = Integration Tests
  • check-success = Test (windows-latest)
  • all of [🛡 Merge Protections rule Full CI must pass]:
    • check-success = Build
    • check-success = Integration Tests
    • check-success = Test (windows-latest)
    • check-success = Coverage
    • check-success = Lint
    • check-success = Test (macos-latest)
    • check-success = Test (ubuntu-latest)
  • check-success = Coverage
  • check-success = Lint
  • check-success = Test (macos-latest)
  • check-success = Test (ubuntu-latest)
  • all of [🛡 Merge Protections rule Do not merge outdated PRs]:
  • all of [🛡 Merge Protections rule Enforce conventional commit]:
  • any of [🛡 GitHub repository ruleset rule Main]:
    • check-neutral = Mergify Merge Protections
    • check-skipped = Mergify Merge Protections
    • check-success = Mergify Merge Protections

Reason

The pull request #434 has been manually updated

Hint

If you want to requeue this pull request, you can post a @mergifyio queue comment.

…spatch and eliminate scattered switch statements

Signed-off-by: UncleSp1d3r <unclesp1d3r@evilbitlabs.io>
Copilot AI review requested due to automatic review settings March 20, 2026 04:49

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/converter/registry_test.go`:
- Around line 429-445: The test compares handler values with assert.Equal in the
loop; change that to assert.Same to verify pointer identity consistency between
the alias-resolved handler and the canonical handler: in the table-driven test
where you call DefaultRegistry.Get(tc.alias) and
DefaultRegistry.Get(tc.canonical) (variables h and canonicalHandler), replace
the final assert.Equal(t, canonicalHandler, h, ...) with assert.Same(t,
canonicalHandler, h, ...) so the test ensures the handlers are the exact same
object rather than just equal.

In `@internal/converter/registry.go`:
- Around line 47-92: In FormatRegistry.Register, add validation to detect (1)
self-aliasing and (2) duplicate aliases returned by handler.Aliases(): while
iterating aliases build a local seen map/set (e.g., seenAlias) and for each
normalized aliasKey panic if aliasKey == key (self-aliasing) and panic if
seenAlias[aliasKey] is already true (duplicate within the same handler); keep
using aliasKeys for the commit phase and do these checks before mutating the
handlers and aliases maps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dc1db350-062a-4a64-93d0-0d2c69951120

📥 Commits

Reviewing files that changed from the base of the PR and between 0cc5fbb and 09ccc3f.

📒 Files selected for processing (10)
  • cmd/convert.go
  • docs/development/architecture.md
  • docs/solutions/logic-errors/documentation-code-drift-interface-refactoring.md
  • internal/converter/hybrid_generator.go
  • internal/converter/plaintext.go
  • internal/converter/plaintext_test.go
  • internal/converter/registry.go
  • internal/converter/registry_test.go
  • internal/processor/README.md
  • internal/processor/processor.go

Comment on lines +429 to +445
for _, tc := range tests {
t.Run(tc.alias, func(t *testing.T) {
t.Parallel()

canonical, ok := DefaultRegistry.Canonical(tc.alias)
assert.Equal(t, tc.canonical, canonical)
assert.True(t, ok)

h, err := DefaultRegistry.Get(tc.alias)
require.NoError(t, err)

canonicalHandler, err := DefaultRegistry.Get(tc.canonical)
require.NoError(t, err)
assert.Equal(t, canonicalHandler, h, "alias should resolve to the same handler as canonical")
})
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Use assert.Same for handler pointer identity consistency.

Line 442 uses assert.Equal to compare handlers, but line 245 correctly uses assert.Same for the same semantic check. For handler pointer identity verification, assert.Same is more precise.

♻️ Suggested change for consistency
 			canonicalHandler, err := DefaultRegistry.Get(tc.canonical)
 			require.NoError(t, err)
-			assert.Equal(t, canonicalHandler, h, "alias should resolve to the same handler as canonical")
+			assert.Same(t, canonicalHandler, h, "alias should resolve to the same handler as canonical")
📝 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
for _, tc := range tests {
t.Run(tc.alias, func(t *testing.T) {
t.Parallel()
canonical, ok := DefaultRegistry.Canonical(tc.alias)
assert.Equal(t, tc.canonical, canonical)
assert.True(t, ok)
h, err := DefaultRegistry.Get(tc.alias)
require.NoError(t, err)
canonicalHandler, err := DefaultRegistry.Get(tc.canonical)
require.NoError(t, err)
assert.Equal(t, canonicalHandler, h, "alias should resolve to the same handler as canonical")
})
}
}
for _, tc := range tests {
t.Run(tc.alias, func(t *testing.T) {
t.Parallel()
canonical, ok := DefaultRegistry.Canonical(tc.alias)
assert.Equal(t, tc.canonical, canonical)
assert.True(t, ok)
h, err := DefaultRegistry.Get(tc.alias)
require.NoError(t, err)
canonicalHandler, err := DefaultRegistry.Get(tc.canonical)
require.NoError(t, err)
assert.Same(t, canonicalHandler, h, "alias should resolve to the same handler as canonical")
})
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/converter/registry_test.go` around lines 429 - 445, The test
compares handler values with assert.Equal in the loop; change that to
assert.Same to verify pointer identity consistency between the alias-resolved
handler and the canonical handler: in the table-driven test where you call
DefaultRegistry.Get(tc.alias) and DefaultRegistry.Get(tc.canonical) (variables h
and canonicalHandler), replace the final assert.Equal(t, canonicalHandler, h,
...) with assert.Same(t, canonicalHandler, h, ...) so the test ensures the
handlers are the exact same object rather than just equal.

Comment thread internal/converter/registry.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 1 comment.

Comment thread internal/converter/registry.go
@unclesp1d3r

Copy link
Copy Markdown
Member Author

@Mergifyio queue

@mergify

mergify Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

This pull request spent 1 minute 59 seconds in the queue, with no time running CI.

Required conditions to merge
  • check-success = Build
  • check-success = Coverage
  • check-success = Integration Tests
  • check-success = Lint
  • check-success = Test (macos-latest)
  • check-success = Test (ubuntu-latest)
  • check-success = Test (windows-latest)
  • all of [🛡 Merge Protections rule Full CI must pass]:
    • check-success = Build
    • check-success = Coverage
    • check-success = Integration Tests
    • check-success = Lint
    • check-success = Test (macos-latest)
    • check-success = Test (ubuntu-latest)
    • check-success = Test (windows-latest)
  • all of [🛡 Merge Protections rule Do not merge outdated PRs]:
  • all of [🛡 Merge Protections rule Enforce conventional commit]:
  • any of [🛡 GitHub repository ruleset rule Main]:
    • check-neutral = Mergify Merge Protections
    • check-skipped = Mergify Merge Protections
    • check-success = Mergify Merge Protections

Reason

Pull request #434 has been merged manually at 4b0a086

Hint

You were too fast!

@mergify mergify Bot added the queued label Mar 20, 2026
mergify Bot added a commit that referenced this pull request Mar 20, 2026
@coderabbitai coderabbitai Bot added go Pull requests that update go code type:feature New feature implementation priority:normal Normal priority issue testing Test infrastructure and test-related issues and removed documentation Improvements or additions to documentation labels Mar 20, 2026
@unclesp1d3r
unclesp1d3r merged commit 4b0a086 into main Mar 20, 2026
22 of 26 checks passed
@unclesp1d3r
unclesp1d3r deleted the 325-refactorconverter-introduce-formatregistry-to-centralize-format-dispatch-and-eliminate-scattered-switch-statements branch March 20, 2026 04:56
@mergify mergify Bot added dequeued and removed queued labels Mar 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

architecture Architectural changes and design decisions dequeued go Pull requests that update go code priority:normal Normal priority issue refactor Code refactoring and restructuring size:XXL This PR changes 1000+ lines, ignoring generated files. testing Test infrastructure and test-related issues type:feature New feature implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(converter): introduce FormatRegistry to centralize format dispatch and eliminate scattered switch statements

2 participants