package com.krusoe.openapimerge.merge; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.core.util.Json; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.PathItem; import org.apache.commons.io.FileUtils; import org.openapitools.codegen.validation.Invalid; import org.openapitools.codegen.validation.ValidationRule; import org.openapitools.codegen.validations.oas.OpenApiEvaluator; import org.openapitools.codegen.validations.oas.RuleConfiguration; import org.springframework.shell.command.annotation.Command; import org.springframework.shell.command.annotation.Option; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.stream.Stream; @Command public class MergeCommand { private ObjectMapper objectMapper = new ObjectMapper(); @Command(description = "Merge APIs") public void merge(@Option(required = true, shortNames = {'d'}, longNames = {"dir"}) String dir, @Option(required = false, shortNames = {'o'}, longNames = {"out"}) String out) throws IOException { File dirFile = new File(dir); if (FileUtils.isDirectory(dirFile)) { ApiSpec mergedSpec = new ApiSpec(); for (File processedFile : FileUtils.listFiles(dirFile, new String[]{".json"}, true)) { System.out.println("Processing file: " + processedFile.getAbsolutePath()); ApiSpec currentFileSpec = objectMapper.readerFor(ApiSpec.class).readValue(processedFile); mergedSpec.merge(currentFileSpec); } var outFile = new File(dirFile, "mergedapi.json"); if (outFile.exists()) { outFile.delete(); } System.out.println("Writing merged APIs"); try (OutputStream outStream = FileUtils.newOutputStream(outFile, false)) { objectMapper.writerWithDefaultPrettyPrinter().writeValue(outStream, mergedSpec); } } } @Command(description = "Validate APIs") public void validate(@Option(required = true, shortNames = {'f'}, longNames = {"file"}) String fileToBalidate) throws IOException { File outFile = new File(fileToBalidate); if (outFile.isDirectory()) { System.out.println("Given file is an directory"); } else { validateFile(outFile); } } private void validateFile(File outFile) throws IOException { if (outFile.exists()) { System.out.println("Validating OpenAPI file " + outFile.getAbsolutePath()); OpenApiEvaluator evaluator = new OpenApiEvaluator(new RuleConfiguration()); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); OpenAPI validatedApi = Json.mapper().readValue(outFile, OpenAPI.class); var validationResult = evaluator.validate(validatedApi); Set uniqueOperationIds = new HashSet<>(); Set nonUniqueOperationIds = new HashSet<>(); for (PathItem processedItem : validatedApi.getPaths().sequencedValues()) { Stream.of(processedItem.getDelete(), processedItem.getGet(), processedItem.getPost(), processedItem.getPut(), processedItem.getPut(), processedItem.getPatch(), processedItem.getHead(), processedItem.getOptions(), processedItem.getTrace()) .filter(Objects::nonNull) .forEach( operation -> { if (uniqueOperationIds.contains(operation.getOperationId())) { validationResult.getErrors() .add( new Invalid( ValidationRule.error("Non unique operationId", o -> null), "Non unique operationId", "Path: " + processedItem.get$ref() + " Operation:" + operation.getOperationId())); nonUniqueOperationIds.add(operation.getOperationId()); uniqueOperationIds.add(operation.getOperationId()); } else { uniqueOperationIds.add(operation.getOperationId()); } } ); } if (validationResult.getErrors().isEmpty() && validationResult.getWarnings().isEmpty()) { System.out.println("Validation complete. No validation errors or warning found. File is valid."); } else { System.out.println("Validation errors:"); validationResult.getErrors().forEach(System.out::println); System.out.println("Validation warnings:"); validationResult.getWarnings().forEach(w -> System.out.println("Severity: " + w.getSeverity().name() + "\nRule: " + w.getRule().getDescription() + "\nMessage:\n" + w.getMessage() + "\nDetails:" + w.getDetails())); } } } }