Skip to content

Commit

Permalink
Merge pull request #84 from AVSystem/adt-metadata
Browse files Browse the repository at this point in the history
ADT metadata & OpenAPI generation for REST
  • Loading branch information
ghik authored Sep 14, 2018
2 parents fd990bf + 43ddbaf commit 1301708
Show file tree
Hide file tree
Showing 68 changed files with 4,426 additions and 1,321 deletions.
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,13 @@ def sourceDirsSettings(baseMapper: File => File) = Seq(
)

lazy val `commons-annotations` = project
.dependsOn(`commons-macros`)
.settings(jvmCommonSettings)

lazy val `commons-annotations-js` = project.in(`commons-annotations`.base / "js")
.enablePlugins(ScalaJSPlugin)
.configure(p => if (forIdeaImport) p.dependsOn(`commons-annotations`) else p)
.dependsOn(`commons-macros`)
.settings(
jsCommonSettings,
name := (name in `commons-annotations`).value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import java.io.{DataInputStream, DataOutputStream}

import akka.actor.{ActorRef, ActorSystem}
import akka.util.ByteString
import com.avsystem.commons.meta._
import com.avsystem.commons.rpc._
import com.avsystem.commons.rpc.akka.client.ClientRawRPC
import com.avsystem.commons.rpc.akka.server.ServerActor
import com.avsystem.commons.rpc.{FunctionRPCFramework, GetterRPCFramework, MetadataAnnotation, ProcedureRPCFramework, RpcMetadataCompanion, TypedMetadata, infer, multi, reifyAnnot, reifyName, verbatim}
import com.avsystem.commons.serialization.{GenCodec, StreamInput, StreamOutput}
import monix.reactive.Observable

Expand All @@ -31,10 +32,10 @@ object AkkaRPCFramework extends GetterRPCFramework with ProcedureRPCFramework wi
case class RPCMetadata[T](
@reifyName name: String,
@reifyAnnot @multi annotations: List[MetadataAnnotation],
@multi @verbatim procedureSignatures: Map[String, ProcedureSignature],
@multi functionSignatures: Map[String, FunctionSignature[_]],
@multi observeSignatures: Map[String, ObserveSignature[_]],
@multi getterSignatures: Map[String, GetterSignature[_]]
@multi @verbatim @rpcMethodMetadata procedureSignatures: Map[String, ProcedureSignature],
@multi @rpcMethodMetadata functionSignatures: Map[String, FunctionSignature[_]],
@multi @rpcMethodMetadata observeSignatures: Map[String, ObserveSignature[_]],
@multi @rpcMethodMetadata getterSignatures: Map[String, GetterSignature[_]]
)
object RPCMetadata extends RpcMetadataCompanion[RPCMetadata]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.avsystem.commons
package rpc.akka

import com.avsystem.commons.meta._
import com.avsystem.commons.rpc._
import monix.reactive.Observable

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.avsystem.commons
package annotation

import scala.annotation.StaticAnnotation

/**
* Marker trait for annotations which don't want to be inherited by subtypes
* of a sealed trait or class that has this annotation applied. Intended for annotations that should apply
* only to the sealed trait itself.
*/
trait NotInheritedFromSealedTypes extends StaticAnnotation
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.avsystem.commons
package annotation

import scala.annotation.StaticAnnotation

/**
* Annotate a symbol (i.e. class, method, parameter, etc.) with `@positioned(positioned.here)` to retain source
* position information for that symbol to be available in macro implementations which inspect that symbol.
* This is necessary e.g. for determining declaration order of subtypes of sealed hierarchies in macro implementations.
* This annotation is only needed when macro is invoked in a different source file than the source file of inspected
* symbol. If macro is invoked in the same file, source position is always available.
*/
class positioned(val point: Int) extends StaticAnnotation
object positioned {
def here: Int = macro macros.misc.MiscMacros.posPoint
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.avsystem.commons
package serialization

import com.avsystem.commons.annotation.NotInheritedFromSealedTypes

import scala.annotation.StaticAnnotation

/**
Expand All @@ -24,4 +26,5 @@ import scala.annotation.StaticAnnotation
* For instance, if a case class field overrides a method of some base trait, the `@name` annotation may
* be used on that method and will affect the case class field.
*/
class name(val name: String) extends StaticAnnotation
class name(val name: String) extends StaticAnnotation with NotInheritedFromSealedTypes

Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,35 @@ object SharedExtensions extends SharedExtensions {
def optRef: OptRef[A] = OptRef(a)
}

private val RemovableLineBreak = "\\n+".r

class StringOps(private val str: String) extends AnyVal {
def ensureSuffix(suffix: String): String =
if (str.endsWith(suffix)) str else str + suffix

def ensurePrefix(prefix: String): String =
if (str.startsWith(prefix)) str else prefix + str

def uncapitalize: String =
if (str.isEmpty || str.charAt(0).isLower) str
else str.charAt(0).toLower + str.substring(1)

/**
* Removes a newline character from every sequence of consecutive newline characters. If the sequence contained
* just one newline character without any whitespace before and after it, a space is inserted.
*
* e.g. `My hovercraft\nis full of eels.\n\nMy hovercraft is\n full of eels.` becomes
* `My hovercraft is full of eels.\nMy hovercraft is full of eels.`
*
* Useful for multi-line string literals with lines wrapped in source code but without intention of including
* these line breaks in actual runtime string.
*/
def unwrapLines: String =
RemovableLineBreak.replaceAllIn(str, { m =>
val insertSpace = m.end == m.start + 1 && m.start - 1 >= 0 && m.end < str.length &&
!Character.isWhitespace(str.charAt(m.start - 1)) && !Character.isWhitespace(str.charAt(m.end))
if (insertSpace) " " else m.matched.substring(1)
})
}

class IntOps(private val int: Int) extends AnyVal {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.avsystem.commons
package meta

import com.avsystem.commons.macros.meta.AdtMetadataMacros
import com.avsystem.commons.misc.MacroGenerated

trait AdtMetadataCompanion[M[_]] extends MetadataCompanion[M] {
def materialize[T]: M[T] = macro AdtMetadataMacros.materialize[T]

implicit def materializeMacroGenerated[T]: MacroGenerated[M[T]] = macro AdtMetadataMacros.materializeMacroGenerated[T]
}
Original file line number Diff line number Diff line change
@@ -1,55 +1,63 @@
package com.avsystem.commons
package rpc
package meta

import com.avsystem.commons.rpc.NamedParams.ConcatIterable
import com.avsystem.commons.meta.Mapping.ConcatIterable
import com.avsystem.commons.serialization.GenCodec

import scala.collection.generic.CanBuildFrom
import scala.collection.mutable

/**
* Simple immutable structure to collect named RPC parameters while retaining their order and
* providing fast, hashed lookup by parameter name when necessary.
* Simple immutable structure to collect named values while retaining their order and
* providing fast, hashed lookup by name when necessary.
* Intended to be used for [[multi]] raw parameters.
*/
final class NamedParams[+V](private val wrapped: IIterable[(String, V)])
final class Mapping[+V](private val wrapped: IIterable[(String, V)])
extends IIterable[(String, V)] with PartialFunction[String, V] {

private[this] lazy val hashMap = new MLinkedHashMap[String, V].setup(_ ++= wrapped)

def iterator: Iterator[(String, V)] =
hashMap.iterator
def valuesIterator: Iterator[V] =
hashMap.valuesIterator
def keys: Iterable[String] =
hashMap.keys
def contains(key: String): Boolean =
hashMap.contains(key)
def isDefinedAt(key: String): Boolean =
hashMap.isDefinedAt(key)
override def applyOrElse[A1 <: String, B1 >: V](key: A1, default: A1 => B1): B1 =
hashMap.applyOrElse(key, default)
override def apply(key: String): V =
hashMap.apply(key)
def get(key: String): Opt[V] =
hashMap.getOpt(key)

def ++[V0 >: V](other: NamedParams[V0]): NamedParams[V0] =
def ++[V0 >: V](other: Mapping[V0]): Mapping[V0] =
if (wrapped.isEmpty) other
else if (other.wrapped.isEmpty) this
else new NamedParams(ConcatIterable(wrapped, other.wrapped))
else new Mapping(ConcatIterable(wrapped, other.wrapped))
}
object NamedParams {
def empty[V]: NamedParams[V] = new NamedParams(Nil)
def newBuilder[V]: mutable.Builder[(String, V), NamedParams[V]] =
new MListBuffer[(String, V)].mapResult(new NamedParams(_))
object Mapping {
def empty[V]: Mapping[V] = new Mapping(Nil)
def newBuilder[V]: mutable.Builder[(String, V), Mapping[V]] =
new MListBuffer[(String, V)].mapResult(new Mapping(_))

private case class ConcatIterable[+V](first: IIterable[V], second: IIterable[V]) extends IIterable[V] {
def iterator: Iterator[V] = first.iterator ++ second.iterator
}

private val reusableCBF = new CanBuildFrom[Nothing, (String, Any), NamedParams[Any]] {
def apply(from: Nothing): mutable.Builder[(String, Any), NamedParams[Any]] = newBuilder[Any]
def apply(): mutable.Builder[(String, Any), NamedParams[Any]] = newBuilder[Any]
private val reusableCBF = new CanBuildFrom[Nothing, (String, Any), Mapping[Any]] {
def apply(from: Nothing): mutable.Builder[(String, Any), Mapping[Any]] = newBuilder[Any]
def apply(): mutable.Builder[(String, Any), Mapping[Any]] = newBuilder[Any]
}

implicit def canBuildFrom[V]: CanBuildFrom[Nothing, (String, V), NamedParams[V]] =
reusableCBF.asInstanceOf[CanBuildFrom[Nothing, (String, V), NamedParams[V]]]
implicit def canBuildFrom[V]: CanBuildFrom[Nothing, (String, V), Mapping[V]] =
reusableCBF.asInstanceOf[CanBuildFrom[Nothing, (String, V), Mapping[V]]]

implicit def genCodec[V: GenCodec]: GenCodec[NamedParams[V]] = GenCodec.createNullableObject(
oi => new NamedParams(oi.iterator(GenCodec.read[V]).toList),
implicit def genCodec[V: GenCodec]: GenCodec[Mapping[V]] = GenCodec.createNullableObject(
oi => new Mapping(oi.iterator(GenCodec.read[V]).toList),
(oo, np) => np.foreach({ case (k, v) => GenCodec.write[V](oo.writeField(k), v) })
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.avsystem.commons
package meta

import com.avsystem.commons.macros.misc.MiscMacros
import com.avsystem.commons.rpc.Fallback

trait MetadataCompanion[M[_]] {
final def apply[Real](implicit metadata: M[Real]): M[Real] = metadata

implicit final def fromFallback[Real](implicit fallback: Fallback[M[Real]]): M[Real] = fallback.value

final class Lazy[Real](metadata: => M[Real]) {
lazy val value: M[Real] = metadata
}
object Lazy {
def apply[Real](metadata: => M[Real]): Lazy[Real] = new Lazy(metadata)

// macro effectively turns `metadata` param into by-name param (implicit params by themselves cannot be by-name)
implicit def lazyMetadata[Real](implicit metadata: M[Real]): Lazy[Real] = macro MiscMacros.lazyMetadata
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.avsystem.commons
package rpc
package meta

sealed trait OptionLike[O] {
type Value
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.avsystem.commons
package rpc
package meta

import com.avsystem.commons.serialization.{HasGenCodec, transparent}

Expand Down Expand Up @@ -77,3 +77,57 @@ final case class ParamPosition(
indexInRaw: Int
)
object ParamPosition extends HasGenCodec[ParamPosition]

/**
* Information about real parameter flags and modifiers as defined in Scala code.
*/
@transparent
final case class TypeFlags(rawFlags: Int) extends AnyVal {

import TypeFlags._

def |(other: TypeFlags): TypeFlags = new TypeFlags(rawFlags | other.rawFlags)
def &(other: TypeFlags): TypeFlags = new TypeFlags(rawFlags & other.rawFlags)
def ^(other: TypeFlags): TypeFlags = new TypeFlags(rawFlags ^ other.rawFlags)
def unary_~ : TypeFlags = new TypeFlags(~rawFlags)

def hasFlags(flags: TypeFlags): Boolean = (this & flags) == flags

def isAbstract: Boolean = hasFlags(Abstract)
def isFinal: Boolean = hasFlags(Final)
def isSealed: Boolean = hasFlags(Sealed)
def isCase: Boolean = hasFlags(Case)
def isTrait: Boolean = hasFlags(Trait)
def isObject: Boolean = hasFlags(Object)

override def toString: String = {
def repr(flags: TypeFlags, r: String): Opt[String] =
r.opt.filter(_ => hasFlags(flags))

List(
repr(Abstract, "abstract"),
repr(Final, "final"),
repr(Sealed, "sealed"),
repr(Case, "case"),
repr(Trait, "trait"),
repr(Object, "object")
).flatten.mkString(",")
}
}

object TypeFlags extends HasGenCodec[TypeFlags] {
private[this] var currentFlag: Int = 1
private[this] def nextFlag(): TypeFlags = {
val flag = currentFlag
currentFlag = currentFlag << 1
new TypeFlags(flag)
}

final val Empty: TypeFlags = new TypeFlags(0)
final val Abstract: TypeFlags = nextFlag()
final val Final: TypeFlags = nextFlag()
final val Sealed: TypeFlags = nextFlag()
final val Case: TypeFlags = nextFlag()
final val Trait: TypeFlags = nextFlag()
final val Object: TypeFlags = nextFlag()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.avsystem.commons
package misc

import com.avsystem.commons.macros.misc.MiscMacros

class ValueOf[T](val value: T) extends AnyVal
object ValueOf {
def apply[T](implicit vof: ValueOf[T]): T = vof.value

implicit def mkValueOf[T]: ValueOf[T] = macro MiscMacros.mkValueOf[T]
}
Loading

0 comments on commit 1301708

Please sign in to comment.