Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MODELIX-949: media type versioning + branch meta data access #862

Merged
merged 1 commit into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading