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

io.atomicbits.scraml.parser.model.Raml.scala Maven / Gradle / Ivy

/*
 * (C) Copyright 2015 Atomic BITS (http://atomicbits.io).
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Affero General Public License
 * (AGPL) version 3.0 which accompanies this distribution, and is available in
 * the LICENSE file or at http://www.gnu.org/licenses/agpl-3.0.en.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Affero General Public License for more details.
 *
 * Contributors:
 *     Peter Rigole
 *
 */

package io.atomicbits.scraml.parser.model

import scala.collection.JavaConverters._

/**
 * Created by peter on 17/05/15, Atomic BITS bvba (http://atomicbits.io). 
 */
case class Raml(resources: List[Resource], schemas: Map[String, String])

object Raml {


  def apply(raml: org.raml.model.Raml): Raml = {

    val parallelResources: List[Resource] = raml.getResources.values().asScala.toList.map(Resource(_))

    // Now, we can still have parallel resources that have overlapping paths because they could result
    // from inclusions in a main raml definition file. We have to merge those parallel resource paths first at all depths.
    val resources: List[Resource] = unparallellizeResources(parallelResources)

    val linkedSchemas: Map[String, String] = {
      Option(raml.getSchemas) map { schema =>
        schema.asScala.foldLeft[Map[String, String]](Map.empty)((aggr, el) => aggr ++ el.asScala)
      } getOrElse Map.empty
    }

    val (resourcesWithoutInlineSchemas, updatedLinkedSchemas) =
      resources.foldLeft[SchemaResourceExtraction]((List.empty, linkedSchemas))(extractFromResource)

    Raml(resourcesWithoutInlineSchemas, updatedLinkedSchemas)
  }


  type SchemaResourceExtraction = (List[Resource], Map[String, String])
  type SchemaActionExtraction = (List[Action], Map[String, String])
  type SchemaResponseExtraction = (Map[String, Response], Map[String, String])
  type Body = Map[String, MimeType]
  type SchemaBodyExtraction = (Body, Map[String, String])

  /**
   * Inline schema's are located inside the MimeType of Action body and Response body elements.
   * We will replace them with a generated link "_inline{number}" and add the link to the linked schemas.
   *
   * @param schemaExtraction The resources that are already updated with the linked schemas.
   * @param resource The resource to extract inline schemas from.
   * @return The updated version of the schema extraction.
   */
  private def extractFromResource(schemaExtraction: SchemaResourceExtraction,
                                  resource: Resource): SchemaResourceExtraction = {

    val (processedResources, linkedSchemas) = schemaExtraction

    val (actionsWithoutInlineSchemas, updatedLinkedSchemasFromActions) =
      resource.actions.foldLeft[SchemaActionExtraction](List.empty, linkedSchemas)(extractFromAction)

    val (childResourcesWithoutInlineSchemas, updatedLinkedSchemas) =
      resource.resources
        .foldLeft[SchemaResourceExtraction]((List.empty, updatedLinkedSchemasFromActions))(extractFromResource)

    val updatedResource =
      resource.copy(
        actions = actionsWithoutInlineSchemas,
        resources = childResourcesWithoutInlineSchemas
      )

    (updatedResource :: processedResources, updatedLinkedSchemas)

  }


  private def extractFromAction(schemaExtraction: SchemaActionExtraction, action: Action): SchemaActionExtraction = {

    val (processedActions, linkedSchemas) = schemaExtraction

    val (updatedBody, updatedLinkedSchemasFromBody) =
      action.body.toList.foldLeft[SchemaBodyExtraction]((Map.empty, linkedSchemas))(extractFromBodyPart)

    val (responsesWithoutInlineSchemas, updatedLinkedSchemasFromResponses) =
      action.responses.toList
        .foldLeft[SchemaResponseExtraction]((Map.empty, updatedLinkedSchemasFromBody))(extractFromResponse)

    val updatedAction = action.copy(body = updatedBody, responses = responsesWithoutInlineSchemas)

    (updatedAction :: processedActions, updatedLinkedSchemasFromResponses)

  }

  private def extractFromResponse(schemaExtraction: SchemaResponseExtraction,
                                  mimeResponse: (String, Response)): SchemaResponseExtraction = {

    val (processedMimeResponses, linkedSchemas) = schemaExtraction

    val (mimeType, response) = mimeResponse

    val (updatedBody, updatedLinkedSchemasFromBody) =
      response.body.toList.foldLeft[SchemaBodyExtraction]((Map.empty, linkedSchemas))(extractFromBodyPart)

    val updatedResponse = response.copy(body = updatedBody)

    (processedMimeResponses + (mimeType -> updatedResponse), updatedLinkedSchemasFromBody)

  }


  private def extractFromBodyPart(schemaExtraction: SchemaBodyExtraction,
                                  bodyPart: (String, MimeType)): SchemaBodyExtraction = {

    val (updatedBody, linkedSchemas) = schemaExtraction

    val (mime, mimeType) = bodyPart

    type SchemaLinkUpdate = (Option[String], Map[String, String])

    var (updatedSchemaLinkOpt, updatedLinkedSchemas) =
      mimeType.schema.foldLeft[SchemaLinkUpdate]((None, linkedSchemas)) { (schemaExtraction, explicietOrInlineSchema) =>
        val (replacementString, linkedSchemas) = schemaExtraction
        if (linkedSchemas.get(explicietOrInlineSchema).isEmpty) {
          // we have an inline schema
          val link = s"_inline-${linkedSchemas.keys.size}"
          (Some(link), linkedSchemas + (link -> explicietOrInlineSchema))
        } else {
          (None, linkedSchemas)
        }
      }

    updatedSchemaLinkOpt match {
      case linkOpt@Some(link) =>
        val updatedMImeType = mimeType.copy(schema = linkOpt)
        (updatedBody + (mime -> updatedMImeType), updatedLinkedSchemas)
      case None               => (updatedBody + bodyPart, linkedSchemas)
    }

  }


  /**
   * Unparallellize the given resources at all levels. This must be done top-down!
   */
  private def unparallellizeResources(resources: List[Resource]): List[Resource] = {

    // Group all resources at this level with the same urlSegment and urlParameter
    val groupedResources: List[List[Resource]] =
      resources.groupBy(resource => (resource.urlSegment, resource.urlParameter)).values.toList

    // Merge all actions and subresources of all resources that have the same (urlSegment, urlParameter)
    def mergeResources(resources: List[Resource]): Resource = {
      resources.reduce { (resourceA, resourceB) =>
        resourceA.copy(actions = resourceA.actions ++ resourceB.actions, resources = resourceA.resources ++ resourceB.resources)
      }
    }
    val mergedResources: List[Resource] = groupedResources.map(mergeResources)

    mergedResources.map { mergedResource =>
      mergedResource.copy(resources = unparallellizeResources(mergedResource.resources))
    }

  }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy