diff --git a/.gitignore b/.gitignore index 27709626..b54447ca 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.iml .gradle build +/bin/ diff --git a/RELEASENOTES.adoc b/RELEASENOTES.adoc index 3e96f98d..d318529a 100644 --- a/RELEASENOTES.adoc +++ b/RELEASENOTES.adoc @@ -1,80 +1,86 @@ -= Release Notes - -== Version 0.1.0 -* Initial version with support for AsciiDoc and Markdown - -== Version 0.2.0 -* This version is not downward compatible. This version supports includes of example files and JSON/XML Schema files. See documentation. - -=== Version 0.2.1 -* Signed jar files and published in Maven Central - -=== Version 0.2.2 -* Fixed wrong dependency version to io.github.robwin:markup-document-builder - -=== Version 0.2.3 -* Fixed issue #7: @ApiModelProperty metadata are ignored for definitions file - -=== Version 0.2.4 -* Fixed issue #8: logback.xml on the classpath -* Fixed issue #13: unknown format not supported for properties - -== Version 0.3.0 -* Support of YAML or JSON String as input. - -== Version 0.4.0 -* Updated Swagger-Parser from 1.0.0 to 1.0.5 -* Updated commons-lang to commons-lang3 -* Swagger2MarkupConverter generates three documents now: overview, paths and definitions -* Support for enums in HeaderParameter, QueryParameter, FormParameter and QueryParameter -* Support for global consumes, produces and tags - -== Version 0.5.0 -* Support for including hand-written descriptions instead of using Swagger Annotations for descriptions - -=== Version 0.5.1 -* Bugfix: Definition name must be lowercase so that descriptions file can be found - -=== Version 0.5.2 -* Swagger License is not mandatory anymore -* Updated markup-document-builder from v0.1.3 to v0.1.4 - -=== Version 0.5.3 -* Fixed compiler warning: [options] bootstrap class path not set in conjunction with -source 1.7 - -== Version 0.6.0 -* Updated swagger-parser from v1.0.5 to v1.0.6 -* Support for default values in Parameters and Model properties - -=== Version 0.6.1 -* Updated swagger-parser from v1.0.6 to v1.0.8 - -=== Version 0.6.2 -* curl-request.adoc from spring-restdocs is also added to the example chapters - -=== Version 0.6.3 -* Added possibility to write object definitions to separate files. Issue #19 - -== Version 0.7.0 -* Added support for both reference models and composed models - -=== Version 0.7.1 -* Workaround: If the type of a BodyParameter is String and not a Model, the schema is null and lost. Therefore the fallback type of a BodyParameter is String now. - -== Version 0.8.0 -* Enhancement #26 and #27: Added a pre-process hook to modify a Swagger Model before it is converted. -* Bugfix #29: Tags are rendered twice - -== Version 0.9.0 -* Updated swagger-parser from v1.0.8 to v1.0.13 -* Support for global responses and parameters - -=== Version 0.9.1 -* Added support to group the paths by tags or as-is -* Added support to order the definitions by natural ordering or as-is - -=== Version 0.9.2 -* Multi language support. Added russian. - -=== Version 0.9.3 -* Updated swagger-parser from v1.0.13 to v1.0.16 \ No newline at end of file += Release Notes + +== Version 0.1.0 +* Initial version with support for AsciiDoc and Markdown + +== Version 0.2.0 +* This version is not downward compatible. This version supports includes of example files and JSON/XML Schema files. See documentation. + +=== Version 0.2.1 +* Signed jar files and published in Maven Central + +=== Version 0.2.2 +* Fixed wrong dependency version to io.github.robwin:markup-document-builder + +=== Version 0.2.3 +* Fixed issue #7: @ApiModelProperty metadata are ignored for definitions file + +=== Version 0.2.4 +* Fixed issue #8: logback.xml on the classpath +* Fixed issue #13: unknown format not supported for properties + +== Version 0.3.0 +* Support of YAML or JSON String as input. + +== Version 0.4.0 +* Updated Swagger-Parser from 1.0.0 to 1.0.5 +* Updated commons-lang to commons-lang3 +* Swagger2MarkupConverter generates three documents now: overview, paths and definitions +* Support for enums in HeaderParameter, QueryParameter, FormParameter and QueryParameter +* Support for global consumes, produces and tags + +== Version 0.5.0 +* Support for including hand-written descriptions instead of using Swagger Annotations for descriptions + +=== Version 0.5.1 +* Bugfix: Definition name must be lowercase so that descriptions file can be found + +=== Version 0.5.2 +* Swagger License is not mandatory anymore +* Updated markup-document-builder from v0.1.3 to v0.1.4 + +=== Version 0.5.3 +* Fixed compiler warning: [options] bootstrap class path not set in conjunction with -source 1.7 + +== Version 0.6.0 +* Updated swagger-parser from v1.0.5 to v1.0.6 +* Support for default values in Parameters and Model properties + +=== Version 0.6.1 +* Updated swagger-parser from v1.0.6 to v1.0.8 + +=== Version 0.6.2 +* curl-request.adoc from spring-restdocs is also added to the example chapters + +=== Version 0.6.3 +* Added possibility to write object definitions to separate files. Issue #19 + +== Version 0.7.0 +* Added support for both reference models and composed models + +=== Version 0.7.1 +* Workaround: If the type of a BodyParameter is String and not a Model, the schema is null and lost. Therefore the fallback type of a BodyParameter is String now. + +== Version 0.8.0 +* Enhancement #26 and #27: Added a pre-process hook to modify a Swagger Model before it is converted. +* Bugfix #29: Tags are rendered twice + +== Version 0.9.0 +* Updated swagger-parser from v1.0.8 to v1.0.13 +* Support for global responses and parameters + +=== Version 0.9.1 +* Added support to group the paths by tags or as-is +* Added support to order the definitions by natural ordering or as-is + +=== Version 0.9.2 +* Multi language support. Added russian. + +=== Version 0.9.3 +* Updated swagger-parser from v1.0.13 to v1.0.16 +* Enhancement #61 Refactor separated documents logic to support inter-document cross-references +* Enhancement #53 : support for tags, paths and methods ordering +* Enhancement #51 : Support for separated operations files +* Enhancement #52: Markdown generation for inline schemas + + diff --git a/build.gradle b/build.gradle index 839f7697..c2883928 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ dependencies { dependencyManagement { dependencies { dependency "io.github.robwin:markup-document-builder:0.1.6-SNAPSHOT" - dependency "io.swagger:swagger-compat-spec-parser:1.0.16" + dependency "io.swagger:swagger-compat-spec-parser:1.0.17" dependency "commons-collections:commons-collections:3.2.1" dependency "commons-io:commons-io:2.4" dependency "junit:junit:4.11" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 70bf843d..4cc1564c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Feb 07 12:01:31 CET 2016 +#Wed Feb 10 13:33:16 CET 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip diff --git a/src/main/java/io/github/robwin/swagger2markup/Swagger2MarkupConverter.java b/src/main/java/io/github/robwin/swagger2markup/Swagger2MarkupConverter.java index 024dae93..92bf3e4c 100644 --- a/src/main/java/io/github/robwin/swagger2markup/Swagger2MarkupConverter.java +++ b/src/main/java/io/github/robwin/swagger2markup/Swagger2MarkupConverter.java @@ -46,15 +46,6 @@ public class Swagger2MarkupConverter { private static final Logger LOG = LoggerFactory.getLogger(Swagger2MarkupConverter.class); private final Swagger2MarkupConfig swagger2MarkupConfig; - private static final String OVERVIEW_DOCUMENT = "overview"; - private static final String PATHS_DOCUMENT = "paths"; - private static final String DEFINITIONS_DOCUMENT = "definitions"; - private static final String SECURITY_DOCUMENT = "security"; - - private static final Comparator DEFAULT_TAG_ORDERING = Ordering.natural(); - private static final Comparator DEFAULT_PATH_ORDERING = Ordering.natural(); - private static final Comparator DEFAULT_PATH_METHOD_ORDERING = Ordering.explicit(HttpMethod.GET, HttpMethod.PUT, HttpMethod.POST, HttpMethod.DELETE, HttpMethod.PATCH, HttpMethod.HEAD, HttpMethod.OPTIONS); - private static final Comparator DEFAULT_DEFINITION_ORDERING = Ordering.natural(); /** * @param swagger2MarkupConfig the configuration @@ -130,10 +121,10 @@ public class Swagger2MarkupConverter { * @throws IOException if a file cannot be written */ private void buildDocuments(String directory) throws IOException { - new OverviewDocument(swagger2MarkupConfig).build().writeToFile(directory, OVERVIEW_DOCUMENT, StandardCharsets.UTF_8); - new PathsDocument(swagger2MarkupConfig, directory).build().writeToFile(directory, PATHS_DOCUMENT, StandardCharsets.UTF_8); - new DefinitionsDocument(swagger2MarkupConfig, directory).build().writeToFile(directory, DEFINITIONS_DOCUMENT, StandardCharsets.UTF_8); - new SecurityDocument(swagger2MarkupConfig).build().writeToFile(directory, SECURITY_DOCUMENT, StandardCharsets.UTF_8); + new OverviewDocument(swagger2MarkupConfig, directory).build().writeToFile(directory, swagger2MarkupConfig.getOverviewDocument(), StandardCharsets.UTF_8); + new PathsDocument(swagger2MarkupConfig, directory).build().writeToFile(directory, swagger2MarkupConfig.getPathsDocument(), StandardCharsets.UTF_8); + new DefinitionsDocument(swagger2MarkupConfig, directory).build().writeToFile(directory, swagger2MarkupConfig.getDefinitionsDocument(), StandardCharsets.UTF_8); + new SecurityDocument(swagger2MarkupConfig, directory).build().writeToFile(directory, swagger2MarkupConfig.getSecurityDocument(), StandardCharsets.UTF_8); } /** @@ -143,10 +134,10 @@ public class Swagger2MarkupConverter { */ private String buildDocuments() { StringBuilder sb = new StringBuilder(); - sb.append(new OverviewDocument(swagger2MarkupConfig).build().toString()); + sb.append(new OverviewDocument(swagger2MarkupConfig, null).build().toString()); sb.append(new PathsDocument(swagger2MarkupConfig, null).build().toString()); sb.append(new DefinitionsDocument(swagger2MarkupConfig, null).build().toString()); - sb.append(new SecurityDocument(swagger2MarkupConfig).build().toString()); + sb.append(new SecurityDocument(swagger2MarkupConfig, null).build().toString()); return sb.toString(); } @@ -163,10 +154,10 @@ public class Swagger2MarkupConverter { private MarkupLanguage markupLanguage = MarkupLanguage.ASCIIDOC; private Language outputLanguage = Language.EN; private int inlineSchemaDepthLevel = 0; - private Comparator tagOrdering = DEFAULT_TAG_ORDERING; - private Comparator pathOrdering = DEFAULT_PATH_ORDERING; - private Comparator pathMethodOrdering = DEFAULT_PATH_METHOD_ORDERING; - private Comparator definitionOrdering = DEFAULT_DEFINITION_ORDERING; + private Comparator tagOrdering = Ordering.natural(); + private Comparator pathOrdering = Ordering.natural(); + private Comparator pathMethodOrdering = Ordering.explicit(HttpMethod.GET, HttpMethod.PUT, HttpMethod.POST, HttpMethod.DELETE, HttpMethod.PATCH, HttpMethod.HEAD, HttpMethod.OPTIONS); + private Comparator definitionOrdering = Ordering.natural(); /** * Creates a Builder using a given Swagger source. diff --git a/src/main/java/io/github/robwin/swagger2markup/builder/document/DefinitionsDocument.java b/src/main/java/io/github/robwin/swagger2markup/builder/document/DefinitionsDocument.java index 9c9fe4ca..63837af8 100644 --- a/src/main/java/io/github/robwin/swagger2markup/builder/document/DefinitionsDocument.java +++ b/src/main/java/io/github/robwin/swagger2markup/builder/document/DefinitionsDocument.java @@ -32,8 +32,10 @@ import io.swagger.models.refs.RefFormat; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.Validate; +import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -57,18 +59,15 @@ public class DefinitionsDocument extends MarkupDocument { private static final String XML = "xml"; private static final String DESCRIPTION_FOLDER_NAME = "definitions"; private static final String DESCRIPTION_FILE_NAME = "description"; - private static final String SEPARATED_DEFINITIONS_FOLDER_NAME = "definitions"; private boolean schemasEnabled; private String schemasFolderPath; private boolean handWrittenDescriptionsEnabled; private String descriptionsFolderPath; - private boolean separatedDefinitionsEnabled; - private String outputDirectory; private final int inlineSchemaDepthLevel; private final Comparator definitionOrdering; public DefinitionsDocument(Swagger2MarkupConfig swagger2MarkupConfig, String outputDirectory){ - super(swagger2MarkupConfig); + super(swagger2MarkupConfig, outputDirectory); ResourceBundle labels = ResourceBundle.getBundle("lang/labels", swagger2MarkupConfig.getOutputLanguage().toLocale()); @@ -103,7 +102,6 @@ public class DefinitionsDocument extends MarkupDocument { logger.debug("Include hand-written descriptions is disabled."); } } - this.separatedDefinitionsEnabled = swagger2MarkupConfig.isSeparatedDefinitions(); if(this.separatedDefinitionsEnabled){ if (logger.isDebugEnabled()) { logger.debug("Create separated definition files is enabled."); @@ -114,7 +112,6 @@ public class DefinitionsDocument extends MarkupDocument { logger.debug("Create separated definition files is disabled."); } } - this.outputDirectory = outputDirectory; this.definitionOrdering = swagger2MarkupConfig.getDefinitionOrdering(); } @@ -158,22 +155,28 @@ public class DefinitionsDocument extends MarkupDocument { private void processDefinition(Map definitions, String definitionName, Model model) { - definition(definitions, definitionName, model, this.markupDocBuilder); - if (separatedDefinitionsEnabled) { MarkupDocBuilder defDocBuilder = MarkupDocBuilders.documentBuilder(markupLanguage); definition(definitions, definitionName, model, defDocBuilder); - String definitionFileName = definitionName.toLowerCase(); + File definitionFile = new File(outputDirectory, resolveDefinitionDocument(definitionName)); try { - defDocBuilder.writeToFile(Paths.get(outputDirectory, SEPARATED_DEFINITIONS_FOLDER_NAME).toString(), definitionFileName, StandardCharsets.UTF_8); + String definitionDirectory = FilenameUtils.getFullPath(definitionFile.getPath()); + String definitionFileName = FilenameUtils.getName(definitionFile.getPath()); + + defDocBuilder.writeToFileWithoutExtension(definitionDirectory, definitionFileName, StandardCharsets.UTF_8); } catch (IOException e) { if (logger.isWarnEnabled()) { - logger.warn(String.format("Failed to write definition file: %s", definitionFileName), e); + logger.warn(String.format("Failed to write definition file: %s", definitionFile), e); } } if (logger.isInfoEnabled()) { - logger.info("Separate definition file produced: {}", definitionFileName); + logger.info("Separate definition file produced: {}", definitionFile); } + + definitionRef(definitionName, this.markupDocBuilder); + + } else { + definition(definitions, definitionName, model, this.markupDocBuilder); } } @@ -195,12 +198,20 @@ public class DefinitionsDocument extends MarkupDocument { * @param docBuilder the docbuilder do use for output */ private void definition(Map definitions, String definitionName, Model model, MarkupDocBuilder docBuilder){ - docBuilder.sectionTitleLevel2(definitionName); + addDefinitionTitle(definitionName, docBuilder); descriptionSection(definitionName, model, docBuilder); propertiesSection(definitions, definitionName, model, docBuilder); definitionSchema(definitionName, docBuilder); } + private void addDefinitionTitle(String title, MarkupDocBuilder docBuilder) { + docBuilder.sectionTitleLevel2(title); + } + + private void definitionRef(String definitionName, MarkupDocBuilder docBuilder){ + addDefinitionTitle(docBuilder.crossReferenceAsString(resolveDefinitionDocument(definitionName), definitionName, definitionName), docBuilder); + } + private class DefinitionPropertyDescriptor extends PropertyDescriptor { public DefinitionPropertyDescriptor(Type type) { @@ -230,7 +241,10 @@ public class DefinitionsDocument extends MarkupDocument { Map properties = getAllProperties(definitions, model); Type type = new ObjectType(definitionName, properties); - List localDefinitions = typeProperties(type, inlineSchemaDepthLevel, new PropertyDescriptor(type), docBuilder); + String definitionsRelativePath = null; + if (this.separatedDefinitionsEnabled) + definitionsRelativePath = ".."; + List localDefinitions = typeProperties(type, inlineSchemaDepthLevel, new PropertyDescriptor(type), definitionsRelativePath, docBuilder); inlineDefinitions(localDefinitions, inlineSchemaDepthLevel - 1, docBuilder); } @@ -367,7 +381,10 @@ public class DefinitionsDocument extends MarkupDocument { if(CollectionUtils.isNotEmpty(definitions)){ for (Type definition: definitions) { addInlineDefinitionTitle(definition.getName(), definition.getUniqueName(), docBuilder); - List localDefinitions = typeProperties(definition, depth, new DefinitionPropertyDescriptor(definition), docBuilder); + String definitionsRelativePath = null; + if (this.separatedDefinitionsEnabled) + definitionsRelativePath = ".."; + List localDefinitions = typeProperties(definition, depth, new DefinitionPropertyDescriptor(definition), definitionsRelativePath, docBuilder); for (Type localDefinition : localDefinitions) inlineDefinitions(Collections.singletonList(localDefinition), depth - 1, docBuilder); } diff --git a/src/main/java/io/github/robwin/swagger2markup/builder/document/MarkupDocument.java b/src/main/java/io/github/robwin/swagger2markup/builder/document/MarkupDocument.java index 05506031..21568169 100644 --- a/src/main/java/io/github/robwin/swagger2markup/builder/document/MarkupDocument.java +++ b/src/main/java/io/github/robwin/swagger2markup/builder/document/MarkupDocument.java @@ -18,6 +18,7 @@ */ package io.github.robwin.swagger2markup.builder.document; +import com.google.common.base.Function; import io.github.robwin.markup.builder.MarkupDocBuilder; import io.github.robwin.markup.builder.MarkupDocBuilders; import io.github.robwin.markup.builder.MarkupLanguage; @@ -33,6 +34,8 @@ import org.apache.commons.collections.MapUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nullable; +import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.*; @@ -61,13 +64,22 @@ public abstract class MarkupDocument { protected Swagger swagger; protected MarkupLanguage markupLanguage; protected MarkupDocBuilder markupDocBuilder; + protected boolean separatedDefinitionsEnabled; + protected String separatedDefinitionsFolder; + protected String definitionsDocument; + protected String outputDirectory; + protected static AtomicInteger typeIdCount = new AtomicInteger(0); - MarkupDocument(Swagger2MarkupConfig swagger2MarkupConfig) { + MarkupDocument(Swagger2MarkupConfig swagger2MarkupConfig, String outputDirectory) { this.swagger = swagger2MarkupConfig.getSwagger(); this.markupLanguage = swagger2MarkupConfig.getMarkupLanguage(); this.markupDocBuilder = MarkupDocBuilders.documentBuilder(markupLanguage); + this.separatedDefinitionsEnabled = swagger2MarkupConfig.isSeparatedDefinitions(); + this.separatedDefinitionsFolder = swagger2MarkupConfig.getSeparatedDefinitionsFolder(); + this.definitionsDocument = swagger2MarkupConfig.getDefinitionsDocument(); + this.outputDirectory = outputDirectory; ResourceBundle labels = ResourceBundle.getBundle("lang/labels", swagger2MarkupConfig.getOutputLanguage().toLocale()); @@ -84,6 +96,39 @@ public abstract class MarkupDocument { NO_CONTENT = labels.getString("no_content"); } + protected String normalizeDefinitionFileName(String definitionName) { + return definitionName.toLowerCase(); + } + + protected String resolveDefinitionDocument(String definitionName, String relativePath) { + if (this.outputDirectory == null) + return null; + else if (this.separatedDefinitionsEnabled) + return new File(new File(relativePath, this.separatedDefinitionsFolder), this.markupDocBuilder.addfileExtension(normalizeDefinitionFileName(definitionName))).getPath(); + else + return new File(relativePath, this.markupDocBuilder.addfileExtension(this.definitionsDocument)).getPath(); + } + + protected String resolveDefinitionDocument(String definitionName) { + return resolveDefinitionDocument(definitionName, null); + } + + class DefinitionDocumentResolver implements Function { + private String relativePath; + + public DefinitionDocumentResolver(String relativePath) { + this.relativePath = relativePath; + } + + public DefinitionDocumentResolver() {} + + @Nullable + @Override + public String apply(@Nullable String definitionName) { + return resolveDefinitionDocument(definitionName, relativePath); + } + } + /** * Builds the MarkupDocument. * @@ -115,7 +160,7 @@ public abstract class MarkupDocument { return name + "-" + typeIdCount.getAndIncrement(); } - public List typeProperties(Type type, int depth, PropertyDescriptor propertyDescriptor, MarkupDocBuilder docBuilder) { + public List typeProperties(Type type, int depth, PropertyDescriptor propertyDescriptor, String definitionsRelativePath, MarkupDocBuilder docBuilder) { List localDefinitions = new ArrayList<>(); if (type instanceof ObjectType) { ObjectType objectType = (ObjectType) type; @@ -130,7 +175,7 @@ public abstract class MarkupDocument { for (Map.Entry propertyEntry : objectType.getProperties().entrySet()) { Property property = propertyEntry.getValue(); String propertyName = propertyEntry.getKey(); - Type propertyType = PropertyUtils.getType(property); + Type propertyType = PropertyUtils.getType(property, new DefinitionDocumentResolver(definitionsRelativePath)); if (depth > 0 && propertyType instanceof ObjectType) { if (MapUtils.isNotEmpty(((ObjectType) propertyType).getProperties())) { propertyType.setName(propertyName); diff --git a/src/main/java/io/github/robwin/swagger2markup/builder/document/OverviewDocument.java b/src/main/java/io/github/robwin/swagger2markup/builder/document/OverviewDocument.java index 8be64943..165104cc 100644 --- a/src/main/java/io/github/robwin/swagger2markup/builder/document/OverviewDocument.java +++ b/src/main/java/io/github/robwin/swagger2markup/builder/document/OverviewDocument.java @@ -45,8 +45,8 @@ public class OverviewDocument extends MarkupDocument { private final String BASE_PATH; private final String SCHEMES; - public OverviewDocument(Swagger2MarkupConfig swagger2MarkupConfig){ - super(swagger2MarkupConfig); + public OverviewDocument(Swagger2MarkupConfig swagger2MarkupConfig, String outputDirectory){ + super(swagger2MarkupConfig, outputDirectory); ResourceBundle labels = ResourceBundle.getBundle("lang/labels", swagger2MarkupConfig.getOutputLanguage().toLocale()); diff --git a/src/main/java/io/github/robwin/swagger2markup/builder/document/PathsDocument.java b/src/main/java/io/github/robwin/swagger2markup/builder/document/PathsDocument.java index aa0ae069..3303cc69 100644 --- a/src/main/java/io/github/robwin/swagger2markup/builder/document/PathsDocument.java +++ b/src/main/java/io/github/robwin/swagger2markup/builder/document/PathsDocument.java @@ -41,10 +41,12 @@ import io.swagger.models.properties.Property; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.text.WordUtils; import org.apache.commons.lang3.tuple.Pair; +import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -53,8 +55,7 @@ import java.util.*; import java.util.regex.Pattern; import static io.github.robwin.swagger2markup.utils.TagUtils.*; -import static org.apache.commons.lang3.StringUtils.defaultString; -import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.*; /** * @author Robert Winkler @@ -78,7 +79,6 @@ public class PathsDocument extends MarkupDocument { private static final String CURL_EXAMPLE_FILE_NAME = "curl-request"; private static final String DESCRIPTION_FOLDER_NAME = "paths"; private static final String DESCRIPTION_FILE_NAME = "description"; - private static final String SEPARATED_PATHS_FOLDER_NAME = "paths"; private final String PARAMETER; private static final Pattern FILENAME_FORBIDDEN_PATTERN = Pattern.compile("[^0-9A-Za-z-_]+"); @@ -92,10 +92,12 @@ public class PathsDocument extends MarkupDocument { private final Comparator pathOrdering; private final Comparator pathMethodOrdering; private boolean separatedPathsEnabled; - private String outputDirectory; + private String separatedPathsFolder; + private String pathsDocument; + public PathsDocument(Swagger2MarkupConfig swagger2MarkupConfig, String outputDirectory){ - super(swagger2MarkupConfig); + super(swagger2MarkupConfig, outputDirectory); ResourceBundle labels = ResourceBundle.getBundle("lang/labels", swagger2MarkupConfig.getOutputLanguage().toLocale()); @@ -112,6 +114,7 @@ public class PathsDocument extends MarkupDocument { HTTP_CODE_COLUMN = labels.getString("http_code_column"); PARAMETER = labels.getString("parameter"); + this.pathsDocument = swagger2MarkupConfig.getPathsDocument(); this.inlineSchemaDepthLevel = swagger2MarkupConfig.getInlineSchemaDepthLevel(); this.pathsGroupedBy = swagger2MarkupConfig.getPathsGroupedBy(); if(isNotBlank(swagger2MarkupConfig.getExamplesFolderPath())){ @@ -143,6 +146,7 @@ public class PathsDocument extends MarkupDocument { } this.separatedPathsEnabled = swagger2MarkupConfig.isSeparatedPaths(); + this.separatedPathsFolder = swagger2MarkupConfig.getSeparatedPathsFolder(); if(this.separatedPathsEnabled){ if (logger.isDebugEnabled()) { logger.debug("Create separated path files is enabled."); @@ -153,7 +157,6 @@ public class PathsDocument extends MarkupDocument { logger.debug("Create separated path files is disabled."); } } - this.outputDirectory = outputDirectory; tagOrdering = swagger2MarkupConfig.getTagOrdering(); pathOrdering = swagger2MarkupConfig.getPathOrdering(); pathMethodOrdering = swagger2MarkupConfig.getPathMethodOrdering(); @@ -228,30 +231,61 @@ public class PathsDocument extends MarkupDocument { } } - private void processPath(String methodAndPath, Operation operation) { + private String normalizePathFileName(String methodAndPath, Operation operation) { + String pathFileName = operation.getOperationId(); - path(methodAndPath, operation, this.markupDocBuilder); + if (pathFileName == null) + pathFileName = methodAndPath; + pathFileName = FILENAME_FORBIDDEN_PATTERN.matcher(pathFileName).replaceAll("_").toLowerCase(); + return pathFileName; + } + + private String resolvePathDocument(String methodAndPath, Operation operation) { + if (this.outputDirectory == null) + return null; + else if (this.separatedPathsEnabled) + return "./" + this.separatedPathsFolder + "/" + this.markupDocBuilder.addfileExtension(normalizePathFileName(methodAndPath, operation)); + else + return "./" + this.markupDocBuilder.addfileExtension(this.pathsDocument); + } + + private void processPath(String methodAndPath, Operation operation) { if (separatedPathsEnabled) { MarkupDocBuilder pathDocBuilder = MarkupDocBuilders.documentBuilder(markupLanguage); path(methodAndPath, operation, pathDocBuilder); - String pathFileName = operation.getOperationId(); - if (pathFileName == null) - pathFileName = methodAndPath; - pathFileName = FILENAME_FORBIDDEN_PATTERN.matcher(pathFileName).replaceAll("_").toLowerCase(); + File pathFile = new File(outputDirectory, resolvePathDocument(methodAndPath, operation)); + try { - pathDocBuilder.writeToFile(Paths.get(outputDirectory, SEPARATED_PATHS_FOLDER_NAME).toString(), pathFileName, StandardCharsets.UTF_8); + String pathDirectory = FilenameUtils.getFullPath(pathFile.getPath()); + String pathFileName = FilenameUtils.getName(pathFile.getPath()); + + pathDocBuilder.writeToFileWithoutExtension(pathDirectory, pathFileName, StandardCharsets.UTF_8); } catch (IOException e) { if (logger.isWarnEnabled()) { - logger.warn(String.format("Failed to write path file: %s", pathFileName), e); + logger.warn(String.format("Failed to write path file: %s", pathFile), e); } } if (logger.isInfoEnabled()) { - logger.info("Separate path file produced: {}", pathFileName); + logger.info("Separate path file produced: {}", pathFile); } + + pathRef(methodAndPath, operation, this.markupDocBuilder); + + } else { + path(methodAndPath, operation, this.markupDocBuilder); } } + + private String operationName(String methodAndPath, Operation operation) { + String operationName = operation.getSummary(); + if(isBlank(operationName)) { + operationName = methodAndPath; + } + return operationName; + } + /** * Builds an operation. * @@ -272,6 +306,18 @@ public class PathsDocument extends MarkupDocument { } } + /** + * Builds a cross-reference to separated path file + * @param methodAndPath the Method of the operation and the URL of the path + * @param operation the Swagger Operation + */ + private void pathRef(String methodAndPath, Operation operation, MarkupDocBuilder docBuilder) { + String document = resolvePathDocument(methodAndPath, operation); + String operationName = operationName(methodAndPath, operation); + + addPathTitle(docBuilder.crossReferenceAsString(document, operationName, operationName), docBuilder); + } + /** * Adds the path title to the document. If the operation has a summary, the title is the summary. * Otherwise the title is the method of the operation and the URL of the path. @@ -280,15 +326,13 @@ public class PathsDocument extends MarkupDocument { * @param operation the Swagger Operation */ private void pathTitle(String methodAndPath, Operation operation, MarkupDocBuilder docBuilder) { - String summary = operation.getSummary(); - String title; - if(isNotBlank(summary)) { - title = summary; - addPathTitle(title, docBuilder); + String operationName = operationName(methodAndPath, operation); + + addPathTitle(operationName, docBuilder); + if(operationName.equals(operation.getSummary())) { docBuilder.listing(methodAndPath); - }else{ - addPathTitle(methodAndPath, docBuilder); } + if (logger.isInfoEnabled()) { logger.info("Path processed: {}", methodAndPath); } @@ -370,7 +414,7 @@ public class PathsDocument extends MarkupDocument { new MarkupTableColumn(SCHEMA_COLUMN, 1), new MarkupTableColumn(DEFAULT_COLUMN, 1)); for(Parameter parameter : parameters){ - Type type = ParameterUtils.getType(parameter); + Type type = ParameterUtils.getType(parameter, new DefinitionDocumentResolver()); if (inlineSchemaDepthLevel > 0 && type instanceof ObjectType) { if (MapUtils.isNotEmpty(((ObjectType) type).getProperties())) { String localTypeName = parameter.getName(); @@ -463,7 +507,7 @@ public class PathsDocument extends MarkupDocument { else sortedTags = new TreeSet<>(this.tagOrdering); sortedTags.addAll(tags); - this.markupDocBuilder.unorderedList(new ArrayList<>(sortedTags)); + docBuilder.unorderedList(new ArrayList<>(sortedTags)); } } } @@ -560,7 +604,8 @@ public class PathsDocument extends MarkupDocument { if (securityDefinitions != null && securityDefinitions.containsKey(securityKey)) { type = securityDefinitions.get(securityKey).getType(); } - List content = Arrays.asList(type, docBuilder.crossReferenceAsString(securityKey, securityKey), + List content = Arrays.asList(type, docBuilder.crossReferenceAsString(securityKey, + securityKey, securityKey), Joiner.on(",").join(securityEntry.getValue())); cells.add(content); } @@ -615,7 +660,7 @@ public class PathsDocument extends MarkupDocument { Response response = entry.getValue(); if(response.getSchema() != null){ Property property = response.getSchema(); - Type type = PropertyUtils.getType(property); + Type type = PropertyUtils.getType(property, new DefinitionDocumentResolver()); if (this.inlineSchemaDepthLevel > 0 && type instanceof ObjectType) { if (MapUtils.isNotEmpty(((ObjectType) type).getProperties())) { String localTypeName = RESPONSE + " " + entry.getKey(); @@ -650,7 +695,10 @@ public class PathsDocument extends MarkupDocument { if(CollectionUtils.isNotEmpty(definitions)){ for (Type definition: definitions) { addInlineDefinitionTitle(definition.getName(), definition.getUniqueName(), docBuilder); - List localDefinitions = typeProperties(definition, depth, new PropertyDescriptor(definition), this.markupDocBuilder); + String definitionsRelativePath = null; + if (this.separatedPathsEnabled) + definitionsRelativePath = ".."; + List localDefinitions = typeProperties(definition, depth, new PropertyDescriptor(definition), definitionsRelativePath, docBuilder); for (Type localDefinition : localDefinitions) inlineDefinitions(Collections.singletonList(localDefinition), depth - 1, docBuilder); } diff --git a/src/main/java/io/github/robwin/swagger2markup/builder/document/SecurityDocument.java b/src/main/java/io/github/robwin/swagger2markup/builder/document/SecurityDocument.java index dffb9cda..285d2662 100644 --- a/src/main/java/io/github/robwin/swagger2markup/builder/document/SecurityDocument.java +++ b/src/main/java/io/github/robwin/swagger2markup/builder/document/SecurityDocument.java @@ -49,8 +49,8 @@ public class SecurityDocument extends MarkupDocument { private final String AUTHORIZATION_URL; private final String TOKEN_URL; - public SecurityDocument(Swagger2MarkupConfig swagger2MarkupConfig) { - super(swagger2MarkupConfig); + public SecurityDocument(Swagger2MarkupConfig swagger2MarkupConfig, String outputDirectory) { + super(swagger2MarkupConfig, outputDirectory); ResourceBundle labels = ResourceBundle.getBundle("lang/labels", swagger2MarkupConfig.getOutputLanguage().toLocale()); diff --git a/src/main/java/io/github/robwin/swagger2markup/config/Swagger2MarkupConfig.java b/src/main/java/io/github/robwin/swagger2markup/config/Swagger2MarkupConfig.java index cc7f613b..b8cdb020 100644 --- a/src/main/java/io/github/robwin/swagger2markup/config/Swagger2MarkupConfig.java +++ b/src/main/java/io/github/robwin/swagger2markup/config/Swagger2MarkupConfig.java @@ -46,6 +46,14 @@ public class Swagger2MarkupConfig { private final Comparator pathMethodOrdering; private final Comparator definitionOrdering; + private static final String OVERVIEW_DOCUMENT = "overview"; + private static final String PATHS_DOCUMENT = "paths"; + private static final String DEFINITIONS_DOCUMENT = "definitions"; + private static final String SECURITY_DOCUMENT = "security"; + + private static final String SEPARATED_DEFINITIONS_FOLDER = "definitions"; + private static final String SEPARATED_PATHS_FOLDER = "paths"; + /** * @param swagger the Swagger source * @param markupLanguage the markup language which is used to generate the files @@ -144,4 +152,28 @@ public class Swagger2MarkupConfig { public Comparator getDefinitionOrdering() { return definitionOrdering; } + + public String getOverviewDocument() { + return OVERVIEW_DOCUMENT; + } + + public String getPathsDocument() { + return PATHS_DOCUMENT; + } + + public String getDefinitionsDocument() { + return DEFINITIONS_DOCUMENT; + } + + public String getSecurityDocument() { + return SECURITY_DOCUMENT; + } + + public String getSeparatedDefinitionsFolder() { + return SEPARATED_DEFINITIONS_FOLDER; + } + + public String getSeparatedPathsFolder() { + return SEPARATED_PATHS_FOLDER; + } } diff --git a/src/main/java/io/github/robwin/swagger2markup/type/RefType.java b/src/main/java/io/github/robwin/swagger2markup/type/RefType.java index d6e9186c..2aba2155 100644 --- a/src/main/java/io/github/robwin/swagger2markup/type/RefType.java +++ b/src/main/java/io/github/robwin/swagger2markup/type/RefType.java @@ -4,8 +4,11 @@ import io.github.robwin.markup.builder.MarkupDocBuilder; public class RefType extends Type { - public RefType(String name) { + private String document; + + public RefType(String document, String name) { super(name); + this.document = document; } public RefType(Type type) { @@ -14,6 +17,14 @@ public class RefType extends Type { @Override public String displaySchema(MarkupDocBuilder docBuilder) { - return docBuilder.crossReferenceAsString(getUniqueName(), getName()); + return docBuilder.crossReferenceAsString(getDocument(), getUniqueName(), getName()); + } + + public String getDocument() { + return document; + } + + public void setDocument(String document) { + this.document = document; } } diff --git a/src/main/java/io/github/robwin/swagger2markup/utils/ModelUtils.java b/src/main/java/io/github/robwin/swagger2markup/utils/ModelUtils.java index 9e2d5fac..e4d3880e 100644 --- a/src/main/java/io/github/robwin/swagger2markup/utils/ModelUtils.java +++ b/src/main/java/io/github/robwin/swagger2markup/utils/ModelUtils.java @@ -18,10 +18,8 @@ */ package io.github.robwin.swagger2markup.utils; -import io.github.robwin.swagger2markup.type.ArrayType; -import io.github.robwin.swagger2markup.type.ObjectType; -import io.github.robwin.swagger2markup.type.RefType; -import io.github.robwin.swagger2markup.type.Type; +import com.google.common.base.Function; +import io.github.robwin.swagger2markup.type.*; import io.swagger.models.ArrayModel; import io.swagger.models.Model; import io.swagger.models.ModelImpl; @@ -36,15 +34,16 @@ public final class ModelUtils { * @param model the model * @return the type of the model, or otherwise null */ - public static Type getType(Model model) { + public static Type getType(Model model, Function definitionDocumentResolver) { Validate.notNull(model, "model must not be null!"); if (model instanceof ModelImpl) { return new ObjectType(null, model.getProperties()); } else if (model instanceof RefModel) { - return new RefType(((RefModel) model).getSimpleRef()); + String simpleRef = ((RefModel) model).getSimpleRef(); + return new RefType(definitionDocumentResolver.apply(simpleRef), simpleRef); } else if (model instanceof ArrayModel) { ArrayModel arrayModel = ((ArrayModel) model); - return new ArrayType(null, PropertyUtils.getType(arrayModel.getItems())); + return new ArrayType(null, PropertyUtils.getType(arrayModel.getItems(), definitionDocumentResolver)); } return null; } diff --git a/src/main/java/io/github/robwin/swagger2markup/utils/ParameterUtils.java b/src/main/java/io/github/robwin/swagger2markup/utils/ParameterUtils.java index 112c0a77..82a3fffd 100644 --- a/src/main/java/io/github/robwin/swagger2markup/utils/ParameterUtils.java +++ b/src/main/java/io/github/robwin/swagger2markup/utils/ParameterUtils.java @@ -18,6 +18,7 @@ */ package io.github.robwin.swagger2markup.utils; +import com.google.common.base.Function; import io.github.robwin.swagger2markup.type.*; import io.swagger.models.Model; import io.swagger.models.parameters.AbstractSerializableParameter; @@ -40,14 +41,14 @@ public final class ParameterUtils { * @param parameter the parameter * @return the type of the parameter, or otherwise null */ - public static Type getType(Parameter parameter){ + public static Type getType(Parameter parameter, Function definitionDocumentResolver){ Validate.notNull(parameter, "parameter must not be null!"); Type type = null; if(parameter instanceof BodyParameter){ BodyParameter bodyParameter = (BodyParameter)parameter; Model model = bodyParameter.getSchema(); if(model != null){ - type = ModelUtils.getType(model); + type = ModelUtils.getType(model, definitionDocumentResolver); }else{ type = new BasicType("string"); } @@ -63,12 +64,12 @@ public final class ParameterUtils { } if(type.getName().equals("array")){ String collectionFormat = serializableParameter.getCollectionFormat(); - type = new ArrayType(null, PropertyUtils.getType(serializableParameter.getItems()), collectionFormat); + type = new ArrayType(null, PropertyUtils.getType(serializableParameter.getItems(), definitionDocumentResolver), collectionFormat); } } else if(parameter instanceof RefParameter){ - RefParameter refParameter = (RefParameter)parameter; - type = new RefType(refParameter.getSimpleRef()); + String simpleRef = ((RefParameter)parameter).getSimpleRef(); + type = new RefType(definitionDocumentResolver.apply(simpleRef), simpleRef); } return type; } diff --git a/src/main/java/io/github/robwin/swagger2markup/utils/PropertyUtils.java b/src/main/java/io/github/robwin/swagger2markup/utils/PropertyUtils.java index 581e6a0c..e6c0cb88 100644 --- a/src/main/java/io/github/robwin/swagger2markup/utils/PropertyUtils.java +++ b/src/main/java/io/github/robwin/swagger2markup/utils/PropertyUtils.java @@ -18,6 +18,7 @@ */ package io.github.robwin.swagger2markup.utils; +import com.google.common.base.Function; import io.github.robwin.swagger2markup.type.*; import io.swagger.models.properties.*; import io.swagger.models.refs.RefFormat; @@ -37,7 +38,7 @@ public final class PropertyUtils { * @param property the property * @return the type of the property */ - public static Type getType(Property property){ + public static Type getType(Property property, Function definitionDocumentResolver){ Validate.notNull(property, "property must not be null!"); Type type; if(property instanceof RefProperty){ @@ -45,11 +46,11 @@ public final class PropertyUtils { if (refProperty.getRefFormat() == RefFormat.RELATIVE) type = new ObjectType(null, null); // FIXME : Workaround for https://github.com/swagger-api/swagger-parser/issues/177 else - type = new RefType(refProperty.getSimpleRef()); + type = new RefType(definitionDocumentResolver.apply(refProperty.getSimpleRef()), refProperty.getSimpleRef()); }else if(property instanceof ArrayProperty){ ArrayProperty arrayProperty = (ArrayProperty)property; Property items = arrayProperty.getItems(); - type = new ArrayType(null, getType(items)); + type = new ArrayType(null, getType(items, definitionDocumentResolver)); }else if(property instanceof StringProperty){ StringProperty stringProperty = (StringProperty)property; List enums = stringProperty.getEnum(); diff --git a/src/test/java/io/github/robwin/swagger2markup/Swagger2MarkupConverterTest.java b/src/test/java/io/github/robwin/swagger2markup/Swagger2MarkupConverterTest.java index 0168b52a..ff02f79b 100644 --- a/src/test/java/io/github/robwin/swagger2markup/Swagger2MarkupConverterTest.java +++ b/src/test/java/io/github/robwin/swagger2markup/Swagger2MarkupConverterTest.java @@ -259,9 +259,6 @@ public class Swagger2MarkupConverterTest { String[] directories = outputDirectory.list(); assertThat(directories).hasSize(5).containsAll( asList("definitions", "definitions.adoc", "overview.adoc", "paths.adoc", "security.adoc")); - File definitionsDirectory = new File(outputDirectory, "definitions"); - assertThat(new String(Files.readAllBytes(new File(outputDirectory, "definitions.adoc").toPath()))) - .contains(new String(Files.readAllBytes(new File(definitionsDirectory, "user.adoc").toPath()))); } @Test @@ -285,9 +282,6 @@ public class Swagger2MarkupConverterTest { String[] definitions = definitionsDirectory.list(); assertThat(definitions).hasSize(6).containsAll( asList("identified.md", "user.md", "category.md", "pet.md", "tag.md", "order.md")); - - assertThat(new String(Files.readAllBytes(new File(outputDirectory, "definitions.md").toPath()))) - .contains(new String(Files.readAllBytes(new File(definitionsDirectory, "user.md").toPath()))); } @Test @@ -306,13 +300,6 @@ public class Swagger2MarkupConverterTest { String[] directories = outputDirectory.list(); assertThat(directories).hasSize(5).containsAll( asList("definitions", "definitions.md", "overview.md", "paths.md", "security.md")); - verifyMarkdownContainsFieldsInTables( - new File(outputDirectory, "definitions.md"), - ImmutableMap.>builder() - .put("Identified", ImmutableSet.of("id")) - .put("User", ImmutableSet.of("id", "username", "firstName", - "lastName", "email", "password", "phone", "userStatus")) - .build()); File definitionsDirectory = new File(outputDirectory, "definitions"); verifyMarkdownContainsFieldsInTables( new File(definitionsDirectory, "user.md"),