Skip to content

Commit

Permalink
feat: handle root security requirements on root DSL (#101)
Browse files Browse the repository at this point in the history
  • Loading branch information
anthony-legay authored Nov 3, 2024
1 parent 1307dac commit 72d90e2
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 35 deletions.
30 changes: 25 additions & 5 deletions tegral-openapi/tegral-openapi-dsl/api/tegral-openapi-dsl.api
Original file line number Diff line number Diff line change
Expand Up @@ -199,16 +199,18 @@ public final class guru/zoroark/tegral/openapi/dsl/OperationBuilder : guru/zoroa
public fun response (ILkotlin/jvm/functions/Function1;)V
public fun security (Ljava/lang/String;)V
public fun security (Ljava/lang/String;[Ljava/lang/String;)V
public fun security (Lkotlin/jvm/functions/Function1;)V
public fun setDeprecated (Ljava/lang/Boolean;)V
public fun setDescription (Ljava/lang/String;)V
public fun setExternalDocsDescription (Ljava/lang/String;)V
public fun setExternalDocsUrl (Ljava/lang/String;)V
public fun setOperationId (Ljava/lang/String;)V
public fun setRequestBody (Lguru/zoroark/tegral/openapi/dsl/RequestBodyBuilder;)V
public fun setSecurityRequirements (Ljava/util/List;)V
public fun setSummary (Ljava/lang/String;)V
}

public abstract interface class guru/zoroark/tegral/openapi/dsl/OperationDsl {
public abstract interface class guru/zoroark/tegral/openapi/dsl/OperationDsl : guru/zoroark/tegral/openapi/dsl/SecurityDsl {
public abstract fun body (Lkotlin/jvm/functions/Function1;)V
public abstract fun cookieParameter (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public abstract fun getDeprecated ()Ljava/lang/Boolean;
Expand All @@ -219,15 +221,12 @@ public abstract interface class guru/zoroark/tegral/openapi/dsl/OperationDsl {
public abstract fun getParameters ()Ljava/util/List;
public abstract fun getRequestBody ()Lguru/zoroark/tegral/openapi/dsl/RequestBodyBuilder;
public abstract fun getResponses ()Ljava/util/Map;
public abstract fun getSecurityRequirements ()Ljava/util/List;
public abstract fun getSummary ()Ljava/lang/String;
public abstract fun getTags ()Ljava/util/List;
public abstract fun headerParameter (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public abstract fun pathParameter (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public abstract fun queryParameter (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public abstract fun response (ILkotlin/jvm/functions/Function1;)V
public abstract fun security (Ljava/lang/String;)V
public abstract fun security (Ljava/lang/String;[Ljava/lang/String;)V
public abstract fun setDeprecated (Ljava/lang/Boolean;)V
public abstract fun setDescription (Ljava/lang/String;)V
public abstract fun setExternalDocsDescription (Ljava/lang/String;)V
Expand Down Expand Up @@ -332,6 +331,7 @@ public final class guru/zoroark/tegral/openapi/dsl/PathBuilder : guru/zoroark/te
public fun response (ILkotlin/jvm/functions/Function1;)V
public fun security (Ljava/lang/String;)V
public fun security (Ljava/lang/String;[Ljava/lang/String;)V
public fun security (Lkotlin/jvm/functions/Function1;)V
public fun setDelete (Lguru/zoroark/tegral/openapi/dsl/OperationBuilder;)V
public fun setDeprecated (Ljava/lang/Boolean;)V
public fun setDescription (Ljava/lang/String;)V
Expand Down Expand Up @@ -479,6 +479,7 @@ public final class guru/zoroark/tegral/openapi/dsl/RootBuilder : guru/zoroark/te
public fun getLicenseIdentifier ()Ljava/lang/String;
public fun getLicenseName ()Ljava/lang/String;
public fun getLicenseUrl ()Ljava/lang/String;
public fun getSecurityRequirements ()Ljava/util/List;
public fun getSummary ()Ljava/lang/String;
public fun getTermsOfService ()Ljava/lang/String;
public fun getTitle ()Ljava/lang/String;
Expand All @@ -489,6 +490,9 @@ public final class guru/zoroark/tegral/openapi/dsl/RootBuilder : guru/zoroark/te
public fun patch (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public fun post (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public fun put (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public fun security (Ljava/lang/String;)V
public fun security (Ljava/lang/String;[Ljava/lang/String;)V
public fun security (Lkotlin/jvm/functions/Function1;)V
public fun securityScheme (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public fun server (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public fun setContactEmail (Ljava/lang/String;)V
Expand All @@ -500,14 +504,15 @@ public final class guru/zoroark/tegral/openapi/dsl/RootBuilder : guru/zoroark/te
public fun setLicenseIdentifier (Ljava/lang/String;)V
public fun setLicenseName (Ljava/lang/String;)V
public fun setLicenseUrl (Ljava/lang/String;)V
public fun setSecurityRequirements (Ljava/util/List;)V
public fun setSummary (Ljava/lang/String;)V
public fun setTermsOfService (Ljava/lang/String;)V
public fun setTitle (Ljava/lang/String;)V
public fun setVersion (Ljava/lang/String;)V
public fun tag (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
}

public abstract interface class guru/zoroark/tegral/openapi/dsl/RootDsl : guru/zoroark/tegral/openapi/dsl/InfoDsl, guru/zoroark/tegral/openapi/dsl/PathsDsl, guru/zoroark/tegral/openapi/dsl/TagsDsl {
public abstract interface class guru/zoroark/tegral/openapi/dsl/RootDsl : guru/zoroark/tegral/openapi/dsl/InfoDsl, guru/zoroark/tegral/openapi/dsl/PathsDsl, guru/zoroark/tegral/openapi/dsl/SecurityDsl, guru/zoroark/tegral/openapi/dsl/TagsDsl {
public abstract fun getExternalDocsDescription ()Ljava/lang/String;
public abstract fun getExternalDocsUrl ()Ljava/lang/String;
public abstract fun securityScheme (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
Expand All @@ -516,6 +521,21 @@ public abstract interface class guru/zoroark/tegral/openapi/dsl/RootDsl : guru/z
public abstract fun setExternalDocsUrl (Ljava/lang/String;)V
}

public abstract interface class guru/zoroark/tegral/openapi/dsl/SecurityDsl {
public abstract fun getSecurityRequirements ()Ljava/util/List;
public abstract fun security (Ljava/lang/String;)V
public abstract fun security (Ljava/lang/String;[Ljava/lang/String;)V
public abstract fun security (Lkotlin/jvm/functions/Function1;)V
}

public final class guru/zoroark/tegral/openapi/dsl/SecurityRequirementsBuilder : guru/zoroark/tegral/core/Buildable {
public fun <init> ()V
public fun build ()Lio/swagger/v3/oas/models/security/SecurityRequirement;
public synthetic fun build ()Ljava/lang/Object;
public final fun requirement (Ljava/lang/String;)V
public final fun requirement (Ljava/lang/String;[Ljava/lang/String;)V
}

public final class guru/zoroark/tegral/openapi/dsl/SecuritySchemeBuilder : guru/zoroark/tegral/core/Buildable, guru/zoroark/tegral/openapi/dsl/SecuritySchemeDsl {
public fun <init> ()V
public fun build ()Lio/swagger/v3/oas/models/security/SecurityScheme;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import io.swagger.v3.oas.models.security.SecurityRequirement
* Note that the `externalDocs` object is embedded in this DSL.
*/
@TegralDsl
interface OperationDsl {
interface OperationDsl : SecurityDsl {
/**
* A short summary of what the operation does.
*/
Expand Down Expand Up @@ -86,16 +86,6 @@ interface OperationDsl {
@TegralDsl
val parameters: MutableList<Buildable<Parameter>>

/**
* A declaration of which security mechanisms can be used for this operation.
*
* - This list behaves like an "OR", only one needs to be fulfilled for the operation.
* - Requirements defined in the individual `SecurityRequirement` objects behave like an "AND", and all of them need
* to be fulfilled.
*/
@TegralDsl
val securityRequirements: MutableList<SecurityRequirement>

/**
* The list of possible responses as they are returned from executing this operation.
*/
Expand All @@ -109,18 +99,6 @@ interface OperationDsl {
@TegralDsl
val tags: MutableList<String>

/**
* Adds a security requirement object to this operation with the given key.
*/
@TegralDsl
fun security(key: String)

/**
* Adds a security requirement object to this operation with the given key and scopes.
*/
@TegralDsl
fun security(key: String, vararg scopes: String)

/**
* Creates a response for the given response code (passed as an integer value).
*/
Expand Down Expand Up @@ -172,14 +150,12 @@ class OperationBuilder(private val context: OpenApiDslContext) : OperationDsl, B
override var requestBody: RequestBodyBuilder? = null
override var deprecated: Boolean? = null
override var operationId: String? = null
override var securityRequirements = mutableListOf<SecurityRequirement>()

// TODO callbacks, servers

override val tags = mutableListOf<String>()
override val parameters = mutableListOf<Buildable<Parameter>>()
override val securityRequirements = mutableListOf<SecurityRequirement>()

// TODO properly support AND scenarios between security requirements (right now it's OR only)

override fun security(key: String) {
securityRequirements.add(SecurityRequirement().addList(key))
Expand All @@ -189,6 +165,10 @@ class OperationBuilder(private val context: OpenApiDslContext) : OperationDsl, B
securityRequirements.add(SecurityRequirement().addList(key, scopes.toList()))
}

override fun security(builder: SecurityRequirementsBuilder.() -> Unit) {
securityRequirements.add(SecurityRequirementsBuilder().apply(builder).build())
}

override infix fun Int.response(builder: ResponseDsl.() -> Unit) {
responses[this] = ResponseBuilder(context).apply(builder)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,13 @@ class PathBuilder(private val context: OpenApiDslContext) : PathDsl, Buildable<P
}
override val parameters: MutableList<Buildable<Parameter>>
get() = error(WRITE_ONLY_ERROR_MSG)

override val securityRequirements: MutableList<SecurityRequirement>
get() = error(WRITE_ONLY_ERROR_MSG)

override val responses: MutableMap<Int, Buildable<ApiResponse>>
get() = error(WRITE_ONLY_ERROR_MSG)

override val tags: MutableList<String> = mutableListOf()

override fun security(key: String) {
Expand All @@ -229,6 +232,10 @@ class PathBuilder(private val context: OpenApiDslContext) : PathDsl, Buildable<P
addOperationDefault { security(key, *scopes) }
}

override fun security(builder: SecurityRequirementsBuilder.() -> Unit) {
addOperationDefault { security(builder) }
}

override fun Int.response(builder: ResponseDsl.() -> Unit) {
addOperationDefault { this@response.response(builder) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.swagger.v3.oas.models.Components
import io.swagger.v3.oas.models.ExternalDocumentation
import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.info.Info
import io.swagger.v3.oas.models.security.SecurityRequirement
import io.swagger.v3.oas.models.servers.Server

/**
Expand All @@ -32,12 +33,13 @@ import io.swagger.v3.oas.models.servers.Server
* - [Info][InfoDsl] (embedded)
* - [Tags][TagsDsl] (embedded)
* - [Paths][PathsDsl] (embedded)
* - [Security][SecurityDsl] (embedded)
* - External documentation ([description][externalDocsDescription] and [url][externalDocsUrl])
*
* (Items marked as embedded are separate DSL interfaces that are available in [RootDsl] and can be used directly).
*/
@TegralDsl
interface RootDsl : InfoDsl, TagsDsl, PathsDsl {
interface RootDsl : InfoDsl, TagsDsl, PathsDsl, SecurityDsl {
/**
* Adds a security scheme to this OpenAPI document with the given string as the name, using the lambda to configure
* further options.
Expand Down Expand Up @@ -76,7 +78,7 @@ class RootBuilder(
) : RootDsl, InfoDsl by infoBuilder, PathsDsl by paths, Buildable<OpenAPI> {
private val tags = mutableListOf<TagBuilder>()
private val servers = mutableListOf<Buildable<Server>>()

override var securityRequirements = mutableListOf<SecurityRequirement>()
override var externalDocsDescription: String? = null
override var externalDocsUrl: String? = null

Expand All @@ -93,6 +95,18 @@ class RootBuilder(
servers.add(serverBuilder)
}

override fun security(key: String) {
securityRequirements.add(SecurityRequirement().addList(key))
}

override fun security(key: String, vararg scopes: String) {
securityRequirements.add(SecurityRequirement().addList(key, scopes.toList()))
}

override fun security(builder: SecurityRequirementsBuilder.() -> Unit) {
securityRequirements.add(SecurityRequirementsBuilder().apply(builder).build())
}

override fun build(): OpenAPI = OpenAPI().apply {
tags = this@RootBuilder.tags.map { it.build() }.ifEmpty { null }
// In case the info part is completely empty, output 'null' to avoid getting an empty, useless object.
Expand All @@ -114,7 +128,7 @@ class RootBuilder(
description = externalDocsDescription
}
}

security = this@RootBuilder.securityRequirements.ifEmpty { null }
servers = this@RootBuilder.servers.map { it.build() }.ifEmpty { null }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package guru.zoroark.tegral.openapi.dsl

import guru.zoroark.tegral.core.Buildable
import guru.zoroark.tegral.core.TegralDsl
import io.swagger.v3.oas.models.security.SecurityRequirement

/**
* DSL for the [security item object](https://spec.openapis.org/oas/v3.1.0#security-requirement-object).
*
* Can be defined at root level (see [RootDsl]) and operation level (see [OperationDsl]).
*/
@TegralDsl
interface SecurityDsl {
/**
* A declaration of which security mechanisms can be used for this operation.
*
* - This list behaves like an "OR", only one needs to be fulfilled for the operation.
* - Requirements defined in the individual `SecurityRequirement` objects behave like an "AND", and all of them need
* to be fulfilled.
*/
@TegralDsl
val securityRequirements: MutableList<SecurityRequirement>

/**
* Adds a security requirement object to this operation with the given key.
*/
@TegralDsl
fun security(key: String)

/**
* Adds a security requirement object to this operation with the given key and scopes.
*/
@TegralDsl
fun security(key: String, vararg scopes: String)

/**
* Adds a security requirement object using the provided builder.
* Allows to define multiple requirements (which behave like an "AND", and all of them need to be fulfilled).
*/
@TegralDsl
fun security(builder: SecurityRequirementsBuilder.() -> Unit)
}

/**
* Builder for [SecurityRequirement]
*/
class SecurityRequirementsBuilder : Buildable<SecurityRequirement> {
private val securityRequirement = SecurityRequirement()

/**
* Adds a security requirement object with the given key.
*/
fun requirement(key: String) {
securityRequirement.addList(key)
}

/**
* Adds a security requirement object with the given key and scopes.
*/
fun requirement(key: String, vararg scopes: String) {
securityRequirement.addList(key, scopes.asList())
}

override fun build(): SecurityRequirement = securityRequirement
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class OperationBuilderTest {
operationId = "myOperation"
security("sec-one")
security("sec-two", "scope-a", "scope-b")
security {
requirement("sec-three", "scope-c")
requirement("sec-four", "scope-d")
}
tags += "tag-alpha"
"pathParam" pathParameter {}
"headerParam" headerParameter {}
Expand All @@ -64,6 +68,10 @@ class OperationBuilderTest {
},
SecurityRequirement().apply {
addList("sec-two", listOf("scope-a", "scope-b"))
},
SecurityRequirement().apply {
addList("sec-three", listOf("scope-c"))
addList("sec-four", listOf("scope-d"))
}
)
tags = listOf("tag-alpha")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ class PathsBuilderTest {
assertEquals(expected, paths)
}

@Suppress("LongMethod")
@Test
fun `Add everything via definition at path level`() {
val paths = PathsBuilder(mockk()).apply {
Expand All @@ -242,6 +243,8 @@ class PathsBuilderTest {
operationId = "up"
deprecated = true
security("never")
security("gonna", "let")
security { requirement("you", "down") }
200 response {
description = "gonna"
}
Expand Down Expand Up @@ -276,14 +279,16 @@ class PathsBuilderTest {
assertNotNull(path.post)
)
for (op in operations) {
assertEquals(1, op.security.size)
assertEquals(3, op.security.size)

assertEquals("Never", op.summary)
assertEquals("gonna", op.description)
assertEquals("give", op.externalDocs.description)
assertEquals("you", op.externalDocs.url)
assertEquals("up", op.operationId)
assertEquals(emptyList(), op.security[0]["never"])
assertEquals(listOf("let"), op.security[1]["gonna"])
assertEquals(listOf("down"), op.security[2]["you"])
assertTrue(op.deprecated)
assertEquals("gonna", op.responses["200"]?.description)
assertEquals(4, op.parameters.size)
Expand Down
Loading

0 comments on commit 72d90e2

Please sign in to comment.