Adding a New Parser
Transform Platform uses the Open/Closed principle β adding a new file format requires implementing one interface and annotating with @Component. No changes to ParserRegistry or TransformationPipeline.
How Parser Discovery Worksβ
Stepsβ
1. Create the parser fileβ
platform-core/src/main/kotlin/com/transformplatform/core/parsers/impl/NachaFileParser.kt
2. Implement FileParserβ
@Component
class NachaFileParser : FileParser {
override val parserName = "NACHA_PARSER"
override fun supports(format: FileFormat) = format == FileFormat.NACHA
override fun parse(input: InputStream, spec: FileSpec): Flow<ParsedRecord> = flow {
// Stream records line by line β never load the full file into memory
// For each record, emit a ParsedRecord
// On field errors: add ParseError to the record, do NOT throw
emit(
ParsedRecord(
fields = mapOf("routingNumber" to "021000021", "amount" to "10050"),
errors = emptyList(),
rowNumber = 1L
)
)
}
override fun validateSpec(spec: FileSpec) {
// Optional: throw SpecValidationException if the spec is incompatible with NACHA
}
}
3. Add the format enum value (if new)β
If FileFormat.NACHA doesn't exist yet, add it to FileSpec.kt:
enum class FileFormat {
CSV, FIXED_WIDTH, XML, JSON, NACHA, ISO_20022
}
4. Write testsβ
Create platform-core/src/test/kotlin/com/transformplatform/core/parsers/NachaFileParserTest.kt:
class NachaFileParserTest : DescribeSpec({
val parser = NachaFileParser()
describe("supports()") {
it("returns true for NACHA format") {
parser.supports(FileFormat.NACHA) shouldBe true
}
it("returns false for other formats") {
parser.supports(FileFormat.CSV) shouldBe false
}
}
describe("parse()") {
it("emits one record per NACHA entry detail") {
val input = /* test file input stream */
val spec = /* minimal FileSpec for NACHA */
val records = parser.parse(input, spec).toList()
records shouldHaveSize 3
}
}
})
Run: ./gradlew :platform-core:test
Checklistβ
- Parser implements
FileParserand is annotated@Component -
supports()is accurate β exactly one parser should match each format -
parse()returns aFlow<ParsedRecord>β never aList - Errors are added to
ParsedRecord.errorsβ never thrown inside the flow - Sensitive fields masked with
"***"in any error messages -
DescribeSpectests cover happy path and error cases -
AGENTS.mdΒ§6 andREADME.mdsupported formats updated
warning
Never throw inside a Flow { } block. Exceptions inside a flow create uncollectable flows. Always catch, create a ParseError, and add it to the record.