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

feat: handle root security requirements on root DSL #101

Merged
merged 8 commits into from
Nov 3, 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
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 { [email protected](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 = [email protected] { 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 = [email protected] { null }
servers = [email protected] { 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