Skip to content
Open
7 changes: 7 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ tasks.wrapper {
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}
sourceSets {
main {
// bundles the official OMG spec (resources/openapi.json) so the
// /meta/datatypes endpoints can serve its schema definitions
resources.srcDir("resources")
}
}
kotlin {
jvmToolchain(21)
}
Expand Down
18 changes: 14 additions & 4 deletions src/main/kotlin/org/openmbee/flexo/sysmlv2/AppMain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.ktor.server.routing.*
import kotlinx.serialization.json.Json
import io.ktor.server.application.ApplicationStopped
import org.openmbee.flexo.sysmlv2.apis.*
import org.openmbee.flexo.sysmlv2.models.Error

lateinit var GlobalFlexoConfig: FlexoConfig
lateinit var FlexoHttpClient: HttpClient
Expand Down Expand Up @@ -105,17 +106,26 @@ fun Application.module() {
//install(HSTS, ApplicationHstsConfiguration()) // see https://ktor.io/docs/hsts.html
install(Resources)
install(StatusPages) {
// failures respond with the spec's Error shape so clients can
// parse them uniformly
exception<InvalidSysmlSerializationError> { call, cause ->
call.respondText(cause.message ?: "Bad Request", status = HttpStatusCode.BadRequest)
call.respond(HttpStatusCode.BadRequest,
Error(Error.AtType.Error, cause.message ?: "Bad Request"))
}
exception<BadRequestException> { call, cause ->
call.respondText(cause.message ?: "Bad Request", status = HttpStatusCode.BadRequest)
call.respond(HttpStatusCode.BadRequest,
Error(Error.AtType.Error, cause.message ?: "Bad Request"))
}
exception<NotImplementedError> { call, cause ->
call.respondText(cause.message ?: "Not Implemented", status = HttpStatusCode.NotImplemented)
call.respond(HttpStatusCode.NotImplemented,
Error(Error.AtType.Error, cause.message ?: "Not Implemented"))
}
exception<Throwable> { call, cause ->
call.respondText(cause.message ?: "Internal Server Error", status = HttpStatusCode.InternalServerError)
// do not leak internal details (exception messages may contain
// IRIs, paths, or upstream internals)
call.application.log.error("unhandled exception", cause)
call.respond(HttpStatusCode.InternalServerError,
Error(Error.AtType.Error, "Internal Server Error"))
}
}
routing {
Expand Down
29 changes: 29 additions & 0 deletions src/main/kotlin/org/openmbee/flexo/sysmlv2/Http.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,39 @@
package org.openmbee.flexo.sysmlv2

import io.ktor.http.*
import io.ktor.server.plugins.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*


suspend fun RoutingContext.notImplemented() {
call.respondText("Not implemented", status= HttpStatusCode.NotImplemented)
}

/**
* Respond with a cursor page of [items]: ordered by id, resuming after
* page[after], bounded by page[size], with a Link rel="next" header when
* more items remain. With neither param set, responds with the unpaged
* historical list untouched.
*/
suspend inline fun <reified T : Any> RoutingContext.respondPage(
items: List<T>,
pageSize: Int?,
pageAfter: String?,
crossinline idOf: (T) -> String
) {
if (pageSize == null && pageAfter == null) {
return call.respond(items)
}
if (pageSize != null && pageSize < 1) {
throw BadRequestException("page[size] must be a positive integer")
}
val remaining = items.sortedBy(idOf).filter { pageAfter == null || idOf(it) > pageAfter }
val page = if (pageSize == null) remaining else remaining.take(pageSize)
if (pageSize != null && remaining.size > pageSize) {
val nextUrl = "${call.request.path()}?page%5Bsize%5D=$pageSize&page%5Bafter%5D=${idOf(page.last())}"
call.response.headers.append(HttpHeaders.Link, "<$nextUrl>; rel=\"next\"")
}
call.respond(page)
}
30 changes: 15 additions & 15 deletions src/main/kotlin/org/openmbee/flexo/sysmlv2/Paths.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects/{projectId}/branches") class getBranchesByProject(val projectId: kotlin.String, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects/{projectId}/branches") class getBranchesByProject(val projectId: kotlin.String, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Get branch by project and ID
Expand Down Expand Up @@ -71,7 +71,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects/{projectId}/commits/{commitId}/changes") class getChangesByProjectCommit(val projectId: kotlin.String, val commitId: kotlin.String, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects/{projectId}/commits/{commitId}/changes") class getChangesByProjectCommit(val projectId: kotlin.String, val commitId: kotlin.String, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Get commit by project and ID
Expand All @@ -89,7 +89,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects/{projectId}/commits") class getCommitsByProject(val projectId: kotlin.String, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects/{projectId}/commits") class getCommitsByProject(val projectId: kotlin.String, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Create commit by project
Expand All @@ -110,7 +110,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects/{projectId}/commits/{compareCommitId}/diff") class diff(val projectId: kotlin.String, val baseCommitId: kotlin.String, val compareCommitId: kotlin.String, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects/{projectId}/commits/{compareCommitId}/diff") class diff(val projectId: kotlin.String, val baseCommitId: kotlin.String, val compareCommitId: kotlin.String, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Merge source commit(s) into a target branch
Expand All @@ -124,7 +124,7 @@ object Paths {
* @param pageSize Page size (optional)
* @param &#x60;data&#x60; (optional)
*/
@Serializable @Resource("/projects/{projectId}/branches/{targetBranchId}/merge") class merge(val projectId: kotlin.String, val sourceCommitId: kotlin.collections.List<kotlin.String>, val targetBranchId: kotlin.String, val description: kotlin.String? = null, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null, val `data`: kotlin.collections.List<JsonObject>? = null)
@Serializable @Resource("/projects/{projectId}/branches/{targetBranchId}/merge") class merge(val projectId: kotlin.String, val sourceCommitId: kotlin.collections.List<kotlin.String>, val targetBranchId: kotlin.String, val description: kotlin.String? = null, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null, val `data`: kotlin.collections.List<JsonObject>? = null)

/**
* Get element by project, commit and ID
Expand All @@ -146,7 +146,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects/{projectId}/commits/{commitId}/elements") class getElementsByProjectCommit(val projectId: kotlin.String, val commitId: kotlin.String, val excludeUsed: kotlin.Boolean? = null, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects/{projectId}/commits/{commitId}/elements") class getElementsByProjectCommit(val projectId: kotlin.String, val commitId: kotlin.String, val excludeUsed: kotlin.Boolean? = null, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Get ProjectUsage that originates the provided element
Expand All @@ -167,7 +167,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects/{projectId}/commits/{commitId}/roots") class getRootsByProjectCommit(val projectId: kotlin.String, val commitId: kotlin.String, val excludeUsed: kotlin.Boolean? = null, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects/{projectId}/commits/{commitId}/roots") class getRootsByProjectCommit(val projectId: kotlin.String, val commitId: kotlin.String, val excludeUsed: kotlin.Boolean? = null, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Get datatype by ID
Expand All @@ -183,7 +183,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/meta/datatypes") class getDatatypes(val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/meta/datatypes") class getDatatypes(@SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Delete project by ID
Expand All @@ -206,7 +206,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects") class getProjects(val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects") class getProjects(@SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Create project
Expand Down Expand Up @@ -239,7 +239,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects/{projectId}/queries") class getQueriesByProject(val projectId: kotlin.String, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects/{projectId}/queries") class getQueriesByProject(val projectId: kotlin.String, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Get query by project and ID
Expand All @@ -259,7 +259,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects/{projectId}/query-results") class getQueryResultsByProjectIdQuery(val projectId: kotlin.String, val queryRequest: QueryRequest, val commitId: kotlin.String? = null, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects/{projectId}/query-results") class getQueryResultsByProjectIdQuery(val projectId: kotlin.String, val queryRequest: QueryRequest, val commitId: kotlin.String? = null, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Get query results by project and query
Expand All @@ -271,7 +271,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects/{projectId}/queries/{queryId}/results") class getQueryResultsByProjectIdQueryId(val projectId: kotlin.String, val queryId: kotlin.String, val commitId: kotlin.String? = null, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects/{projectId}/queries/{queryId}/results") class getQueryResultsByProjectIdQueryId(val projectId: kotlin.String, val queryId: kotlin.String, val commitId: kotlin.String? = null, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Get query results by project and query definition via POST
Expand All @@ -283,7 +283,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects/{projectId}/query-results") class getQueryResultsByProjectIdQueryPost(val projectId: kotlin.String, val queryRequest: QueryRequest, val commitId: kotlin.String? = null, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects/{projectId}/query-results") class getQueryResultsByProjectIdQueryPost(val projectId: kotlin.String, val queryRequest: QueryRequest, val commitId: kotlin.String? = null, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Create query by project
Expand Down Expand Up @@ -314,7 +314,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects/{projectId}/commits/{commitId}/elements/{relatedElementId}/relationships") class getRelationshipsByProjectCommitRelatedElement(val projectId: kotlin.String, val commitId: kotlin.String, val relatedElementId: kotlin.String, val direction: kotlin.String? = null, val excludeUsed: kotlin.Boolean? = null, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects/{projectId}/commits/{commitId}/elements/{relatedElementId}/relationships") class getRelationshipsByProjectCommitRelatedElement(val projectId: kotlin.String, val commitId: kotlin.String, val relatedElementId: kotlin.String, val direction: kotlin.String? = null, val excludeUsed: kotlin.Boolean? = null, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Delete tag by project and ID
Expand All @@ -340,7 +340,7 @@ object Paths {
* @param pageBefore Page before (optional)
* @param pageSize Page size (optional)
*/
@Serializable @Resource("/projects/{projectId}/tags") class getTagsByProject(val projectId: kotlin.String, val pageAfter: kotlin.String? = null, val pageBefore: kotlin.String? = null, val pageSize: kotlin.Int? = null)
@Serializable @Resource("/projects/{projectId}/tags") class getTagsByProject(val projectId: kotlin.String, @SerialName("page[after]") val pageAfter: kotlin.String? = null, @SerialName("page[before]") val pageBefore: kotlin.String? = null, @SerialName("page[size]") val pageSize: kotlin.Int? = null)

/**
* Create tag by project
Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/org/openmbee/flexo/sysmlv2/apis/BranchApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,14 @@ fun Route.BranchApi() {
return@get forward(flexoResponse)
}
// parse the response model, convert it to JSON, and reply to client
call.respond(flexoResponse.parseModel {
val branches = flexoResponse.parseModel {
model.listSubjectsWithProperty(RDF.type, MMS.Branch).filterDrop {
(it.hasProperty(SYSMLV2.DELETED) || it.uri.uriSuffix == "master")
}.mapWith {
branchFromResponse(it.outgoing(), path.projectId, it.uri.uriSuffix)
}.toList().filterNotNull()
})
}
respondPage(branches, path.pageSize, path.pageAfter) { it.atId }
}

get<Paths.getBranchByProjectAndId> { path ->
Expand Down
Loading