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

org.http4k.tracing.renderer.MermaidSequenceDiagram.kt Maven / Gradle / Ivy

The newest version!
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