Skip to content

Commit 06e9f37

Browse files
committed
annotation for OpenAPI parameter value examples
1 parent b66824e commit 06e9f37

File tree

12 files changed

+65
-15
lines changed

12 files changed

+65
-15
lines changed

commons-annotations/src/main/scala/com/avsystem/commons/meta/metaAnnotations.scala

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,24 @@ trait MetadataParamStrategy extends StaticAnnotation
189189
* method or real parameter matches a metadata parameter. For example, if an implicit for `@infer` parameter cannot be
190190
* found, you will only know about it *after* the metadata materializing macro has already been expanded.
191191
* This behaviour can be changed with [[checked]] annotation.
192-
*/
192+
*
193+
* The `@infer` annotation may also be used on a parameter of an annotation reified with [[reifyAnnot]] or
194+
* [[reifyEncodedAnnot]] in order to simulate implicit parameter. This is a workaround for the fact that Scala
195+
* annotations cannot accept multiple parameter lists. In such situation, `infer.value` should be used as default
196+
* value of "implicit" annotation parameter:
197+
*
198+
* {{{
199+
* class valueWithCodec[T](value: T, @infer codec: GenCodec[T] = infer.value)
200+
* extends scala.annotation.StaticAnnotation
201+
* }}}
202+
**/
193203
final class infer extends MetadataParamStrategy
204+
object infer {
205+
/**
206+
* Can be used as default value of `@infer` annotation parameters.
207+
*/
208+
def value[T]: T = macro macros.misc.WhiteMiscMacros.inferValue
209+
}
194210

195211
/**
196212
* `@adtParamMetadata` applied on metadata parameter of metadata class for case class or object indicates that

commons-core/src/main/scala/com/avsystem/commons/rest/openapi/OpenApi.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ object Style extends AbstractValueEnumCompanion[Style] {
428428
*/
429429
case class MediaType(
430430
@td schema: OptArg[RefOr[Schema]] = OptArg.Empty,
431-
@td example: OptArg[String] = OptArg.Empty, //TODO other values than strings
431+
@td example: OptArg[JsonValue] = OptArg.Empty,
432432
@td examples: Map[String, RefOr[Example]] = Map.empty,
433433
@td encoding: Map[String, Encoding] = Map.empty
434434
)

commons-core/src/main/scala/com/avsystem/commons/rest/openapi/OpenApiMetadata.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.avsystem.commons
22
package rest.openapi
33

4-
import com.avsystem.commons.meta.{multi, reifyAnnot, _}
4+
import com.avsystem.commons.meta._
5+
import com.avsystem.commons.rest.openapi.adjusters._
56
import com.avsystem.commons.rest.{Header => HeaderAnnot, _}
67
import com.avsystem.commons.rpc._
78
import com.avsystem.commons.serialization.{transientDefault, whenAbsent}

commons-core/src/main/scala/com/avsystem/commons/rest/openapi/RestSchema.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import java.util.UUID
55

66
import com.avsystem.commons.misc.{NamedEnum, NamedEnumCompanion, Timestamp}
77
import com.avsystem.commons.rest.HttpBody
8+
import com.avsystem.commons.rest.openapi.adjusters.SchemaAdjuster
89

910
import scala.annotation.implicitNotFound
1011

commons-core/src/main/scala/com/avsystem/commons/rest/openapi/RestStructure.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.avsystem.commons.annotation.positioned
55
import com.avsystem.commons.meta._
66
import com.avsystem.commons.misc.ValueOf
77
import com.avsystem.commons.rest.JsonValue
8+
import com.avsystem.commons.rest.openapi.adjusters.SchemaAdjuster
89
import com.avsystem.commons.serialization._
910

1011
sealed trait RestStructure[T] extends TypedMetadata[T] {

commons-core/src/main/scala/com/avsystem/commons/rest/openapi/Adjuster.scala renamed to commons-core/src/main/scala/com/avsystem/commons/rest/openapi/adjusters/Adjuster.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.avsystem.commons
2-
package rest.openapi
2+
package rest.openapi.adjusters
33

44
import com.avsystem.commons.annotation.NotInheritedFromSealedTypes
5+
import com.avsystem.commons.meta.infer
6+
import com.avsystem.commons.rest.JsonValue
7+
import com.avsystem.commons.rest.openapi.{Operation, Parameter, RefOr, RestSchema, RestStructure, Schema}
8+
import com.avsystem.commons.rpc.AsRaw
59

610
import scala.annotation.StaticAnnotation
711

@@ -77,3 +81,11 @@ class description(desc: String) extends SchemaAdjuster with ParameterAdjuster wi
7781
def adjustParameter(parameter: Parameter): Parameter = parameter.copy(description = desc)
7882
def adjustOperation(operation: Operation): Operation = operation.copy(description = desc)
7983
}
84+
85+
/**
86+
* Adds example to [[Parameter]] object generated for REST method parameter annotated with this annotation.
87+
*/
88+
class example[+T](value: T, @infer asJson: AsRaw[JsonValue, T] = infer.value) extends ParameterAdjuster {
89+
def adjustParameter(parameter: Parameter): Parameter =
90+
parameter.copy(example = asJson.asRaw(value))
91+
}

commons-core/src/test/scala/com/avsystem/commons/rest/RestTestApi.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.avsystem.commons
22
package rest
33

4-
import com.avsystem.commons.rest.openapi.description
4+
import com.avsystem.commons.rest.openapi.adjusters.{description, example}
55
import com.avsystem.commons.serialization.{flatten, whenAbsent}
66

77
sealed trait BaseEntity
@@ -56,7 +56,7 @@ trait RestTestApi {
5656
def prefix(
5757
p0: String,
5858
@Header("X-H0") h0: String,
59-
@Query q0: String
59+
@Query @example("q0example") q0: String
6060
): RestTestSubApi
6161

6262
def complexParams(

commons-core/src/test/scala/com/avsystem/commons/rest/openapi/OpenApiGenerationTest.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ class OpenApiGenerationTest extends FunSuite {
5757
| "required": true,
5858
| "schema": {
5959
| "type": "string"
60-
| }
60+
| },
61+
| "example": "q0example"
6162
| },
6263
| {
6364
| "name": "p1",

commons-core/src/test/scala/com/avsystem/commons/rest/openapi/RestSchemaTest.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.avsystem.commons
22
package rest.openapi
33

44
import com.avsystem.commons.rest.RestDataCompanion
5+
import com.avsystem.commons.rest.openapi.adjusters.description
56
import com.avsystem.commons.serialization.json.JsonStringOutput
67
import com.avsystem.commons.serialization.{GenCodec, name, transparent}
78
import org.scalatest.FunSuite

commons-macros/src/main/scala/com/avsystem/commons/macros/MacroCommons.scala

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ trait MacroCommons { bundle =>
1313

1414
import c.universe._
1515

16-
import scala.reflect.{ClassTag, classTag}
16+
type ClassTag[T] = scala.reflect.ClassTag[T]
17+
final def classTag[T: ClassTag]: ClassTag[T] = scala.reflect.classTag[T]
1718

1819
final val ScalaPkg = q"_root_.scala"
1920
final val JavaLangPkg = q"_root_.java.lang"
@@ -44,9 +45,11 @@ trait MacroCommons { bundle =>
4445
final val ImplicitsObj = q"$CommonsPkg.misc.Implicits"
4546
final val AnnotationAggregateType = getType(tq"$CommonsPkg.annotation.AnnotationAggregate")
4647
final val DefaultsToNameAT = getType(tq"$CommonsPkg.annotation.defaultsToName")
48+
final val InferAT: Type = getType(tq"$CommonsPkg.meta.infer")
4749
final val NotInheritedFromSealedTypes = getType(tq"$CommonsPkg.annotation.NotInheritedFromSealedTypes")
4850
final val SeqCompanionSym = typeOf[scala.collection.Seq.type].termSymbol
4951
final val PositionedAT = getType(tq"$CommonsPkg.annotation.positioned")
52+
final val AnnotationType = getType(tq"$ScalaPkg.annotation.Annotation")
5053
final val ImplicitNotFoundAT = getType(tq"$ScalaPkg.annotation.implicitNotFound")
5154

5255
final val NothingTpe: Type = typeOf[Nothing]
@@ -104,13 +107,17 @@ trait MacroCommons { bundle =>
104107
lazy val tree: Tree = annotTree match {
105108
case Apply(constr@Select(New(tpt), termNames.CONSTRUCTOR), args) =>
106109
val clsTpe = tpt.tpe
107-
val params = primaryConstructorOf(clsTpe).typeSignature.paramLists.head
110+
val params = primaryConstructorOf(clsTpe).typeSignatureIn(clsTpe).paramLists.head
108111

109112
val newArgs = (args zip params) map {
110113
case (arg, param) if param.asTerm.isParamWithDefault && arg.symbol != null &&
111-
arg.symbol.isSynthetic && arg.symbol.name.decodedName.toString.contains("$default$") &&
112-
findAnnotation(param, DefaultsToNameAT).nonEmpty =>
113-
q"${subject.name.decodedName.toString}"
114+
arg.symbol.isSynthetic && arg.symbol.name.decodedName.toString.contains("$default$") =>
115+
if (findAnnotation(param, DefaultsToNameAT).nonEmpty)
116+
q"${subject.name.decodedName.toString}"
117+
else if (findAnnotation(param, InferAT).nonEmpty)
118+
q"$ImplicitsObj.infer[${param.typeSignature}](${StringLiteral("", arg.pos)})"
119+
else
120+
arg
114121
case (arg, _) => arg
115122
}
116123

0 commit comments

Comments
 (0)