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

jvmMain.assembly.RaptorKtorRouteComponent.kt Maven / Gradle / Ivy

The newest version!
package io.fluidsonic.raptor.ktor

import io.fluidsonic.raptor.*
import io.fluidsonic.raptor.transactions.*
import io.ktor.server.routing.*


// TODO needs custom property set scope & hierarchy
// TODO continue here
public class RaptorKtorRouteComponent internal constructor(
	private val host: String?,
	private val path: String,
) : RaptorComponent.Base(RaptorKtorPlugin),
	RaptorTaggableComponent,
	RaptorTransactionBoundary {

	private var configuration: KtorRouteConfiguration? = null
	private val customConfigurations = mutableListOf Unit>()
	private val plugins = mutableSetOf()
	private val propertyRegistry = RaptorPropertyRegistry.default()
	private var wrapper: (Route.(next: Route.() -> Unit) -> Unit)? = null


	// TODO Rethink architecture.
	//      At this point we need a per-route propertyRegistry (hierarchical) and transactionFactory (hierarchical).
	//      Standardize RaptorTransactionGeneratingComponent -> RaptorTransactionBoundary and add RaptorPropertyBoundary.
	//      Make sure that RaptorComponentConfigurationStartScope2 and RaptorComponentConfigurationEndScope2 are consistent and component-bound.
	//      Allow requiring the completed configuration of other plugins (in end scope) and detect cycles.
	//      Eventually force scope for referencing other plugins, e.g. complete(RaptorGraphPlugin) { graph(tag) }.
	//      (handy once we have context receivers to extend other types)
	//      Alternatively make each plugin have it's own shortcut for "require and use", e.g. val graphPlugin = use(plugins.graph)
	//      or make plugins.graph.… automatically finalize its configurations.
	//      How do we know what component belongs to what plugin?
	//      Should we allow plugin configuration on installation? Rarely needed & adds complexity.
	//      Should we allow dynamic plugins? I.e. class instead of object. How does that affect API?
	//      Should we allow plugins to be installed at defined boundaries?
	//          e.g. RaptorKtorRoutePlugin : RaptorBoundaryPlugin, class RaptorKtorRouteComponent: RaptorPluginBoundary
	//      Make all component companions internal and use them for component definition? (label, key, "return type")
	internal fun complete() = checkNotNull(configuration)


	@RaptorDsl
	public fun custom(configure: RaptorKtorRouteInitializationScope.() -> Unit) {
		customConfigurations += configure
	}


	@RaptorDsl
	public fun install(plugin: RaptorKtorRoutePlugin) {
		if (plugins.add(plugin))
			with(plugin) {
				ConfigurationStartScope().onConfigurationStarted()
			}
	}


	@RaptorDsl
	public val routes: RaptorKtorRoutesComponent<*>
		get() = componentRegistry.oneOrRegister(Keys.routesComponent) { RaptorKtorRoutesComponent.NonRoot() }


	@RaptorDsl
	public fun wrap(wrapper: RaptorKtorRouteInitializationScope.(next: Route.() -> Unit) -> Unit) {
		val previousWrapper = this.wrapper
		if (previousWrapper != null)
			this.wrapper = { next ->
				previousWrapper { wrapper(next) }
			}
		else
			this.wrapper = wrapper
	}


	// TODO use DSL instead of overridden functions? see Ktor 2
	override fun RaptorComponentConfigurationEndScope.onConfigurationEnded() {
		if (plugins.isNotEmpty()) {
			val scope = ConfigurationEndScope(parent = this)

			for (plugin in plugins)
				with(plugin) {
					scope.onConfigurationEnded()
				}
		}

		// TODO Check/clean paths.
		configuration = KtorRouteConfiguration(
			children = componentRegistry.oneOrNull(Keys.routesComponent)?.complete().orEmpty(),
			customConfigurations = customConfigurations.toList(),
			host = host,
			path = path,
			properties = [email protected](),
			transactionFactory = transactionFactory(), // TODO use component-bound scope
			wrapper = wrapper,
		)
	}


	private inner class ConfigurationEndScope(
		parent: RaptorComponentConfigurationEndScope,
	) : RaptorKtorRoutePluginConfigurationEndScope, RaptorAssemblyCompletionScope by parent {

		private val routeScope = object :
			RaptorKtorRoutePluginConfigurationEndScope.RouteScope,
			RaptorComponentConfigurationEndScope by parent {

			override val propertyRegistry: RaptorPropertyRegistry
				get() = [email protected]
		}


		override fun route(configuration: RaptorKtorRoutePluginConfigurationEndScope.RouteScope.() -> Unit) {
			routeScope.configuration()
		}
	}


	private inner class ConfigurationStartScope : RaptorKtorRoutePluginConfigurationStartScope {

		override val route: RaptorKtorRouteComponent
			get() = this@RaptorKtorRouteComponent
	}
}


@RaptorDsl
public fun RaptorAssemblyQuery.custom(configure: RaptorKtorRouteInitializationScope.() -> Unit) {
	each {
		custom(configure)
	}
}


@RaptorDsl
public fun RaptorAssemblyQuery.install(plugin: RaptorKtorRoutePlugin) {
	each {
		install(plugin)
	}
}


@RaptorDsl
public val RaptorAssemblyQuery.routes: RaptorAssemblyQuery>
	get() = map { it.routes }


@RaptorDsl
public fun RaptorAssemblyQuery.wrap(wrapper: RaptorKtorRouteInitializationScope.(next: Route.() -> Unit) -> Unit) {
	each {
		wrap(wrapper)
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy