camundala.api.ast.scala Maven / Gradle / Ivy
The newest version!
package camundala
package api
import camundala.bpmn.*
import camundala.domain.*
import sttp.tapir.EndpointIO
import sttp.tapir.json.circe.*
import scala.annotation.targetName
import scala.reflect.ClassTag
import scala.util.Random
case class ApiDoc(apis: List[CApi]):
lazy val groupTags: List[ApiTag] = apis.flatMap(_.groupTag)
end ApiDoc
case class ApiTag(name: String, description: String, `x-displayName`: String)
object ApiTag:
given InOutCodec[ApiTag] = deriveInOutCodec
// given ApiSchema[ApiTag] = deriveApiSchema // problem with magnolia
sealed trait CApi:
def name: String
def groupTag: Option[ApiTag] = None
sealed trait GroupedApi extends CApi:
def name: String
def apis: List[? <: InOutApi[?, ?]]
def withApis(apis: List[? <: InOutApi[?, ?]]): GroupedApi
end GroupedApi
sealed trait InOutApi[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag
] extends CApi:
def inOut: InOut[In, Out, ?]
def apiExamples: ApiExamples[In, Out]
lazy val inOutDescr: InOutDescr[In, Out] = inOut.inOutDescr
lazy val id: String = inOutDescr.id
lazy val descr: String = inOut.descr.getOrElse("")
lazy val typeName: String = inOut.typeName
lazy val inOutType: InOutType = inOut.inOutType
def withExamples(
examples: ApiExamples[In, Out]
): InOutApi[In, Out]
def addInExample(label: String, example: In): InOutApi[In, Out] =
withExamples(
apiExamples.copy(inputExamples = apiExamples.inputExamples :+ (label, example))
)
def addOutExample(label: String, example: Out): InOutApi[In, Out] =
withExamples(
apiExamples.copy(outputExamples = apiExamples.outputExamples :+ (label, example))
)
// this function needs to be here as circe does not find the JsonEncoder in the extension method
lazy val inMapper: EndpointIO.Body[String, In] = jsonBody[In]
// this function needs to be here as circe does not find the JsonEncoder in the extension method
lazy val outMapper: EndpointIO.Body[String, Out] = jsonBody[Out]
lazy val inJson: Option[Json] = inOut.in match
case _: NoInput => None
case _ => Some(inOut.in.asJson.deepDropNullValues)
lazy val outJson: Option[Json] = inOut.out match
case _: NoInput => None
case _ => Some(inOut.out.asJson.deepDropNullValues)
lazy val variableNamesIn: List[String] =
inOut.in.productElementNames.toList
lazy val variableNamesOut: List[String] =
inOut.out.productElementNames.toList
def apiDescription(
diagramDownloadPath: Option[String],
diagramNameAdjuster: Option[String => String]
): String =
s"""$descr
|
|- Input: `${inOut.in.getClass.getName.replace("$", " > ")}`
|- Output: `${inOut.out.getClass.getName
.replace("$", " > ")}`""".stripMargin
protected def diagramName: Option[String] = None
protected def diagramFrame(
diagramDownloadPath: String,
diagramNameAdjuster: Option[String => String]
): String =
val postfix = if typeName == "Process" then "bpmn" else "dmn"
val postfixUpper = postfix.head.toUpper + postfix.tail
val pureDiagramName = diagramName.getOrElse(id)
val name =
diagramNameAdjuster.map(_(pureDiagramName)).getOrElse(pureDiagramName)
val fileName = s"$name.$postfix"
val randomPostfix = Random.nextInt(100000)
s"""
|
|
|
|
|
|
|Download: [$fileName]($diagramDownloadPath/$fileName)
|
|
|
|
|
|""".stripMargin
end diagramFrame
end InOutApi
case class ProcessApi[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag,
InitIn <: Product: InOutEncoder: InOutDecoder: Schema
](
name: String,
inOut: Process[In, Out, InitIn],
apiExamples: ApiExamples[In, Out],
apis: List[InOutApi[?, ?]] = List.empty,
override val diagramName: Option[String] = None
) extends InOutApi[In, Out],
GroupedApi:
def withApis(apis: List[InOutApi[?, ?]]): ProcessApi[In, Out, InitIn] = copy(apis = apis)
def withExamples(
examples: ApiExamples[In, Out]
): InOutApi[In, Out] =
copy(apiExamples = examples)
override def apiDescription(
diagramDownloadPath: Option[String],
diagramNameAdjuster: Option[String => String]
): String =
s"""${super.apiDescription(diagramDownloadPath, diagramNameAdjuster)}
|
|${inOut.in match
case _: GenericServiceIn => "" // no diagram if generic
case _ =>
diagramDownloadPath
.map(diagramFrame(_, diagramNameAdjuster))
.getOrElse("")
}
|${generalVariablesDescr(inOut.out, "")}""".stripMargin
// this function needs to be here as circe does not find the JsonEncoder in the extension method
lazy val initInMapper: EndpointIO.Body[String, InitIn] = jsonBody[InitIn]
end ProcessApi
object ProcessApi:
def apply[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag,
InitIn <: Product: InOutEncoder: InOutDecoder: Schema
](name: String, inOut: Process[In, Out, InitIn]): ProcessApi[In, Out, InitIn] =
ProcessApi(name, inOut, ApiExamples(name, inOut))
end ProcessApi
def generalVariablesDescr[Out <: Product: InOutEncoder](
out: Out,
serviceMock: String
) =
s"""
|
|
|
|General Variable(s)
|
|
|
|
|**outputVariables**:
|
|Just take the variable you need in your process!
|```json
|...
|"outputVariables": "${out.productElementNames.mkString(",")}",
|...
|```
|
|**outputMock**:
|
|```json
|...
|"outputMock": ${out.asJson.deepDropNullValues},
|...
|```
|$serviceMock
|
|
|
""".stripMargin
end generalVariablesDescr
sealed trait ExternalTaskApi[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag
] extends InOutApi[In, Out]:
def inOut: ExternalTask[In, Out, ?]
def processName: String = inOut.processName
lazy val topicName = inOut.topicName
override def apiDescription(
diagramDownloadPath: Option[String],
diagramNameAdjuster: Option[String => String]
): String =
s"""
|**Topic:** `$topicName` (to define in the _**Topic**_ of the _**External Task**_ > _Service Task_ of type _External_)
|
|${super.apiDescription(diagramDownloadPath, diagramNameAdjuster)}
|
|You can test this worker using the generic process _**$GenericExternalTaskProcessName**_ (e.g. with Postman).
|""".stripMargin
end ExternalTaskApi
case class ServiceWorkerApi[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag,
ServiceIn: InOutEncoder: InOutDecoder: Schema,
ServiceOut: InOutEncoder: InOutDecoder: Schema
](
name: String,
inOut: ServiceTask[In, Out, ServiceIn, ServiceOut],
apiExamples: ApiExamples[In, Out]
) extends ExternalTaskApi[In, Out]:
def withExamples(
examples: ApiExamples[In, Out]
): InOutApi[In, Out] =
copy(apiExamples = examples)
override def apiDescription(
diagramDownloadPath: Option[String],
diagramNameAdjuster: Option[String => String]
): String =
s"""
|
|${super.apiDescription(diagramDownloadPath, diagramNameAdjuster)}
|- ServiceOut: `$serviceOutDescr`
|${generalVariablesDescr(
inOut.out,
s"""
|**outputServiceMock**:
|```json
|...
|"outputServiceMock": ${MockedServiceResponse
.success200(inOut.defaultServiceOutMock)
.asJson.deepDropNullValues},
|...
|```"""
)}
""".stripMargin
private def serviceOutDescr =
inOut.defaultServiceOutMock match
case seq: Seq[?] =>
s"Seq[${seq.head.getClass.getName.replace("$", " > ")}]"
case other => other.getClass.getName.replace("$", " > ")
end ServiceWorkerApi
object ServiceWorkerApi:
def apply[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag,
ServiceIn: InOutEncoder: InOutDecoder: Schema,
ServiceOut: InOutEncoder: InOutDecoder: Schema
](
name: String,
inOut: ServiceTask[In, Out, ServiceIn, ServiceOut]
): ServiceWorkerApi[In, Out, ServiceIn, ServiceOut] =
ServiceWorkerApi(name, inOut, ApiExamples(name, inOut))
end ServiceWorkerApi
case class CustomWorkerApi[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag
](
name: String,
inOut: CustomTask[In, Out],
apiExamples: ApiExamples[In, Out]
) extends ExternalTaskApi[In, Out]:
def withExamples(
examples: ApiExamples[In, Out]
): InOutApi[In, Out] =
copy(apiExamples = examples)
end CustomWorkerApi
object CustomWorkerApi:
def apply[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag
](
name: String,
inOut: CustomTask[In, Out]
): CustomWorkerApi[In, Out] =
CustomWorkerApi(name, inOut, ApiExamples(name, inOut))
end CustomWorkerApi
case class DecisionDmnApi[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag
](
name: String,
inOut: DecisionDmn[In, Out],
apiExamples: ApiExamples[In, Out],
override val diagramName: Option[String] = None
) extends InOutApi[In, Out]:
def withExamples(
examples: ApiExamples[In, Out]
): InOutApi[In, Out] =
copy(apiExamples = examples)
def toActivityApi: ActivityApi[In, Out] =
ActivityApi(name, inOut)
override def apiDescription(
diagramDownloadPath: Option[String],
diagramNameAdjuster: Option[String => String]
): String =
s"""${super.apiDescription(diagramDownloadPath, diagramNameAdjuster)}
|
|${diagramDownloadPath
.map(diagramFrame(_, diagramNameAdjuster))
.getOrElse("")}
|""".stripMargin
end DecisionDmnApi
object DecisionDmnApi:
def apply[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag
](name: String, inOut: DecisionDmn[In, Out]): DecisionDmnApi[In, Out] =
DecisionDmnApi(name, inOut, ApiExamples(name, inOut))
end DecisionDmnApi
case class CApiGroup(
name: String,
description: String,
apis: List[InOutApi[?, ?]]
) extends GroupedApi:
def withApis(apis: List[InOutApi[?, ?]]): CApiGroup = copy(apis = apis)
override def groupTag: Option[ApiTag] = Some(ApiTag(name, description, name))
end CApiGroup
case class ActivityApi[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag
](
name: String,
inOut: Activity[In, Out, ?],
apiExamples: ApiExamples[In, Out]
) extends InOutApi[In, Out]:
def withExamples(
examples: ApiExamples[In, Out]
): ActivityApi[In, Out] =
copy(apiExamples = examples)
end ActivityApi
object ActivityApi:
def apply[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag
](name: String, inOut: Activity[In, Out, ?]): ActivityApi[In, Out] =
ActivityApi(name, inOut, ApiExamples(name, inOut))
end ActivityApi
case class ApiExamples[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag
](
inputExamples: InOutExamples[In],
outputExamples: InOutExamples[Out]
)
object ApiExamples:
def apply[
In <: Product: InOutEncoder: InOutDecoder: Schema,
Out <: Product: InOutEncoder: InOutDecoder: Schema: ClassTag
](name: String, inOut: InOut[In, Out, ?]): ApiExamples[In, Out] =
val enumInExamples = inOut.otherEnumInExamples
.map: examples =>
InOutExample(inOut.in) +:
examples.map: ex =>
InOutExample(ex)
.getOrElse:
Seq(InOutExample(name, inOut.in))
val enumOutExamples = inOut.otherEnumOutExamples
.map: examples =>
InOutExample(inOut.out) +:
examples.map: ex =>
InOutExample(ex)
.getOrElse:
Seq(InOutExample(name, inOut.out))
ApiExamples(
InOutExamples(enumInExamples),
InOutExamples(enumOutExamples)
)
end apply
end ApiExamples
case class InOutExamples[T <: Product: InOutEncoder: InOutDecoder: Schema](
examples: Seq[InOutExample[T]]
):
@targetName("add")
def :+(label: String, example: T): InOutExamples[T] =
copy(examples = examples :+ InOutExample(label, example))
lazy val fetchExamples: Seq[InOutExample[T]] =
examples
end InOutExamples
case class InOutExample[T <: Product: InOutEncoder: InOutDecoder: Schema](
name: String,
example: T
):
// this function needs to be here as circe does not find theInOutEncoderin the extension method
def toCamunda: FormVariables = CamundaVariable.toCamunda(example)
end InOutExample
object InOutExample:
def apply[T <: Product: InOutEncoder: InOutDecoder: Schema](inOut: T): InOutExample[T] =
val name = inOut.getClass.getName.replace("$", " > ").split('.').last
InOutExample(name, inOut)
end InOutExample
© 2015 - 2025 Weber Informatics LLC | Privacy Policy