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

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

package org.http4k.tracing.renderer

import org.http4k.core.Status
import org.http4k.tracing.Actor
import org.http4k.tracing.ActorType.Database
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 PumlSequenceDiagram : TraceRenderer {
    override fun render(scenarioName: String, steps: List): TraceRender {
        val actors = steps.filterIsInstance().chronologicalActors()

        return TraceRender(
            "$scenarioName - Sequence",
            "PUML",
            """@startuml
            |title $scenarioName
            |${actors.toActor().joinToString("\n")}
            |${
                steps.joinToString("\n") {
                    when (it) {
                        is RequestResponse -> it.asPumlSequenceDiagram()
                        is BiDirectional -> it.asPumlSequenceDiagram()
                        is FireAndForget -> it.asPumlSequenceDiagram()
                        is StartInteraction -> it.asPumlSequenceDiagram()
                        is StartRendering, is StopRendering -> ""
                    }
                }
            }
@enduml""".trimMargin())
    }

    private fun Iterable.toActor() =
        fold(emptyList()) { acc, next ->
            val nextVal = when (next.type) {
                Database -> "database"
                else -> "participant"
            } + " \"${next.name}\""
            if (acc.contains(nextVal)) acc else acc + nextVal
        }

    private fun Trace.asPumlSequenceDiagram() = when (this) {
        is RequestResponse -> asPumlSequenceDiagram()
        is BiDirectional -> asPumlSequenceDiagram()
        is FireAndForget -> asPumlSequenceDiagram()
    }

    private fun RequestResponse.asPumlSequenceDiagram(): String = """
           |"${origin.name}" -> "${target.name}": $request
           |activate "${target.name}"
           |${children.joinToString("\n") { it.asPumlSequenceDiagram() }}
           |"${target.name}" ${response.toArrow()} "${origin.name}": ${response.toColour()} $response
           |deactivate "${target.name}"
            """.trimMargin()

    private fun String.toColour() = try {
        with(toStatus()) {
            when {
                successful -> ""
                redirection -> ""
                clientError -> ""
                serverError -> ""
                else -> ""
            }
        }
    } catch (e: Exception) {
        ">"
    }

    private fun BiDirectional.asPumlSequenceDiagram(): String = (
        """
           |"${origin.name}" <-> "${target.name}": $request""" +
            if (children.isNotEmpty()) """|activate "${target.name}"
           |${children.joinToString("\n") { it.asPumlSequenceDiagram() }}
           |deactivate "${target.name}"""
            else ""
        ).trimMargin()

    private fun FireAndForget.asPumlSequenceDiagram(): String = (
        """
           |"${origin.name}" -> "${target.name}": $request""" +
            if (children.isNotEmpty()) """
           |${children.joinToString("\n") { it.asPumlSequenceDiagram() }}"""
            else ""
        ).trimMargin()

    private fun StartInteraction.asPumlSequenceDiagram(): String = """

    note over "$origin" : "$origin" $interactionName
    """.trimIndent()

    private fun String.toArrow(): String =
        try {
            with(toStatus()) {
                when {
                    successful -> "-[#DarkGreen]>"
                    redirection -> "-[#DarkBlue]>"
                    clientError -> "X-[#DarkOrange]>"
                    serverError -> "X-[#FireBrick]>"
                    else -> "-[#Black]>"
                }
            }
        } catch (e: Exception) {
            "-->"
        }

    private fun String.toStatus() = Status(
        split(" ").first()
            .filter(Char::isDigit)
            .toInt(), split(" ").last()
    )
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy