Skip to content

Commit

Permalink
Merge pull request #862 from modelix/feature/media-type-versioning
Browse files Browse the repository at this point in the history
MODELIX-949: media type versioning + branch meta data access
  • Loading branch information
languitar authored Jul 2, 2024
2 parents 43922d7 + 2abc6b0 commit 92ce337
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 92ce337

Please sign in to comment.