All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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