Skip to content
Open
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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ Lets you hide a value in generated schema

case class MyType(@Term.Hide mySecretField: Int)

### Required
Lets you specify a value as a required value

case class MyType(@Term.Required myRequiredField: Int)

Copyright 2014 Coursera Inc.

Licensed under the Apache License, Version 2.0 (the "License");
Expand Down
16 changes: 15 additions & 1 deletion src/main/scala/org/coursera/autoschema/AutoSchema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import play.api.libs.json.JsArray
import play.api.libs.json.Json
import play.api.libs.json.JsObject
import play.api.libs.json.JsString
import play.api.libs.json.JsBoolean

import scala.reflect.runtime.{universe => ru}

Expand Down Expand Up @@ -69,6 +70,9 @@ object AutoSchema {
private[this] val isDescriptionAnnotation = (annotaion: ru.Annotation) =>
annotaion.tpe.typeSymbol.fullName == "org.coursera.autoschema.annotations.Description"

private[this] val isRequiredAnnotation = (annotation: ru.Annotation) =>
annotation.tpe.typeSymbol.fullName == "org.coursera.autoschema.annotations.Term.Required"

// Generates JSON schema based on a FormatAs annotation
private[this] def formatAnnotationJson(annotation: ru.Annotation) = {
annotation.scalaArgs match {
Expand All @@ -89,6 +93,10 @@ object AutoSchema {
}
}

private [this] def requiredAnnotationJson(annotation: ru.Annotation) = {
Some("required" -> JsBoolean.apply(true))
}

private[this] def createClassJson(tpe: ru.Type, previousTypes: Set[String]) = {
// Check if schema for this class has already been generated
classSchemaCache.getOrElseUpdate(tpe.typeSymbol.fullName, {
Expand All @@ -112,7 +120,13 @@ object AutoSchema {
case None => termFormat
}

Some(term.name.decoded.trim -> termFormatWithDescription)
val required = term.annotations.find(isRequiredAnnotation).flatMap(requiredAnnotationJson)
val termFormatWithRequired = required match {
case Some(value) => termFormatWithDescription + value
case None => termFormatWithDescription
}

Some(term.name.decoded.trim -> termFormatWithRequired)
} else {
None
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,15 @@ object Term {
*/
@field
type Description = annotations.Description@field

/**
* Marks the annotated field as a required field
* @example
* {{{
* case class MyType(@Required myRequiredField: String)
* }}}
*/
@field
class Required extends StaticAnnotation

}
14 changes: 14 additions & 0 deletions src/test/scala/org/coursera/AutoSchema/AutoSchemaTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ case class MutuallyRecursiveTypeTwo(param1: MutuallyRecursiveTypeOne)
@Description("Type description")
case class TypeWithDescription(@Term.Description("Parameter description") param1: String)

case class TypeWithRequired(@Term.Required param1: String)

class AutoSchemaTest extends AssertionsForJUnit {
@Test
def justAnInt: Unit = {
Expand Down Expand Up @@ -219,4 +221,16 @@ class AutoSchemaTest extends AssertionsForJUnit {
"description" -> "Parameter description")),
"description" -> "Type description"))
}

@Test
def typeWithRequired: Unit = {
assert(createSchema[TypeWithRequired] ===
Json.obj(
"title" -> "TypeWithRequired",
"type" -> "object",
"properties" -> Json.obj(
"param1" -> Json.obj(
"type" -> "string",
"required" -> true))))
}
}