Skip to content

Commit a41d717

Browse files
Copilotlitlfred
andcommitted
Add FML syntax validation functionality to services library
Co-authored-by: litlfred <662242+litlfred@users.noreply.github.com>
1 parent 41e0a96 commit a41d717

File tree

6 files changed

+137
-0
lines changed

6 files changed

+137
-0
lines changed
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.

src/commonMain/kotlin/org/litlfred/fmlrunner/FmlRunner.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ class FmlRunner {
2828
return compiler.compile(fmlContent)
2929
}
3030

31+
/**
32+
* Validate FML syntax without full compilation
33+
*/
34+
fun validateFmlSyntax(fmlContent: String): FmlSyntaxValidationResult {
35+
return compiler.validateSyntax(fmlContent)
36+
}
37+
3138
/**
3239
* Execute StructureMap on input content
3340
*/

src/commonMain/kotlin/org/litlfred/fmlrunner/compiler/FmlCompiler.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,4 +464,30 @@ class FmlCompiler {
464464
)
465465
}
466466
}
467+
468+
/**
469+
* Validate FML syntax without full compilation
470+
*/
471+
fun validateSyntax(fmlContent: String): FmlSyntaxValidationResult {
472+
return try {
473+
// Tokenize
474+
val tokenizer = FmlTokenizer(fmlContent)
475+
val tokens = tokenizer.tokenize()
476+
477+
// Parse for syntax validation (without requiring complete StructureMap)
478+
val parser = FmlParser(tokens)
479+
val result = parser.parse()
480+
481+
FmlSyntaxValidationResult(
482+
valid = result.success,
483+
errors = result.errors,
484+
warnings = result.warnings
485+
)
486+
} catch (e: Exception) {
487+
FmlSyntaxValidationResult(
488+
valid = false,
489+
errors = listOf("Syntax validation failed: ${e.message}")
490+
)
491+
}
492+
}
467493
}

src/commonMain/kotlin/org/litlfred/fmlrunner/types/FhirTypes.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ data class FmlCompilationResult(
9494
val warnings: List<String> = emptyList()
9595
)
9696

97+
/**
98+
* FML Syntax Validation Result
99+
*/
100+
@Serializable
101+
data class FmlSyntaxValidationResult(
102+
val valid: Boolean,
103+
val errors: List<String> = emptyList(),
104+
val warnings: List<String> = emptyList()
105+
)
106+
97107
/**
98108
* Execution Result for StructureMap execution
99109
*/

src/commonTest/kotlin/org/litlfred/fmlrunner/BasicTest.kt

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,98 @@ class FmlRunnerTest {
9595

9696
assertTrue(result.success, "Execution should succeed")
9797
}
98+
99+
@Test
100+
fun testValidFmlSyntaxValidation() {
101+
val runner = FmlRunner()
102+
val validFmlContent = """
103+
map "http://example.org/StructureMap/Patient" = "PatientTransform"
104+
105+
group main(source src, target tgt) {
106+
src.name -> tgt.fullName;
107+
src.active -> tgt.isActive;
108+
}
109+
""".trimIndent()
110+
111+
val result = runner.validateFmlSyntax(validFmlContent)
112+
assertTrue(result.valid, "Valid FML syntax should pass validation")
113+
assertTrue(result.errors.isEmpty(), "No errors should be reported for valid syntax")
114+
}
115+
116+
@Test
117+
fun testInvalidFmlSyntaxValidation_MissingMapKeyword() {
118+
val runner = FmlRunner()
119+
val invalidFmlContent = """
120+
"http://example.org/StructureMap/Patient" = "PatientTransform"
121+
122+
group main(source src, target tgt) {
123+
src.name -> tgt.fullName;
124+
}
125+
""".trimIndent()
126+
127+
val result = runner.validateFmlSyntax(invalidFmlContent)
128+
assertTrue(!result.valid, "Invalid FML syntax should fail validation")
129+
assertTrue(result.errors.isNotEmpty(), "Errors should be reported for invalid syntax")
130+
assertTrue(result.errors.any { it.contains("map") || it.contains("Expected") },
131+
"Error should mention missing 'map' keyword")
132+
}
133+
134+
@Test
135+
fun testInvalidFmlSyntaxValidation_MalformedGroup() {
136+
val runner = FmlRunner()
137+
val invalidFmlContent = """
138+
map "http://example.org/StructureMap/Patient" = "PatientTransform"
139+
140+
group main(source src, target tgt {
141+
src.name -> tgt.fullName;
142+
}
143+
""".trimIndent()
144+
145+
val result = runner.validateFmlSyntax(invalidFmlContent)
146+
assertTrue(!result.valid, "Invalid FML syntax should fail validation")
147+
assertTrue(result.errors.isNotEmpty(), "Errors should be reported for malformed syntax")
148+
}
149+
150+
@Test
151+
fun testInvalidFmlSyntaxValidation_EmptyContent() {
152+
val runner = FmlRunner()
153+
val emptyContent = ""
154+
155+
val result = runner.validateFmlSyntax(emptyContent)
156+
assertTrue(!result.valid, "Empty content should fail validation")
157+
assertTrue(result.errors.isNotEmpty(), "Errors should be reported for empty content")
158+
}
159+
160+
@Test
161+
fun testInvalidFmlSyntaxValidation_MissingClosingBrace() {
162+
val runner = FmlRunner()
163+
val invalidFmlContent = """
164+
map "http://example.org/StructureMap/Patient" = "PatientTransform"
165+
166+
group main(source src, target tgt) {
167+
src.name -> tgt.fullName;
168+
""".trimIndent()
169+
170+
val result = runner.validateFmlSyntax(invalidFmlContent)
171+
assertTrue(!result.valid, "Invalid FML syntax should fail validation")
172+
assertTrue(result.errors.isNotEmpty(), "Errors should be reported for missing closing brace")
173+
}
174+
175+
@Test
176+
fun testComplexValidFmlSyntaxValidation() {
177+
val runner = FmlRunner()
178+
val complexValidFmlContent = """
179+
map "http://example.org/StructureMap/Complex" = "ComplexTransform"
180+
181+
group main(source src, target tgt) {
182+
src.name -> tgt.fullName;
183+
src.active -> tgt.isActive;
184+
src.email -> tgt.contactEmail;
185+
}
186+
""".trimIndent()
187+
188+
val result = runner.validateFmlSyntax(complexValidFmlContent)
189+
assertTrue(result.valid, "Complex valid FML syntax should pass validation")
190+
assertTrue(result.errors.isEmpty(), "No errors should be reported for complex valid syntax")
191+
}
98192
}

0 commit comments

Comments
 (0)