diff --git a/cli/src/main/java/com/mrv/yangtools/codegen/main/Main.java b/cli/src/main/java/com/mrv/yangtools/codegen/main/Main.java index ec419f4..c60d823 100644 --- a/cli/src/main/java/com/mrv/yangtools/codegen/main/Main.java +++ b/cli/src/main/java/com/mrv/yangtools/codegen/main/Main.java @@ -107,7 +107,7 @@ public static void main(String[] args) { } void init() throws FileNotFoundException { - if (output != null && output.trim().length() > 0) { + if (output != null && !output.trim().isEmpty()) { out = new FileOutputStream(output); } } @@ -165,7 +165,11 @@ void generate() throws IOException, ReactorException { generator.appendPostProcessor(new SingleParentInheritenceModel()); } - generator.appendPostProcessor(new Rfc4080PayloadWrapper()); + if(!odlPathFormat) { + //TODO check if something similar is needed for ODL paths as well + generator.appendPostProcessor(new Rfc4080PayloadWrapper()); + } + generator.appendPostProcessor(new RemoveUnusedDefinitions()); generator.generate(new OutputStreamWriter(out)); diff --git a/cli/src/test/java/com/mrv/yangtools/codegen/main/Issue57.java b/cli/src/test/java/com/mrv/yangtools/codegen/main/Issue57.java index 807860e..9c46732 100644 --- a/cli/src/test/java/com/mrv/yangtools/codegen/main/Issue57.java +++ b/cli/src/test/java/com/mrv/yangtools/codegen/main/Issue57.java @@ -1,22 +1,17 @@ package com.mrv.yangtools.codegen.main; import io.swagger.models.Swagger; -import io.swagger.parser.SwaggerParser; -import org.junit.Assert; -import org.junit.Test; -import org.kohsuke.args4j.CmdLineParser; - -import java.io.ByteArrayOutputStream; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.List; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.junit.Assert; +import org.junit.Test; -public class Issue57 { +public class Issue57 { private static String path; static { @@ -35,7 +30,7 @@ public void testRegular() { path ).collect(Collectors.toList()); - Swagger swagger = runParser(args); + Swagger swagger = Utils.runParser(args); assertContainsOnly(swagger, s -> s.endsWith("Input"), "objects.createobject.Input","objects.updateobject.Input"); } @@ -49,7 +44,7 @@ public void testOptimized() { path ).collect(Collectors.toList()); - Swagger swagger = runParser(args); + Swagger swagger = Utils.runParser(args); assertContainsOnly(swagger, s -> s.endsWith("Input"), "objects.createobject.Input"); } @@ -62,21 +57,4 @@ private void assertContainsOnly(Swagger swagger, Predicate filterDefs, S .collect(Collectors.toSet()); Assert.assertEquals(expected, result); } - - private Swagger runParser(List args) { - Main main = new Main(); - CmdLineParser parser = new CmdLineParser(main); - try { - parser.parseArgument(args); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - main.out = baos; - main.init(); - main.generate(); - - return new SwaggerParser().parse(new String(baos.toByteArray(), StandardCharsets.UTF_8)); - } catch (Exception e) { - throw new RuntimeException(e); - } - - } } diff --git a/cli/src/test/java/com/mrv/yangtools/codegen/main/Utils.java b/cli/src/test/java/com/mrv/yangtools/codegen/main/Utils.java new file mode 100644 index 0000000..7f19d65 --- /dev/null +++ b/cli/src/test/java/com/mrv/yangtools/codegen/main/Utils.java @@ -0,0 +1,27 @@ +package com.mrv.yangtools.codegen.main; + +import io.swagger.models.Swagger; +import io.swagger.parser.SwaggerParser; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import org.kohsuke.args4j.CmdLineParser; + +public interface Utils { + static Swagger runParser(List args) { + Main main = new Main(); + CmdLineParser parser = new CmdLineParser(main); + try { + parser.parseArgument(args); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + main.out = baos; + main.init(); + main.generate(); + + return new SwaggerParser().parse(baos.toString(StandardCharsets.UTF_8)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } +} diff --git a/swagger-generator/pom.xml b/swagger-generator/pom.xml index 2430d2d..6c5263d 100644 --- a/swagger-generator/pom.xml +++ b/swagger-generator/pom.xml @@ -72,6 +72,12 @@ org.mockito mockito-core + + org.mozilla + rhino + 1.7.7.2 + compile + diff --git a/swagger-generator/src/main/java/com/mrv/yangtools/codegen/SwaggerGenerator.java b/swagger-generator/src/main/java/com/mrv/yangtools/codegen/SwaggerGenerator.java index ab88403..4399356 100644 --- a/swagger-generator/src/main/java/com/mrv/yangtools/codegen/SwaggerGenerator.java +++ b/swagger-generator/src/main/java/com/mrv/yangtools/codegen/SwaggerGenerator.java @@ -312,8 +312,8 @@ public Swagger generate() { modules.forEach(m -> new ModuleGenerator(m).generate()); // update info with module names and descriptions - String modules = mNames.stream().collect(Collectors.joining(",")); - String descriptions = mDescs.stream().collect(Collectors.joining(",")); + String modules = String.join(",", mNames); + String descriptions = String.join(",", mDescs); if(descriptions.isEmpty()) { descriptions = modules + " API generated from yang definitions"; } @@ -379,7 +379,7 @@ private void generate(RpcDefinition rpc) { private void generate(DataSchemaNode node, final int depth) { if(depth == 0) { - log.debug("Maxmium depth level reached, skipping {} and it's childs", node.getPath()); + log.debug("Maximum depth level reached, skipping {} and it's childs", node.getPath()); return; } diff --git a/swagger-generator/src/main/java/com/mrv/yangtools/codegen/impl/AbstractDataObjectBuilder.java b/swagger-generator/src/main/java/com/mrv/yangtools/codegen/impl/AbstractDataObjectBuilder.java index 47923be..c267e29 100644 --- a/swagger-generator/src/main/java/com/mrv/yangtools/codegen/impl/AbstractDataObjectBuilder.java +++ b/swagger-generator/src/main/java/com/mrv/yangtools/codegen/impl/AbstractDataObjectBuilder.java @@ -75,6 +75,10 @@ public AbstractDataObjectBuilder(EffectiveModelContext ctx, Swagger swagger, Typ this.moduleUtils = new ModuleUtils(ctx); this.generatedEnums = new HashMap<>(); this.orgNames = new HashMap<>(); + + if(swagger.getDefinitions() == null) { + swagger.setDefinitions(new LinkedHashMap<>()); + } } /** diff --git a/swagger-generator/src/main/java/com/mrv/yangtools/codegen/impl/postprocessor/ReplaceDefinitionsProcessor.java b/swagger-generator/src/main/java/com/mrv/yangtools/codegen/impl/postprocessor/ReplaceDefinitionsProcessor.java index 03adf0c..c302621 100644 --- a/swagger-generator/src/main/java/com/mrv/yangtools/codegen/impl/postprocessor/ReplaceDefinitionsProcessor.java +++ b/swagger-generator/src/main/java/com/mrv/yangtools/codegen/impl/postprocessor/ReplaceDefinitionsProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Amartus. All rights reserved. + * Copyright (c) 2024 Amartus. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html @@ -15,6 +15,9 @@ import io.swagger.models.properties.ArrayProperty; import io.swagger.models.properties.Property; import io.swagger.models.properties.RefProperty; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +36,7 @@ public abstract class ReplaceDefinitionsProcessor implements Consumer { private final Logger log = LoggerFactory.getLogger(ReplaceDefinitionsProcessor.class); @Override public void accept(Swagger target) { - Map replacements = prepareForReplacement(target); + Map replacements = optimize(prepareForReplacement(target)); log.debug("{} replacement found for definitions", replacements.size()); log.trace("replacing paths"); @@ -47,51 +50,72 @@ public void accept(Swagger target) { }); } + + + private Map optimize(Map replacements) { + Function last = s -> { + String replacement = replacements.get(s); + while (replacement != null) { + s = replacement; + replacement = replacements.get(s); + } + return s; + + }; + + return replacements.entrySet().stream().map(e -> Map.entry(e.getKey(), last.apply(e.getValue()))) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + } + protected abstract Map prepareForReplacement(Swagger swagger); - private void fixModel(String name, Model m, Map replacements) { - ModelImpl fixProperties = null; + private Optional fixModel(String name, Model m, Map replacements) { - if(m instanceof RefModel) { - fixRefModel((RefModel) m, replacements); - return; + if(m != null && replacements.containsKey(name)) { + return Optional.empty(); } - if(m instanceof ModelImpl) { - fixProperties = (ModelImpl) m; + if(m instanceof RefModel) { + return Optional.ofNullable(fixRefModel((RefModel) m, replacements)); } if(m instanceof ComposedModel) { ComposedModel cm = (ComposedModel) m; fixComposedModel(cm, replacements); - fixProperties = cm.getAllOf().stream() - .filter(c -> c instanceof ModelImpl).map(c -> (ModelImpl)c) - .findFirst().orElse(null); + Stream> propAll = cm.getAllOf().stream() + .flatMap(this::properties); + fixProperties(propAll, replacements); } + fixProperties(properties(m), replacements); + return Optional.empty(); + } - if(fixProperties == null) return; - if(fixProperties.getProperties() == null) { - if(fixProperties.getEnum() == null) { - log.warn("Empty model in {}", name); - } - return; - } - fixProperties.getProperties().forEach((key, value) -> { + private Stream> properties(Model m) { + if(m == null) return Stream.empty(); + return Optional.ofNullable(m.getProperties()).stream() + .flatMap(it -> it.entrySet().stream()); + } + + private void fixProperties(Stream> properties, Map replacements) { + properties.forEach(e -> { + var key = e.getKey(); + var value = e.getValue(); if (value instanceof RefProperty) { if (fixProperty((RefProperty) value, replacements)) { - log.debug("fixing property {} of {}", key, name); + log.debug("fixing property {}", key); } } else if (value instanceof ArrayProperty) { Property items = ((ArrayProperty) value).getItems(); if (items instanceof RefProperty) { if (fixProperty((RefProperty) items, replacements)) { - log.debug("fixing property {} of {}", key, name); + log.debug("fixing property {}", key); } } } }); - } + + private boolean fixProperty(RefProperty p, Map replacements) { if(replacements.containsKey(p.getSimpleRef())) { p.set$ref("#/definitions/" + replacements.get(p.getSimpleRef())); @@ -113,10 +137,12 @@ private void fixComposedModel(ComposedModel m, Map replacements) }); } - private void fixRefModel(RefModel model, Map replacements) { + private Model fixRefModel(RefModel model, Map replacements) { if(replacements.containsKey(model.getSimpleRef())) { model.set$ref("#/definitions/" + replacements.get(model.getSimpleRef())); + return model; } + return null; } private void fixOperation(Operation operation, Map replacements) { @@ -138,13 +164,35 @@ private void fixParameter(Parameter p, Map replacements) { if(!(p instanceof BodyParameter)) return; BodyParameter bp = (BodyParameter) p; - fixModel(null, bp.getSchema(), replacements); + fixModel(null, bp.getSchema(), replacements) + .ifPresent(m -> { + bp.setSchema(m); + var nType = toTypeName(m.getReference()); + var rep = replacements.entrySet().stream() + .filter(e -> e.getValue().equals(nType)) + .findFirst(); + rep.ifPresent(e -> { + var nDesc = bp.getDescription().replace(e.getKey(), e.getValue()); + bp.setDescription(nDesc); + }); + + }); } private void fixResponse(Response r, Map replacements) { Model model = r.getResponseSchema(); if(model != null) { - fixModel(null, model, replacements); + fixModel(null, model, replacements) + .ifPresent(m -> { + r.setResponseSchema(m); + r.setDescription(toTypeName(m.getReference())); + }); } } + + private static String toTypeName(String ref) { + if(ref == null) return null; + var seg = ref.split("/"); + return seg[seg.length - 1]; + } } diff --git a/swagger-generator/src/main/java/com/mrv/yangtools/codegen/impl/postprocessor/SingleParentInheritenceModel.java b/swagger-generator/src/main/java/com/mrv/yangtools/codegen/impl/postprocessor/SingleParentInheritenceModel.java index 3ae85b7..672f63a 100644 --- a/swagger-generator/src/main/java/com/mrv/yangtools/codegen/impl/postprocessor/SingleParentInheritenceModel.java +++ b/swagger-generator/src/main/java/com/mrv/yangtools/codegen/impl/postprocessor/SingleParentInheritenceModel.java @@ -68,7 +68,7 @@ public void accept(Swagger swagger) { } - private class Worker { + private static class Worker { private final Map hierarchy; private final Swagger swagger; private Set toUnpack; @@ -102,7 +102,7 @@ private TypeNode findParent(Set typesToUnpack) { } Stream getAllInHierarchy(TypeNode node) { - if(node.getReferencing().size() > 0) return Stream.concat(Stream.of(node), sorted(node.getReferencing()).flatMap(this::getAllInHierarchy)); + if(!node.getReferencing().isEmpty()) return Stream.concat(Stream.of(node), sorted(node.getReferencing()).flatMap(this::getAllInHierarchy)); return Stream.of(node); } diff --git a/swagger-generator/src/test/java/com/mrv/yangtools/codegen/impl/postprocessor/AbstractWithSwagger.java b/swagger-generator/src/test/java/com/mrv/yangtools/codegen/impl/postprocessor/AbstractWithSwagger.java index 25136e1..f7aee50 100644 --- a/swagger-generator/src/test/java/com/mrv/yangtools/codegen/impl/postprocessor/AbstractWithSwagger.java +++ b/swagger-generator/src/test/java/com/mrv/yangtools/codegen/impl/postprocessor/AbstractWithSwagger.java @@ -74,12 +74,15 @@ protected Model m(Set parents, Map properites) { if(properites.isEmpty() && parents.isEmpty()) return new ModelImpl(); + var parRef = parents.stream().map(p -> new RefModel(DEF_PREFIX + p)) + .collect(Collectors.toList()); + + ComposedModel model = new ComposedModel(); - model.setAllOf( - parents.stream() - .map(p -> new RefModel(DEF_PREFIX + p)) - .collect(Collectors.toList()) - ); + + model.setParent(parRef.get(0)); + model.setInterfaces(parRef.subList(1, parRef.size())); + if(!properites.isEmpty()) { final ModelImpl m = new ModelImpl(); diff --git a/swagger-generator/src/test/java/com/mrv/yangtools/codegen/impl/postprocessor/ReplaceEmptyWithParentTest.java b/swagger-generator/src/test/java/com/mrv/yangtools/codegen/impl/postprocessor/ReplaceEmptyWithParentTest.java new file mode 100644 index 0000000..9375007 --- /dev/null +++ b/swagger-generator/src/test/java/com/mrv/yangtools/codegen/impl/postprocessor/ReplaceEmptyWithParentTest.java @@ -0,0 +1,30 @@ +package com.mrv.yangtools.codegen.impl.postprocessor; + +import static org.junit.Assert.assertTrue; + +import com.mrv.yangtools.test.utils.SwaggerHelper; +import io.swagger.models.Swagger; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.Test; + +public class ReplaceEmptyWithParentTest extends AbstractWithSwagger { + + @Test + public void withEmptyParent() { + new ReplaceEmptyWithParent().accept(swagger); + + noDanglingReferences(swagger); + } + + private void noDanglingReferences(Swagger swagger) { + var path = new SwaggerHelper(swagger).getReferencesFromPaths(); + var defs = new SwaggerHelper(swagger).getReferencesFromDefinitions(); + var all = Stream.concat(path, defs).collect(Collectors.toSet()); + + var definitions = swagger.getDefinitions().keySet(); + var remaining = all.stream().filter(it -> !definitions.contains(it)).collect(Collectors.toSet()); + assertTrue("Dangling references: " + remaining, remaining.isEmpty()); + } + +} \ No newline at end of file diff --git a/swagger-generator/src/test/java/com/mrv/yangtools/test/utils/SwaggerHelper.java b/swagger-generator/src/test/java/com/mrv/yangtools/test/utils/SwaggerHelper.java new file mode 100644 index 0000000..d45f80e --- /dev/null +++ b/swagger-generator/src/test/java/com/mrv/yangtools/test/utils/SwaggerHelper.java @@ -0,0 +1,97 @@ +package com.mrv.yangtools.test.utils; + +import io.swagger.models.ArrayModel; +import io.swagger.models.ComposedModel; +import io.swagger.models.Model; +import io.swagger.models.Path; +import io.swagger.models.RefModel; +import io.swagger.models.Swagger; +import io.swagger.models.parameters.BodyParameter; +import io.swagger.models.properties.ArrayProperty; +import io.swagger.models.properties.Property; +import io.swagger.models.properties.RefProperty; +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Stream; + +public class SwaggerHelper { + private final Swagger swagger; + + public SwaggerHelper(Swagger swagger) { + this.swagger = swagger; + } + + public Swagger getSwagger() { + return swagger; + } + + public Stream getReferencesFromDefinitions() { + return fromDefinition(swagger.getDefinitions().values()); + } + + public Stream getReferencesFromPaths() { + return swagger.getPaths().values().stream() + .flatMap(this::fromPath); + } + + public Stream fromPath(Path path) { + return path.getOperations().stream() + .flatMap(this::fromOperation); + } + public Stream fromOperation(io.swagger.models.Operation operation) { + var body = operation.getParameters().stream() + .filter(it -> it instanceof BodyParameter) + .map(it -> (BodyParameter) it) + .flatMap(it -> fromDefinition(it.getSchema())); + var responses = operation.getResponses().values().stream() + .flatMap(it -> fromDefinition(it.getResponseSchema())); + return Stream.concat(body, responses); + } + + + + private Optional fromDefinition(Property model) { + if(model instanceof RefProperty) { + return Optional.of(((RefProperty) model).getSimpleRef()); + } + + if(model instanceof ArrayProperty) { + return fromDefinition(((ArrayProperty) model).getItems()); + } + + return Optional.empty(); + } + + private Stream fromDefinition(Collection models) { + if(models == null) { + return Stream.empty(); + } + return models.stream().flatMap(this::fromDefinition); + } + + private Stream fromDefinition(Model model) { + if(model == null) { + return Stream.empty(); + } + + if(model instanceof RefModel) { + return Stream.of(((RefModel) model).getSimpleRef()); + } + + if(model instanceof ArrayModel) { + return fromDefinition(((ArrayModel) model).getItems()).stream(); + } + + var props = Optional.ofNullable(model.getProperties()).stream() + .flatMap(it -> it.values().stream()) + .flatMap(it -> fromDefinition(it).stream()); + var composed = Stream.empty(); + if(model instanceof ComposedModel) { + composed = fromDefinition(((ComposedModel) model).getAllOf()); + } + + return Stream.concat(props, composed); + + } + +}