All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.kardapoltsev.astparser.model.Model.scala Maven / Gradle / Ivy

There is a newer version: 8.4.0
Show newest version
/*
 * Copyright 2016 Alexey Kardapoltsev
 *
 * 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 com.github.kardapoltsev.astparser.model

import com.github.kardapoltsev.astparser.parser
import com.github.kardapoltsev.astparser.parser.AstParser
import com.github.kardapoltsev.astparser.parser.doc.{DocParser}
import com.github.kardapoltsev.astparser.parser.doc
import com.github.kardapoltsev.astparser.parser.http.{HttpParser, HttpRequest, PathParam}

case class Model(
  schemas: Seq[Schema],
  private[astparser] val parsedModel: parser.Model
) {

  private val definitions = deepDefinitions
    .groupBy(_.fullName)
    .map {
      case (fullName, defs) =>
        fullName -> defs
    }
    .withDefaultValue(Seq.empty)

  def deepDefinitions: Seq[Definition] = {
    schemas ++ (schemas flatMap (_.deepDefinitions))
  }

  def getDefinition(fullName: String): Seq[Definition] = {
    definitions(fullName)
  }

  def getType(typeReference: TypeReference): TypeLike = {
    getDefinition(typeReference.fullName) match {
      case Seq(t: TypeLike) => t
      case x =>
        throw new Exception(s"found unexpected entity for $typeReference: $x")
    }
  }

  def slice(from: Int, to: Int): Model = {
    this.copy(schemas = schemas.map(_.slice(VersionsInterval(Some(from), Some(to)))))
  }

}

object Model {
  private val astParser  = new AstParser()
  private val httpParser = new HttpParser()
  private val docParser  = new DocParser()

  def build(schemas: Seq[java.io.File]): Model = {
    val parsed = schemas.map(f => astParser.parse(f))
    build(parser.Model(parsed))
  }
  //src -> src name
  def buildFromString(schemas: Seq[(String, String)]): Model = {
    val parsed = schemas.map { case (source, sourceName) => astParser.parse(source, sourceName) }
    build(parser.Model(parsed))
  }

  private def build(implicit parsed: parser.Model): Model = {
    Model(
      parsed.schemas map { s =>
        Schema(
          name = s.name,
          definitions = convertDefinitions(s.definitions)
        )
      },
      parsed
    )
  }

  private def convertDefinitions(
    definitions: Seq[parser.Definition]
  )(implicit m: parser.Model): Seq[Definition] = {
    definitions collect {
      case p: parser.Package =>
        convertPackage(p)
      case c: parser.Call =>
        convertCall(c)
      case t: parser.Type =>
        convertType(t)
      case ta: parser.TypeAlias =>
        convertTypeAlias(ta)
      case t: parser.Trait =>
        convertTrait(t)
      case et: parser.ExternalType =>
        convertExternalType(et)
    }
  }

  private def convertPackage(
    p: parser.Package
  )(implicit m: parser.Model): Package = {
    Package(
      p.packageName,
      p.name,
      convertDefinitions(p.definitions)
    )
  }

  private def convertTrait(
    t: parser.Trait
  )(implicit m: parser.Model): Trait = {
    Trait(
      t.packageName,
      t.arguments map convertArgument,
      t.name,
      t.parents map resolve map (_.head) map convertParent,
      convertDocs(t.docs)
    )
  }

  private def convertCall(
    c: parser.Call
  )(implicit m: parser.Model): Call = {
    val httpDefinition = c.httpRequest map { http =>
      httpParser.parse(
        http,
        s"http definition for ${c.humanReadable}"
      )
    }

    httpDefinition foreach { http =>
      checkHttpParameters(c, http)
    }

    Call(
      c.packageName,
      c.name,
      c.id,
      c.arguments map convertArgument,
      convertTypeStatement(c.returnType),
      c.parents map resolve map (_.head) map convertParent,
      httpDefinition,
      convertVersionsInterval(c.versions),
      convertDocs(c.docs)
    )
  }

  private def checkHttpParameters(call: parser.Call, http: HttpRequest): Unit = {
    val pathParams = http.url.path.collect {
      case PathParam(name) => name
    }
    val queryParams = http.url.query.map(_.name)
    pathParams ++ queryParams foreach { paramName =>
      call.arguments.find(_.name == paramName) match {
        case Some(_) =>
        case None =>
          throw new Exception(
            s"Parameter `$paramName` defined at ${http.pos} " +
              s"not found in `${call.name}` call definition"
          )
      }
    }
  }

  private def convertArgument(
    a: parser.Argument
  )(implicit m: parser.Model): Argument = {
    Argument(
      a.name,
      convertTypeStatement(a.`type`),
      convertDocs(a.docs)
    )
  }

  private def convertTypeStatement(
    ts: parser.TypeStatement
  )(implicit m: parser.Model): TypeStatement = {
    convertTypeStatement(isTypeArgument = false)(ts)
  }

  private def convertTypeStatement(
    isTypeArgument: Boolean
  )(
    ts: parser.TypeStatement
  )(implicit m: parser.Model): TypeStatement = {
    TypeStatement(
      ts.packageName,
      TypeReference(resolve(ts.ref).head.fullName),
      ts.typeArguments map convertTypeStatement(isTypeArgument = true),
      isTypeArgument
    )
  }

  private def convertTypeLike(
    tl: Seq[parser.TypeLike]
  )(implicit m: parser.Model): TypeLike = {
    if (tl.size == 1) {
      tl.head match {
        case t: parser.Type =>
          convertType(t)
        case c: parser.TypeConstructor =>
          convertTypeConstructor(Seq(c))
        case t: parser.Trait =>
          convertTrait(t)
        case ta: parser.TypeAlias =>
          convertTypeAlias(ta)
        case et: parser.ExternalType =>
          convertExternalType(et)
        case c: parser.Call =>
          convertCall(c)
      }
    } else {
      assume(
        tl.forall(_.isInstanceOf[parser.TypeConstructor]),
        "only Seq[TypeLike] of TypeConstructors may have size > 1")
      convertTypeConstructor(
        tl.collect { case tc: parser.TypeConstructor => tc }
      )
    }
  }

  private def convertExternalType(
    et: parser.ExternalType
  ): ExternalType = {
    ExternalType(
      et.packageName,
      et.fullName,
      et.typeArguments map convertTypeParameter
    )
  }

  private def convertTypeParameter(
    tp: parser.TypeParameter
  ): TypeParameter = {
    TypeParameter(
      tp.name,
      tp.typeParameters map convertTypeParameter
    )
  }

  private def convertType(t: parser.Type)(implicit m: parser.Model): Type = {
    Type(
      t.packageName,
      name = t.name,
      typeArguments = t.typeArguments map convertTypeParameter,
      parents = t.parents map resolve map (_.head) map convertParent,
      constructors = t.constructors.groupBy(_.name).toSeq map {
        case (_, constructors) => convertTypeConstructor(constructors)
      },
      docs = convertDocs(t.docs)
    )
  }

  private def convertParent(p: parser.TypeLike)(implicit m: parser.Model): Parent = {
    p match {
      case t: parser.Trait        => convertTrait(t)
      case e: parser.ExternalType => convertExternalType(e)
      case other =>
        throw new Exception(
          s"expected trait or external type as parent, got ${other.humanReadable}")
    }
  }

  private def convertTypeAlias(
    a: parser.TypeAlias
  )(implicit m: parser.Model): TypeAlias = {
    TypeAlias(a.packageName, a.name, convertTypeLike(resolve(a.reference)))
  }

  private def resolve(ref: parser.Reference)(implicit m: parser.Model): Seq[parser.TypeLike] = {
    m.lookup(ref) match {
      case Seq(i: parser.Import) =>
        resolve(i.reference)
      case Seq(tl: parser.TypeLike) =>
        Seq(tl)
      case found if found.nonEmpty && found.forall(_.isInstanceOf[parser.TypeConstructor]) =>
        found.collect { case tc: parser.TypeConstructor => tc }
      case found if found.nonEmpty =>
        throw new Exception(s"${ref.humanReadable} resolved to unexpected $found")
      case _ =>
        throw new Exception(s"unresolved ${ref.humanReadable}")
    }
  }

  private def convertTypeConstructor(
    c: Seq[parser.TypeConstructor]
  )(implicit m: parser.Model): TypeConstructor = {
    assert(c.nonEmpty, "couldn't convert empty list of constructors")
    val name = c.head.name
    assert(c.forall(_.name == name), "constructors passed to convert should have the same name")
    val packageName = c.head.packageName
    val versions = c.map { constructor =>
      val parents = constructor.parents.map(resolve).map(_.head) map convertParent
      TypeConstructorVersion(
        parent = packageName,
        name = constructor.name,
        parents = parents,
        id = constructor.id,
        typeArguments = constructor.typeArguments map convertTypeParameter,
        arguments = constructor.arguments map convertArgument,
        versions = convertVersionsInterval(constructor.versions),
        docs = convertDocs(constructor.docs)
      )
    }
    TypeConstructor(
      parent = packageName,
      name = name,
      versions = versions
    )
  }

  private def convertDocs(
    docs: Seq[parser.Documentation]
  )(implicit m: parser.Model): Documentation = {
    val elems = docs flatMap { d =>
      docParser.parse(d.content, d.pos.toString()).docs map {
        case plain: doc.DocString =>
          PlainDoc(plain.value)
        case r: doc.DocReference =>
          val ref = parser.Reference(r.reference)
          ref.setPos(r.pos)
          ref.maybeParent = Some(d)
          DocReference(r.name, TypeReference(resolve(ref).head.fullName))
      }
    }
    Documentation(elems)
  }

  private def convertVersionsInterval(
    v: parser.VersionsInterval
  ): VersionsInterval = {
    VersionsInterval(v.start, v.end)
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy