From 489eecde06f8f6a51194e7b58b69d205d4146ff4 Mon Sep 17 00:00:00 2001 From: javakky Date: Tue, 6 Sep 2022 16:27:45 +0900 Subject: [PATCH] feat: make it a plugin option --- .../playSwagger/DefinitionGenerator.scala | 55 +++++++++++-------- .../playSwagger/SwaggerSpecGenerator.scala | 11 ++-- .../playSwagger/SwaggerSpecRunner.scala | 6 +- .../playSwagger/DefinitionGeneratorSpec.scala | 41 ++++++++++---- .../SwaggerSpecGeneratorSpec.scala | 15 +++-- .../iheart/sbtPlaySwagger/SwaggerKeys.scala | 6 ++ .../iheart/sbtPlaySwagger/SwaggerPlugin.scala | 3 +- 7 files changed, 90 insertions(+), 47 deletions(-) diff --git a/core/src/main/scala/com/iheart/playSwagger/DefinitionGenerator.scala b/core/src/main/scala/com/iheart/playSwagger/DefinitionGenerator.scala index d5a8d335..2483c8b9 100644 --- a/core/src/main/scala/com/iheart/playSwagger/DefinitionGenerator.scala +++ b/core/src/main/scala/com/iheart/playSwagger/DefinitionGenerator.scala @@ -21,7 +21,8 @@ final case class DefinitionGenerator( mappings: CustomMappings = Nil, swaggerPlayJava: Boolean = false, _mapper: ObjectMapper = new ObjectMapper(), - namingStrategy: NamingStrategy = NamingStrategy.None + namingStrategy: NamingStrategy = NamingStrategy.None, + embedScaladoc: Boolean = false )(implicit cl: ClassLoader) { private val refinedTypePattern = raw"(eu\.timepit\.refined\.api\.Refined(?:\[.+])?)".r @@ -70,27 +71,31 @@ final case class DefinitionGenerator( case m: MethodSymbol if m.isPrimaryConstructor ⇒ m }.toList.flatMap(_.paramLists).headOption.getOrElse(Nil) - val scaladoc = for { - annotation <- tpe.typeSymbol.annotations - if typeOf[Scaladoc] == annotation.tree.tpe - value <- annotation.tree.children.tail.headOption - docTree <- value.children.tail.headOption - docString = docTree.toString().tail.init.replace("\\n", "\n") - doc <- ScaladocParser.parse(docString) - } yield doc - - val paramDescriptions = (for { - doc <- scaladoc - paragraph <- doc.para - term <- paragraph.terms - tag <- term match { - case iScaladoc.Tag(iScaladoc.TagType.Param, Some(iScaladoc.Word(key)), Seq(text)) => - Some(key -> text) - case _ => None - } - } yield tag).map { - case (name, term) => name -> scalaDocToMarkdown(term).toString - }.toMap + val paramDescriptions = if (embedScaladoc) { + val scaladoc = for { + annotation <- tpe.typeSymbol.annotations + if typeOf[Scaladoc] == annotation.tree.tpe + value <- annotation.tree.children.tail.headOption + docTree <- value.children.tail.headOption + docString = docTree.toString().tail.init.replace("\\n", "\n") + doc <- ScaladocParser.parse(docString) + } yield doc + + (for { + doc <- scaladoc + paragraph <- doc.para + term <- paragraph.terms + tag <- term match { + case iScaladoc.Tag(iScaladoc.TagType.Param, Some(iScaladoc.Word(key)), Seq(text)) => + Some(key -> text) + case _ => None + } + } yield tag).map { + case (name, term) => name -> scalaDocToMarkdown(term).toString + }.toMap + } else { + Map.empty[String, String] + } fields.map { field: Symbol ⇒ // TODO: find a better way to get the string representation of typeSignature @@ -194,11 +199,13 @@ object DefinitionGenerator { def apply( domainNameSpace: String, customParameterTypeMappings: CustomMappings, - namingStrategy: NamingStrategy + namingStrategy: NamingStrategy, + embedScaladoc: Boolean )(implicit cl: ClassLoader): DefinitionGenerator = DefinitionGenerator( PrefixDomainModelQualifier(domainNameSpace), customParameterTypeMappings, - namingStrategy = namingStrategy + namingStrategy = namingStrategy, + embedScaladoc = embedScaladoc ) } diff --git a/core/src/main/scala/com/iheart/playSwagger/SwaggerSpecGenerator.scala b/core/src/main/scala/com/iheart/playSwagger/SwaggerSpecGenerator.scala index 4beb3a73..8c0dbc7d 100644 --- a/core/src/main/scala/com/iheart/playSwagger/SwaggerSpecGenerator.scala +++ b/core/src/main/scala/com/iheart/playSwagger/SwaggerSpecGenerator.scala @@ -38,13 +38,14 @@ object SwaggerSpecGenerator { ) } - def apply(swaggerV3: Boolean, operationIdFully: Boolean, domainNameSpaces: String*)(implicit + def apply(swaggerV3: Boolean, operationIdFully: Boolean, embedScaladoc: Boolean, domainNameSpaces: String*)(implicit cl: ClassLoader): SwaggerSpecGenerator = { SwaggerSpecGenerator( NamingStrategy.None, PrefixDomainModelQualifier(domainNameSpaces: _*), swaggerV3 = swaggerV3, - operationIdFully = operationIdFully + operationIdFully = operationIdFully, + embedScaladoc = embedScaladoc ) } def apply(outputTransformers: Seq[OutputTransformer], domainNameSpaces: String*)(implicit @@ -69,7 +70,8 @@ final case class SwaggerSpecGenerator( swaggerV3: Boolean = false, swaggerPlayJava: Boolean = false, apiVersion: Option[String] = None, - operationIdFully: Boolean = false + operationIdFully: Boolean = false, + embedScaladoc: Boolean = false )(implicit cl: ClassLoader) { import SwaggerSpecGenerator.{MissingBaseSpecException, baseSpecFileName, customMappingsFileName} @@ -202,7 +204,8 @@ final case class SwaggerSpecGenerator( modelQualifier = modelQualifier, mappings = customMappings, swaggerPlayJava = swaggerPlayJava, - namingStrategy = namingStrategy + namingStrategy = namingStrategy, + embedScaladoc = embedScaladoc ).allDefinitions(referredClasses) } diff --git a/core/src/main/scala/com/iheart/playSwagger/SwaggerSpecRunner.scala b/core/src/main/scala/com/iheart/playSwagger/SwaggerSpecRunner.scala index ad4c4fdf..bd4df510 100644 --- a/core/src/main/scala/com/iheart/playSwagger/SwaggerSpecRunner.scala +++ b/core/src/main/scala/com/iheart/playSwagger/SwaggerSpecRunner.scala @@ -9,12 +9,13 @@ import play.api.libs.json.{JsValue, Json} object SwaggerSpecRunner extends App { implicit def cl: ClassLoader = getClass.getClassLoader - val targetFile :: routesFile :: domainNameSpaceArgs :: outputTransformersArgs :: swaggerV3String :: apiVersion :: swaggerPrettyJson :: swaggerPlayJavaString :: namingStrategy :: operationIdNamingFullyString :: Nil = + val targetFile :: routesFile :: domainNameSpaceArgs :: outputTransformersArgs :: swaggerV3String :: apiVersion :: swaggerPrettyJson :: swaggerPlayJavaString :: namingStrategy :: operationIdNamingFullyString :: embedScaladocString :: Nil = args.toList private def fileArg = Paths.get(targetFile) private def swaggerJson = { val swaggerV3 = java.lang.Boolean.parseBoolean(swaggerV3String) val swaggerOperationIdNamingFully = java.lang.Boolean.parseBoolean(operationIdNamingFullyString) + val embedScaladoc = java.lang.Boolean.parseBoolean(embedScaladocString) val swaggerPlayJava = java.lang.Boolean.parseBoolean(swaggerPlayJavaString) val domainModelQualifier = PrefixDomainModelQualifier(domainNameSpaceArgs.split(","): _*) val transformersStrs: Seq[String] = if (outputTransformersArgs.isEmpty) Seq() else outputTransformersArgs.split(",") @@ -36,7 +37,8 @@ object SwaggerSpecRunner extends App { swaggerV3 = swaggerV3, swaggerPlayJava = swaggerPlayJava, apiVersion = Some(apiVersion), - operationIdFully = swaggerOperationIdNamingFully + operationIdFully = swaggerOperationIdNamingFully, + embedScaladoc = embedScaladoc ).generate(routesFile).get if (swaggerPrettyJson.toBoolean) Json.prettyPrint(swaggerSpec) diff --git a/core/src/test/scala/com/iheart/playSwagger/DefinitionGeneratorSpec.scala b/core/src/test/scala/com/iheart/playSwagger/DefinitionGeneratorSpec.scala index bceae979..75408216 100644 --- a/core/src/test/scala/com/iheart/playSwagger/DefinitionGeneratorSpec.scala +++ b/core/src/test/scala/com/iheart/playSwagger/DefinitionGeneratorSpec.scala @@ -69,7 +69,12 @@ class DefinitionGeneratorSpec extends Specification { "generate properties" >> { - val result = DefinitionGenerator("com.iheart.playSwagger", Nil, NamingStrategy.None).definition[Foo].properties + val result = DefinitionGenerator( + "com.iheart.playSwagger", + Nil, + NamingStrategy.None, + embedScaladoc = false + ).definition[Foo].properties result.length === 7 @@ -118,7 +123,9 @@ class DefinitionGeneratorSpec extends Specification { "generate properties using snake case naming strategy" >> { val result = - DefinitionGenerator("com.iheart.playSwagger", Nil, NamingStrategy.SnakeCase).definition[Foo].properties + DefinitionGenerator("com.iheart.playSwagger", Nil, NamingStrategy.SnakeCase, embedScaladoc = false).definition[ + Foo + ].properties result.length === 7 @@ -167,7 +174,9 @@ class DefinitionGeneratorSpec extends Specification { "generate properties using kebab case naming strategy" >> { val result = - DefinitionGenerator("com.iheart.playSwagger", Nil, NamingStrategy.KebabCase).definition[Foo].properties + DefinitionGenerator("com.iheart.playSwagger", Nil, NamingStrategy.KebabCase, embedScaladoc = false).definition[ + Foo + ].properties result.length === 7 @@ -214,14 +223,14 @@ class DefinitionGeneratorSpec extends Specification { } "read class in Object" >> { - val result = DefinitionGenerator("com.iheart", Nil, NamingStrategy.None).definition( + val result = DefinitionGenerator("com.iheart", Nil, NamingStrategy.None, embedScaladoc = false).definition( "com.iheart.playSwagger.MyObject.MyInnerClass" ) result.properties.head.name === "bar" } "read alias type in Object" >> { - val result = DefinitionGenerator("com.iheart", Nil, NamingStrategy.None).definition( + val result = DefinitionGenerator("com.iheart", Nil, NamingStrategy.None, embedScaladoc = false).definition( "com.iheart.playSwagger.MyObject.MyInnerClass" ) @@ -233,14 +242,16 @@ class DefinitionGeneratorSpec extends Specification { "read sequence items" >> { val result = - DefinitionGenerator("com.iheart", Nil, NamingStrategy.None).definition("com.iheart.playSwagger.FooWithSeq") + DefinitionGenerator("com.iheart", Nil, NamingStrategy.None, embedScaladoc = false).definition( + "com.iheart.playSwagger.FooWithSeq" + ) result.properties.head.asInstanceOf[GenSwaggerParameter].items.get.asInstanceOf[ GenSwaggerParameter ].referenceType === Some("com.iheart.playSwagger.SeqItem") } "read primitive sequence items" >> { - val result = DefinitionGenerator("com.iheart", Nil, NamingStrategy.None).definition( + val result = DefinitionGenerator("com.iheart", Nil, NamingStrategy.None, embedScaladoc = false).definition( "com.iheart.playSwagger.WithListOfPrimitive" ) result.properties.head.asInstanceOf[GenSwaggerParameter].items.get.asInstanceOf[ @@ -251,7 +262,9 @@ class DefinitionGeneratorSpec extends Specification { "read Optional items " >> { val result = - DefinitionGenerator("com.iheart", Nil, NamingStrategy.None).definition("com.iheart.playSwagger.FooWithOption") + DefinitionGenerator("com.iheart", Nil, NamingStrategy.None, embedScaladoc = false).definition( + "com.iheart.playSwagger.FooWithOption" + ) result.properties.head.asInstanceOf[GenSwaggerParameter].referenceType must beSome( "com.iheart.playSwagger.OptionItem" ) @@ -260,7 +273,9 @@ class DefinitionGeneratorSpec extends Specification { "with dates" >> { "no override" >> { val result = - DefinitionGenerator("com.iheart", Nil, NamingStrategy.None).definition("com.iheart.playSwagger.WithDate") + DefinitionGenerator("com.iheart", Nil, NamingStrategy.None, embedScaladoc = false).definition( + "com.iheart.playSwagger.WithDate" + ) val prop = result.properties.head.asInstanceOf[GenSwaggerParameter] prop.`type` must beSome("integer") prop.format must beSome("epoch") @@ -275,7 +290,9 @@ class DefinitionGeneratorSpec extends Specification { ) ) val result = - DefinitionGenerator("com.iheart", mappings, NamingStrategy.None).definition("com.iheart.playSwagger.WithDate") + DefinitionGenerator("com.iheart", mappings, NamingStrategy.None, embedScaladoc = false).definition( + "com.iheart.playSwagger.WithDate" + ) val prop = result.properties.head.asInstanceOf[CustomSwaggerParameter] prop.specAsParameter === customJson } @@ -289,7 +306,7 @@ class DefinitionGeneratorSpec extends Specification { specAsParameter = customJson ) ) - val result = DefinitionGenerator("com.iheart", mappings, NamingStrategy.None).definition( + val result = DefinitionGenerator("com.iheart", mappings, NamingStrategy.None, embedScaladoc = false).definition( "com.iheart.playSwagger.WithOptionalDate" ) val prop = result.properties.head.asInstanceOf[CustomSwaggerParameter] @@ -304,7 +321,7 @@ class DefinitionGeneratorSpec extends Specification { `type` = "com.iheart.playSwagger.WrappedString", specAsParameter = customJson ) - val generator = DefinitionGenerator("com.iheart", List(customMapping), NamingStrategy.None) + val generator = DefinitionGenerator("com.iheart", List(customMapping), NamingStrategy.None, embedScaladoc = false) val definition = generator.definition[FooWithWrappedStringProperties] "support simple property types" >> { diff --git a/core/src/test/scala/com/iheart/playSwagger/SwaggerSpecGeneratorSpec.scala b/core/src/test/scala/com/iheart/playSwagger/SwaggerSpecGeneratorSpec.scala index 8ee126f9..ef9e47bf 100644 --- a/core/src/test/scala/com/iheart/playSwagger/SwaggerSpecGeneratorSpec.scala +++ b/core/src/test/scala/com/iheart/playSwagger/SwaggerSpecGeneratorSpec.scala @@ -1,7 +1,6 @@ package com.iheart.playSwagger import java.time.LocalDate - import com.iheart.playSwagger.Domain.CustomMappings import com.iheart.playSwagger.RefinedTypes.{Age, Albums, SpotifyAccount} import org.specs2.mutable.Specification @@ -116,7 +115,7 @@ class SwaggerSpecGeneratorIntegrationSpec extends Specification { (json \ "paths" \ "/player/{pid}/context/{bid}").asOpt[JsObject] must beSome } - lazy val json = SwaggerSpecGenerator(false, false, "com.iheart").generate("test.routes").get + lazy val json = SwaggerSpecGenerator(false, false, false, "com.iheart").generate("test.routes").get lazy val pathJson = json \ "paths" lazy val definitionsJson = json \ "definitions" lazy val postBodyJson = (pathJson \ "/post-body" \ "post").as[JsObject] @@ -513,10 +512,18 @@ class SwaggerSpecGeneratorIntegrationSpec extends Specification { } "embedded scaladoc strings" >> { + lazy val json = SwaggerSpecGenerator(false, false, embedScaladoc = true, "com.iheart").generate("test.routes").get + lazy val definitionsJson = json \ "definitions" + lazy val dayOfWeekJson = (definitionsJson \ "com.iheart.playSwagger.DayOfWeek").asOpt[JsObject] dayOfWeekJson must beSome[JsObject] (dayOfWeekJson.get \ "properties" \ "name" \ "description").as[String] === "e.g. Sunday, Monday, TuesDay..." } + "don't embedded scaladoc strings" >> { + dayOfWeekJson must beSome[JsObject] + (dayOfWeekJson.get \ "properties" \ "name" \ "description").asOpt[String] === None + } + "parse mixin referenced external file" >> { lazy val subjectJson = (pathJson \ "/api/subjects/dow/{subject}" \ "get").as[JsObject] @@ -684,7 +691,7 @@ class SwaggerSpecGeneratorIntegrationSpec extends Specification { } "fully operation id" >> { - lazy val json = SwaggerSpecGenerator(false, true, "com.iheart").generate("test.routes").get + lazy val json = SwaggerSpecGenerator(false, true, false, "com.iheart").generate("test.routes").get lazy val addTrackJson = (json \ "paths" \ "/api/station/playedTracks" \ "post").as[JsObject] (addTrackJson \ "operationId").as[String] ==== "LiveMeta.addPlayedTracks" } @@ -719,7 +726,7 @@ class SwaggerSpecGeneratorIntegrationSpec extends Specification { } "integration v3" >> { - lazy val json = SwaggerSpecGenerator(true, false, "com.iheart").generate("testV3.routes").get + lazy val json = SwaggerSpecGenerator(true, false, false, "com.iheart").generate("testV3.routes").get lazy val componentSchemasJson = json \ "components" \ "schemas" lazy val trackJson = (componentSchemasJson \ "com.iheart.playSwagger.Track").as[JsObject] diff --git a/sbtPlugin/src/main/scala/com/iheart/sbtPlaySwagger/SwaggerKeys.scala b/sbtPlugin/src/main/scala/com/iheart/sbtPlaySwagger/SwaggerKeys.scala index 64a9d9d0..820944d8 100644 --- a/sbtPlugin/src/main/scala/com/iheart/sbtPlaySwagger/SwaggerKeys.scala +++ b/sbtPlugin/src/main/scala/com/iheart/sbtPlaySwagger/SwaggerKeys.scala @@ -36,4 +36,10 @@ trait SwaggerKeys { "swaggerOperationIdNaming", "Either use the operationId of the generated json as the method name" ) + + val embedScaladoc: SettingKey[Boolean] = + SettingKey[Boolean]( + "embedScaladoc", + "Output schema description using scaladoc of case class" + ) } diff --git a/sbtPlugin/src/main/scala/com/iheart/sbtPlaySwagger/SwaggerPlugin.scala b/sbtPlugin/src/main/scala/com/iheart/sbtPlaySwagger/SwaggerPlugin.scala index 71ac000d..d4d8b3a9 100755 --- a/sbtPlugin/src/main/scala/com/iheart/sbtPlaySwagger/SwaggerPlugin.scala +++ b/sbtPlugin/src/main/scala/com/iheart/sbtPlaySwagger/SwaggerPlugin.scala @@ -48,8 +48,9 @@ object SwaggerPlugin extends AutoPlugin { swaggerAPIVersion.value :: swaggerPrettyJson.value.toString :: swaggerPlayJava.value.toString :: - swaggerNamingStrategy.value.toString :: + swaggerNamingStrategy.value :: swaggerOperationIdNamingFully.value.toString :: + embedScaladoc.value.toString :: Nil val swaggerClasspath = data((fullClasspath in Runtime).value) ++ update.value.select(configurationFilter(SwaggerConfig.name))