Skip to content

Commit

Permalink
feat(model-server): implement BranchV1 for branches
Browse files Browse the repository at this point in the history
This adds a new media type for requesting branch metadata. The commit
also prepares the openapi-generator templates to support media-type
versioning by optionally being able to declare separate handler methods
per media type. For registering types for JSON serialization, the model
needs to be annotated with the intended media type via the
x-modelix-media-type vendor extension.
  • Loading branch information
languitar committed Jul 2, 2024
1 parent 43922d7 commit 2abc6b0
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 12 deletions.
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ ktor-serialization = { group = "io.ktor", name = "ktor-serialization", version.r
ktor-serialization-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }

keycloak-authz-client = { group = "org.keycloak", name = "keycloak-authz-client", version = "25.0.1" }

kotest-assertions-coreJvm = { group = "io.kotest", name = "kotest-assertions-core-jvm", version = "5.9.1" }
kotest-assertions-ktor = { group = "io.kotest.extensions", name = "kotest-assertions-ktor", version = "2.0.0" }

guava = { group = "com.google.guava", name = "guava", version = "33.2.1-jre" }
org-json = { group = "org.json", name = "json", version = "20240303" }
google-oauth-client = { group = "com.google.oauth-client", name = "google-oauth-client", version = "1.36.0" }
Expand Down
43 changes: 38 additions & 5 deletions model-server-openapi/specifications/model-server-v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,36 @@ paths:
required: true
schema:
type: string
x-modelix-media-type-handlers:
- v1:
- 'application/x.modelix.branch+json;version=1'
- delta:
- 'application/x-modelix-objects-v2'
- 'application/x-modelix-objects'
- 'application/json'
- 'text/plain'
- '*/*'
responses:
"404":
$ref: '#/components/responses/404'
"200":
$ref: '#/components/responses/versionDelta'
# content:
# '*/*':
# schema:
# $ref: "#/components/schemas/VersionDelta"
description: "Information about a branch for content type `application/x.modelix.branch+json;version=*'. Else all model data of the branch in version delta format."
content:
'application/x.modelix.branch+json;version=1':
schema:
$ref: "#/components/schemas/BranchV1"
'application/x-modelix-objects-v2':
schema:
type: string
'application/x-modelix-objects':
schema:
type: string
'application/json':
schema:
type: object
'text/plain':
schema:
type: string
default:
$ref: '#/components/responses/GeneralError'
post:
Expand Down Expand Up @@ -538,3 +559,15 @@ components:
type: string
value2:
type: string
BranchV1:
x-modelix-media-type: 'application/x.modelix.branch+json;version=1'
type: object
properties:
name:
type: string
current_hash:
type: string
example: 7fQeo*xrdfZuHZtaKhbp0OosarV5tVR8N3pW8JPkl7ZE
required:
- name
- current_hash
2 changes: 2 additions & 0 deletions model-server/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ dependencies {

testImplementation(libs.bundles.apache.cxf)
testImplementation(libs.junit)
testImplementation(libs.kotest.assertions.coreJvm)
testImplementation(libs.kotest.assertions.ktor)
testImplementation(libs.cucumber.java)
testImplementation(libs.ktor.server.test.host)
testImplementation(libs.kotlin.coroutines.test)
Expand Down
2 changes: 2 additions & 0 deletions model-server/src/main/kotlin/org/modelix/model/server/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import kotlinx.serialization.json.Json
import org.apache.commons.io.FileUtils
import org.apache.ignite.Ignition
import org.modelix.api.v1.Problem
import org.modelix.api.v2.Paths.registerJsonTypes
import org.modelix.authorization.KeycloakUtils
import org.modelix.authorization.NoPermissionException
import org.modelix.authorization.NotLoggedInException
Expand Down Expand Up @@ -203,6 +204,7 @@ object Main {
}
install(ContentNegotiation) {
json()
registerJsonTypes()
}
install(CORS) {
anyHost()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEmpty
import kotlinx.coroutines.flow.withIndex
import kotlinx.coroutines.withContext
import org.modelix.api.v2.BranchV1
import org.modelix.api.v2.DefaultApi
import org.modelix.authorization.getUserName
import org.modelix.model.InMemoryModels
Expand Down Expand Up @@ -94,7 +95,9 @@ class ModelReplicationServer(
}
}

private fun repositoryId(paramValue: String?) = RepositoryId(checkNotNull(paramValue) { "Parameter 'repository' not available" })
private fun repositoryId(paramValue: String?) =
RepositoryId(checkNotNull(paramValue) { "Parameter 'repository' not available" })

private suspend fun <R> runWithRepository(repository: String, body: suspend () -> R): R {
return repositoriesManager.runWithRepository(repositoryId(repository), body)
}
Expand All @@ -107,7 +110,7 @@ class ModelReplicationServer(
call.respondText(repositoriesManager.getBranchNames(repositoryId(repository)).joinToString("\n"))
}

override suspend fun PipelineContext<Unit, ApplicationCall>.getRepositoryBranch(
override suspend fun PipelineContext<Unit, ApplicationCall>.getRepositoryBranchDelta(
repository: String,
branch: String,
lastKnown: String?,
Expand All @@ -119,6 +122,18 @@ class ModelReplicationServer(
}
}

override suspend fun PipelineContext<Unit, ApplicationCall>.getRepositoryBranchV1(
repository: String,
branch: String,
lastKnown: String?,
) {
runWithRepository(repository) {
val branchRef = repositoryId(repository).getBranchReference(branch)
val versionHash = repositoriesManager.getVersionHash(branchRef) ?: throw BranchNotFoundException(branchRef)
call.respond(BranchV1(branch, versionHash))
}
}

override suspend fun PipelineContext<Unit, ApplicationCall>.deleteRepositoryBranch(
repository: String,
branch: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package {{packageName}}

import io.ktor.resources.*
import kotlinx.serialization.*
import io.ktor.http.ContentType
import io.ktor.serialization.kotlinx.KotlinxSerializationConverter
import io.ktor.serialization.kotlinx.json.DefaultJson
import io.ktor.server.plugins.contentnegotiation.ContentNegotiationConfig
{{#imports}}import {{import}}
{{/imports}}

Expand All @@ -26,5 +30,22 @@ object Paths {
{{/operation}}
{{/operations}}
{{/apis}}

/**
* Registers all models from /components/schemas with an x-modelix-media-type vendor extension to be serializable
* as JSON for that media type.
*/
fun ContentNegotiationConfig.registerJsonTypes() {
{{#models}}
{{#model}}
{{#vendorExtensions}}
{{#x-modelix-media-type}}
register(ContentType.parse("{{{.}}}"), KotlinxSerializationConverter(DefaultJson))
{{/x-modelix-media-type}}
{{/vendorExtensions}}
{{/model}}
{{/models}}
}

}
{{/apiInfo}}
57 changes: 54 additions & 3 deletions model-server/src/main/resources/openapi/templates/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package {{apiPackage}}

import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
{{#featureResources}}
import {{packageName}}.Paths
Expand All @@ -22,21 +23,50 @@ import io.ktor.util.pipeline.PipelineContext
abstract class {{classname}} {
{{#operations}}
{{#operation}}

{{#vendorExtensions}}
{{#x-modelix-media-type-handlers}}
{{#entrySet}}

/**{{#summary}}
* {{.}}{{/summary}}
*
* {{unescapedNotes}}
*
* {{httpMethod}} {{path}}
*
{{#allParams}}* @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/allParams}}
{{#allParams}}
* @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/allParams}}
*/
{{#isDeprecated}}
@Deprecated("deprecated flag is set in the OpenAPI specification")
{{/isDeprecated}}
abstract suspend fun PipelineContext<Unit, ApplicationCall>.{{operationId}}{{#lambda.titlecase}}{{key}}{{/lambda.titlecase}}({{#allParams}}{{paramName}}: {{{dataType}}}{{^required}}?{{/required}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}})

{{/entrySet}}
{{/x-modelix-media-type-handlers}}
{{^x-modelix-media-type-handlers}}

/**{{#summary}}
* {{.}}{{/summary}}
*
* {{unescapedNotes}}
*
* {{httpMethod}} {{path}}
*
{{#allParams}}
* @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/allParams}}
*/
{{#isDeprecated}}
@Deprecated("deprecated flag is set in the OpenAPI specification")
{{/isDeprecated}}
abstract suspend fun PipelineContext<Unit, ApplicationCall>.{{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}{{^required}}?{{/required}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}})

{{/x-modelix-media-type-handlers}}
{{/vendorExtensions}}

{{/operation}}
{{/operations}}

Expand All @@ -55,9 +85,30 @@ abstract class {{classname}} {
{{#operations}}
{{#operation}}
protected open fun Route.install_{{operationId}}() {
{{#vendorExtensions}}
{{#x-modelix-media-type-handlers}}
{{#entrySet}}

accept(
{{#value}}
ContentType.parse("{{{.}}}"),
{{/value}}
) {
{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}<Paths.{{operationId}}> { parameters ->
{{operationId}}{{#lambda.titlecase}}{{key}}{{/lambda.titlecase}}({{#allParams}}parameters.{{paramName}}{{^-last}}, {{/-last}}{{/allParams}})
}
}

{{/entrySet}}
{{/x-modelix-media-type-handlers}}
{{^x-modelix-media-type-handlers}}

{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}<Paths.{{operationId}}> { parameters ->
{{#lambda.indented_8}}{{operationId}}({{#allParams}}parameters.{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}){{/lambda.indented_8}}
{{operationId}}({{#allParams}}parameters.{{paramName}}{{^-last}}, {{/-last}}{{/allParams}})
}

{{/x-modelix-media-type-handlers}}
{{/vendorExtensions}}
}
{{/operation}}
{{/operations}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import io.ktor.server.routing.IgnoreTrailingSlash
import io.ktor.server.testing.ApplicationTestBuilder
import io.ktor.server.websocket.WebSockets
import kotlinx.coroutines.runBlocking
import org.modelix.api.v2.Paths.registerJsonTypes
import org.modelix.authorization.installAuthentication
import org.modelix.model.client2.ModelClientV2
import org.modelix.model.server.Main.installStatusPages
Expand All @@ -38,7 +39,10 @@ suspend fun ApplicationTestBuilder.createModelClient(): ModelClientV2 {

fun Application.installDefaultServerPlugins() {
install(WebSockets)
install(ContentNegotiation) { json() }
install(ContentNegotiation) {
json()
registerJsonTypes()
}
install(Resources)
install(IgnoreTrailingSlash)
installStatusPages()
Expand Down
Loading

0 comments on commit 2abc6b0

Please sign in to comment.