org.http4k.tracing.renderer.MermaidSequenceDiagram.kt Maven / Gradle / Ivy
package org.http4k.tracing.renderer
import org.http4k.tracing.Actor
import org.http4k.tracing.ActorType.Human
import org.http4k.tracing.BiDirectional
import org.http4k.tracing.FireAndForget
import org.http4k.tracing.RequestResponse
import org.http4k.tracing.StartInteraction
import org.http4k.tracing.StartRendering
import org.http4k.tracing.StopRendering
import org.http4k.tracing.Trace
import org.http4k.tracing.TraceRender
import org.http4k.tracing.TraceRenderer
import org.http4k.tracing.TraceStep
object MermaidSequenceDiagram : TraceRenderer {
override fun render(scenarioName: String, steps: List): TraceRender {
val actors = steps.filterIsInstance().chronologicalActors()
val stepsRendered = steps.joinToString("\n ") {
when (it) {
is RequestResponse -> it.asMermaidSequenceDiagram()
is BiDirectional -> it.asMermaidSequenceDiagram()
is FireAndForget -> it.asMermaidSequenceDiagram()
is StartInteraction -> it.asMermaidSequenceDiagram()
is StartRendering, is StopRendering -> ""
}
}
val actorsRendered = actors.toMermaidActor().joinToString("\n ")
return TraceRender(
"$scenarioName - Sequence",
"MMD",
listOf(
"sequenceDiagram",
"title $scenarioName - Sequence",
actorsRendered,
stepsRendered,
).joinToString("\n ")
)
}
private fun Trace.asMermaidSequenceDiagram() = when (this) {
is RequestResponse -> asMermaidSequenceDiagram()
is BiDirectional -> asMermaidSequenceDiagram()
is FireAndForget -> asMermaidSequenceDiagram()
}
private fun Iterable.toMermaidActor() =
fold(emptyList()) { acc, next ->
val nextVal = when (next.type) {
Human -> "actor"
else -> "participant"
} + " ${next.safeName()}"
if (acc.contains(nextVal)) acc else acc + nextVal
}
private fun RequestResponse.asMermaidSequenceDiagram(): String {
val header = "${origin.safeName()} ->> ${target.safeName()}: $request"
val activate = "activate ${target.safeName()}"
val children = renderChildren()
val deactivate = "deactivate ${target.safeName()}"
val post = "${target.safeName()} ->> ${origin.safeName()}: $response"
return listOfNotNull(header, activate, children, post, deactivate).joinToString("\n ") { it.trim() }
}
private fun Trace.renderChildren() = children
.joinToString("\n ") { it.asMermaidSequenceDiagram() }
.takeIf { it.isNotEmpty() }
?.let { "$it\n " }
private fun BiDirectional.asMermaidSequenceDiagram(): String {
val pre = "${origin.safeName()} ->> ${target.safeName()}: $request".trim()
val children = renderChildren()?.trim()
val post = "${target.safeName()} ->> ${origin.safeName()}: "
return listOfNotNull(pre, children, post).joinToString("\n ")
}
private fun FireAndForget.asMermaidSequenceDiagram(): String {
val pre = "${origin.safeName()} -) ${target.safeName()}: $request"
val children = renderChildren()
return listOfNotNull(pre, children).joinToString("\n ") { it.trim() }
}
private fun StartInteraction.asMermaidSequenceDiagram(): String = """note over $origin: $interactionName"""
}
private fun Actor.safeName() = name.map { if (it.isWhitespace() || it.isLetterOrDigit()) it else '_' }.joinToString("")
© 2015 - 2025 Weber Informatics LLC | Privacy Policy