modules/jooby-apitool/src/main/java/org/jooby/apitool/raml/Raml.java
public static Raml build(Raml base, List<RouteMethod> routes) {
    Raml raml = Optional.ofNullable(base).orElseGet(Raml::new);
    BiFunction<RamlPath, String, RamlPath> pathFactory = (path, segment) ->
        path == null ? raml.path(segment) : path.path(segment);

    BiConsumer<Function<String, RamlParameter>, RouteParameter> parameterFactory = (factory, parameter) -> {
      RamlParameter p = factory.apply(parameter.name());
      p.setDescription(parameter.description()
          .map(Raml::yamlText)
          .orElse(null));
      List<String> enums = parameter.enums();
      if (enums.size() > 0) {
        p.setType(RamlType.STRING);
        p.setEnum(enums);
      } else {
        p.setType(raml.define(parameter.type()));
      }
      p.setRequired(!parameter.optional());
      p.setDefault(parameter.defaultValue());
    };

    /** Default consumes/produces: */
    Set<String> alltypes = new LinkedHashSet<>();
    Consumer<Function<RouteMethod, List<String>>> mediaTypes = types ->
        routes.stream().forEach(r -> types.apply(r).forEach(alltypes::add));
    mediaTypes.accept(RouteMethod::consumes);
    mediaTypes.accept(RouteMethod::produces);
    boolean defaultMediaType = false;
    if (alltypes.size() == 0) {
      raml.setMediaType(ImmutableList.of(MediaType.json.name()));
      defaultMediaType = true;
    } else if (alltypes.size() == 1) {
      raml.setMediaType(ImmutableList.of(alltypes.iterator().next()));
      defaultMediaType = true;
    }

    for (RouteMethod route : routes) {
      List<String> segments = Splitter.on("/")
          .trimResults()
          .omitEmptyStrings()
          .splitToList(route.pattern());
      RamlPath path = null;
      for (String segment : segments) {
        RamlPath newPath = pathFactory.apply(path, "/" + segment);
        if (segment.startsWith("{") && segment.endsWith("}")) {
          String pathvar = segment.substring(1, segment.length() - 1);
          route.parameters().stream()
              .filter(it -> it.kind() == RouteParameter.Kind.PATH)
              .filter(it -> it.name().equals(pathvar))
              .findFirst()
              .ifPresent(it -> parameterFactory.accept(newPath::uriParameter, it));
        }
        path = newPath;
      }
      path = Optional.ofNullable(path).orElseGet(() -> raml.path("/"));
      path.setDescription(route.summary()
          .map(Raml::yamlText)
          .orElse(null));
      RamlMethod method = path.method(route.method());
      method.setDescription(route.description()
          .map(Raml::yamlText)
          .orElse(null));

      /** Check for files, if we find one use multipart to render them: */
      List<RouteParameter> files = route.parameters().stream()
          .filter(it -> it.kind() == RouteParameter.Kind.FILE)
          .collect(Collectors.toList());
      if (files.size() > 0) {
        route.parameters().stream()
            .filter(it -> it.kind() == RouteParameter.Kind.QUERY)
            .forEach(it -> parameterFactory.accept(method::formParameter, it));
        files.forEach(it -> {
          parameterFactory.accept(method::formParameter, it);
          method.setMediaType(ImmutableList.of(MediaType.multipart.name()));
        });
      } else {
        route.parameters().stream()
            .filter(it -> it.kind() == RouteParameter.Kind.QUERY)
            .forEach(it -> parameterFactory.accept(method::queryParameter, it));
      }
      /** Consumes: */
      List<String> consumes = route.consumes();
      if (consumes.size() == 0 && !defaultMediaType) {
        consumes = ImmutableList.of(MediaType.json.name());
      }
      method.setMediaType(consumes);

      /** Headers: */
      route.parameters().stream()
          .filter(it -> it.kind() == RouteParameter.Kind.HEADER)
          .forEach(it -> parameterFactory.accept(method::headerParameter, it));

      /** Body: */
      route.parameters().stream()
          .filter(it -> it.kind() == RouteParameter.Kind.BODY)
          .forEach(it -> {
            method.setMediaType(route.consumes());
            method.setBody(raml.define(it.type()));
          });
      /** Response: */
      List<String> produces = route.produces();
      if (produces.size() == 0) {
        produces = ImmutableList.of(MediaType.json.name());
      }
      RouteResponse returns = route.response();
      Map<Integer, String> status = returns.status();
      Integer statusCode = returns.statusCode();
      RamlResponse response = method.response(statusCode);
      response.setDescription(yamlText(returns.description().orElseGet(() -> FriendlyTypeName
          .name(returns.type()))));
      produces.forEach(type -> response.setMediaType(type, raml.define(returns.type())));
      status.entrySet().stream()
          .filter(it -> !statusCode.equals(it.getKey()))
          .forEach(it -> method.response(it.getKey()).setDescription(it.getValue()));
    }

    return raml;
  }
public static Raml build(Raml base, List<RouteMethod> routes) {
    Raml raml = Optional.ofNullable(base).orElseGet(Raml::new);
    BiFunction<RamlPath, String, RamlPath> pathFactory = (path, segment) ->
        path == null ? raml.path(segment) : path.path(segment);

    BiConsumer<Function<String, RamlParameter>, RouteParameter> parameterFactory = (factory, parameter) -> {
      RamlParameter p = factory.apply(parameter.name());
      p.setDescription(parameter.description()
          .map(Raml::yamlText)
          .orElse(null));
      List<String> enums = parameter.enums();
      if (enums.size() > 0) {
        p.setType(RamlType.STRING);
        p.setEnum(enums);
      } else {
        p.setType(raml.define(parameter.type()));
      }
      p.setRequired(!parameter.optional());
      p.setDefault(parameter.defaultValue());
    };

    /** Default consumes/produces: */
    Set<String> alltypes = new LinkedHashSet<>();
    Consumer<Function<RouteMethod, List<String>>> mediaTypes = types ->
        routes.stream().forEach(r -> types.apply(r).forEach(alltypes::add));
    mediaTypes.accept(RouteMethod::consumes);
    mediaTypes.accept(RouteMethod::produces);
    boolean defaultMediaType = false;
    if (alltypes.size() == 0) {
      raml.setMediaType(ImmutableList.of(MediaType.json.name()));
      defaultMediaType = true;
    } else if (alltypes.size() == 1) {
      raml.setMediaType(ImmutableList.of(alltypes.iterator().next()));
      defaultMediaType = true;
    }

    for (RouteMethod route : routes) {
      List<String> segments = Splitter.on("/")
          .trimResults()
          .omitEmptyStrings()
          .splitToList(route.pattern());
      RamlPath path = null;
      for (String segment : segments) {
        RamlPath newPath = pathFactory.apply(path, "/" + segment);
        if (segment.startsWith("{") && segment.endsWith("}")) {
          String pathvar = segment.substring(1, segment.length() - 1);
          route.parameters().stream()
              .filter(it -> it.kind() == RouteParameter.Kind.PATH)
              .filter(it -> it.name().equals(pathvar))
              .findFirst()
              .ifPresent(it -> parameterFactory.accept(newPath::uriParameter, it));
        }
        path = newPath;
      }
      path = Optional.ofNullable(path).orElseGet(() -> raml.path("/"));
      path.setDescription(route.summary()
          .map(Raml::yamlText)
          .orElse(null));
      RamlMethod method = path.method(route.method());
      method.setDescription(route.description()
          .map(Raml::yamlText)
          .orElse(null));

      /** Check for files, if we find one use multipart to render them: */
      List<RouteParameter> files = route.parameters().stream()
          .filter(it -> it.kind() == RouteParameter.Kind.FILE)
          .collect(Collectors.toList());
      if (files.size() > 0) {
        route.parameters().stream()
            .filter(it -> it.kind() == RouteParameter.Kind.QUERY)
            .forEach(it -> parameterFactory.accept(method::formParameter, it));
        files.forEach(it -> {
          parameterFactory.accept(method::formParameter, it);
          method.setMediaType(ImmutableList.of(MediaType.multipart.name()));
        });
      } else {
        route.parameters().stream()
            .filter(it -> it.kind() == RouteParameter.Kind.QUERY)
            .forEach(it -> parameterFactory.accept(method::queryParameter, it));
      }
      /** Consumes: */
      List<String> consumes = route.consumes();
      if (consumes.size() == 0 && !defaultMediaType) {
        consumes = ImmutableList.of(MediaType.json.name());
      }
      method.setMediaType(consumes);

      /** Headers: */
      route.parameters().stream()
          .filter(it -> it.kind() == RouteParameter.Kind.HEADER)
          .forEach(it -> parameterFactory.accept(method::headerParameter, it));

      /** Body: */
      route.parameters().stream()
          .filter(it -> it.kind() == RouteParameter.Kind.BODY)
          .forEach(it -> {
            method.setMediaType(route.consumes());
            method.setBody(raml.define(it.type()));
          });
      /** Response: */
      List<String> produces = route.produces();
      if (produces.size() == 0) {
        produces = ImmutableList.of(MediaType.json.name());
      }
      RouteResponse returns = route.response();
      Map<Integer, String> status = returns.status();
      Integer statusCode = returns.statusCode();
      RamlResponse response = method.response(statusCode);
      response.setDescription(yamlText(returns.description().orElseGet(() -> FriendlyTypeName
          .name(returns.type()))));
      produces.forEach(type -> response.setMediaType(type, raml.define(returns.type())));
      status.entrySet().stream()
          .filter(it -> !statusCode.equals(it.getKey()))
          .forEach(it -> method.response(it.getKey()).setDescription(it.getValue()));
    }

    return raml;
  }
Build a {@link Raml} object from list of {@link RouteMethod}.

@param base Base raml (might be null).
@param routes List of routes.
@return RAML object.