Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9f95b69
Add mount-point support
marekryznar Dec 11, 2025
fa2a6c1
Add module handling for mount point processing
marekryznar Mar 6, 2026
8aed667
Add error handling for invalid module name
marekryznar Mar 6, 2026
ae77dec
Add mounting rpc
marekryznar Mar 9, 2026
979d51a
Fix rpc handling
marekryznar Mar 9, 2026
1749391
Add --mount-point-mappings-json-file to CLI
marekryznar Mar 16, 2026
2fe5e72
Add actions support
marekryznar Mar 16, 2026
1596062
Manual refactorring
marekryznar Mar 17, 2026
d556d1a
AI + manual refactoring
marekryznar Mar 17, 2026
7024124
AI refactoring
marekryznar Mar 17, 2026
b827133
Test adjustments
marekryznar Mar 18, 2026
db311e1
Test adjustments
marekryznar Mar 18, 2026
fb3db12
Bump version
marekryznar Mar 23, 2026
cab7c3e
Bump version
marekryznar Mar 23, 2026
c641b84
concrete type for mountpoint mappings
bartoszm Mar 24, 2026
b6172e3
minor improvements
bartoszm Mar 24, 2026
4e1a1c6
Merge remote-tracking branch 'origin/SCHEMA_MOUNT_SUPPORT' into SCHEM…
bartoszm Mar 24, 2026
6ac648a
Changes after review
marekryznar Mar 24, 2026
feb518d
Fix rpc path of mounted operation
marekryznar Mar 30, 2026
fbc076d
Add validation of payload in mounted RPC in test
marekryznar Mar 30, 2026
e31ac9a
Change after review
marekryznar Mar 30, 2026
0d1a020
Change tag of mounted operations to a source module
marekryznar Mar 31, 2026
ca352d2
Change implementation to generate swagger API paths for mounted modul…
marekryznar Apr 1, 2026
f8b2cfa
Fix swagger file errors, types definitions & API path generation
marekryznar Apr 2, 2026
46e87c7
Add proper handling of mounted module types & adjust test
marekryznar Apr 2, 2026
6a660f2
Add removal of unused definitions
marekryznar Apr 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ Having the definition you are able to build live documentation services, and gen
Our tool supports:

* rpc - which are translated into POST operations
* actions - which are translated into POST operations on instance-specific RESTCONF operation paths, analogously to rpc
* containers and lists - which are represented in RESTCONF data space URI and Swagger modules.
* leafs and leaf lists - that are translated into Swagger models' attributes. Generator handles enums as well.
* leafrefs - which are represented as model attributes with typesUsageTreeBuilder of the referred leafs
* groupings - which, depending on strategy, are either unpacked into models that use these groupings or optimized model inheritance structures
* augmentations - which, depending on strategy, are either unpacked into models that use these groupings or optimized model inheritance structures
* YANG modules documentation - which is added to generated swagger API specification
* mount-point - for which types can be provided during swagger generation


In this project we use YANG parser from [OpenDaylight](https://www.opendaylight.org/) (ODL) yang-tools project. The generated Swagger specification is available as Java object or serialized either to YAML or JSON file.
Expand Down Expand Up @@ -76,6 +78,26 @@ module ... : List of YANG module names to generate
defaults to current directory.
Multiple dirs might be separated by
system path separator (default: )
-mount-point-mappings : Mount-point mappings as a JSON string.
Assigns mount-point labels (from YANG
files) to lists of content types
(can be passed as a specific type or
a whole module) that should be used
when generating Swagger definitions.
Expected format:
'{"mount-label": ["module:grouping", ...]}'
or
'{"mount-label": ["module", ...]}'
Example:
'{"mount-point-name": [
"mounted-module-name:top-level-container",
"mounted-module-name:top-level-container"
]}'
-mount-point-mappings-json-file file : JSON file containing mount-point
mappings in the same format as
-mount-point-mappings. Useful when the
mapping is too large to pass directly
on the command line.
```

For example:
Expand Down
2 changes: 1 addition & 1 deletion cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<artifactId>yangtools</artifactId>
<groupId>com.mrv.yangtools</groupId>
<version>2.1.0</version>
<version>2.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
72 changes: 68 additions & 4 deletions cli/src/main/java/com/mrv/yangtools/codegen/main/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

package com.mrv.yangtools.codegen.main;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mrv.yangtools.codegen.MountPointMappings;
import com.mrv.yangtools.codegen.SwaggerGenerator;
import com.mrv.yangtools.codegen.impl.path.AbstractPathHandlerBuilder;
import com.mrv.yangtools.codegen.impl.path.odl.ODLPathHandlerBuilder;
Expand All @@ -29,12 +31,12 @@

import java.io.*;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -87,6 +89,12 @@ public class Main {
@Option(name = "-basepath", usage="")
public String basePath = "localhost:1234";

@Option(name = "-mount-point-mappings", usage = "Mount point mappings as JSON string, e.g. '{\"mount-point-name\": [\"mounted-module-name\", \"mounted-module-name\"]}'", metaVar = "json")
public String mountPointMappings = "";

@Option(name = "-mount-point-mappings-json-file", usage = "JSON file containing mount point mappings, same format as -mount-point-mappings", metaVar = "file")
public String mountPointMappingsJson = "";

public enum ElementType {
DATA, RPC, DATA_AND_RPC
}
Expand Down Expand Up @@ -119,6 +127,10 @@ void init() throws FileNotFoundException {
if (output != null && !output.trim().isEmpty()) {
out = new FileOutputStream(output);
}

if (isOptionSet(mountPointMappingsJson) && isOptionSet(mountPointMappings)) {
throw new IllegalArgumentException("mount-point-mappings & mount-point-mappings-json cannot be set at the same time");
}
}

void generate() throws IOException, ReactorException {
Expand Down Expand Up @@ -160,7 +172,7 @@ void generate() throws IOException, ReactorException {
.pathHandler(pathHandler)
.elements(map(elementType));


setYangmntMappings(generator);

if(AuthenticationMechanism.BASIC.equals(authenticationMechanism)) {
generator.appendPostProcessor(new AddSecurityDefinitions().withSecurityDefinition("api_sec", new BasicAuthDefinition()));
Expand All @@ -184,6 +196,58 @@ void generate() throws IOException, ReactorException {
generator.generate(new OutputStreamWriter(out));
}

private void setYangmntMappings(SwaggerGenerator generator) {

if (isOptionSet(mountPointMappingsJson)) {
MountPointMappings mapping = parseMountPointMappingJson();
log.debug("Parsed mount-point-mappings from file '{}': {}", mountPointMappingsJson, mapping);
if (!mapping.isEmpty()) {
generator.yangmntMappings(mapping);
}
return;
}

if (isOptionSet(mountPointMappings)) {
log.debug("Raw mount-point-mappings arg: {}", mountPointMappings);
MountPointMappings mapping = parseMountPointMappings(mountPointMappings);
log.debug("Parsed mount-point-mappings: {}", mapping);
if (mapping != null && !mapping.isEmpty()) {
generator.yangmntMappings(mapping);
}
}
}

private MountPointMappings parseMountPointMappingJson() {
if (!isOptionSet(mountPointMappingsJson)) {
throw new IllegalArgumentException("mount-point-mappings-json-file is empty");
}

Path jsonPath = FileSystems.getDefault().getPath(mountPointMappingsJson);
try {
String raw = new String(Files.readAllBytes(jsonPath), StandardCharsets.UTF_8);
return parseMountPointMappings(raw);
} catch (IOException e) {
log.error("Cannot read mount-point mappings from file: {}", mountPointMappingsJson, e);
throw new IllegalArgumentException("Cannot read mount-point mappings JSON file: " + mountPointMappingsJson, e);
}
}

private MountPointMappings parseMountPointMappings(String raw) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue(raw, MountPointMappings.class);
} catch (IllegalArgumentException iae) {
throw iae;
} catch (Exception e) {
log.error("Invalid mount-point-mappings format: {}", raw, e);
throw new IllegalArgumentException("Invalid mount-point-mappings format", e);
}
}

private boolean isOptionSet(String optionName) {
return optionName != null && !optionName.trim().isEmpty();
}

private void validate(String basePath) {
URI.create(basePath);
}
Expand Down
2 changes: 1 addition & 1 deletion common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<parent>
<artifactId>yangtools</artifactId>
<groupId>com.mrv.yangtools</groupId>
<version>2.1.0</version>
<version>2.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
2 changes: 1 addition & 1 deletion examples/build-standalone/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<artifactId>examples</artifactId>
<groupId>com.mrv.yangtools</groupId>
<version>2.1.0</version>
<version>2.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
2 changes: 1 addition & 1 deletion examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<parent>
<artifactId>yangtools</artifactId>
<groupId>com.mrv.yangtools</groupId>
<version>2.1.0</version>
<version>2.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<groupId>com.mrv.yangtools</groupId>
<artifactId>yangtools</artifactId>
<version>2.1.0</version>
<version>2.2.0</version>
<modules>
<module>swagger-generator</module>
<module>common</module>
Expand Down
2 changes: 1 addition & 1 deletion swagger-codegen-jaxrs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<artifactId>yangtools</artifactId>
<groupId>com.mrv.yangtools</groupId>
<version>2.1.0</version>
<version>2.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
2 changes: 1 addition & 1 deletion swagger-generator/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<artifactId>yangtools</artifactId>
<groupId>com.mrv.yangtools</groupId>
<version>2.1.0</version>
<version>2.2.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import com.mrv.yangtools.codegen.impl.ModuleUtils;
import com.mrv.yangtools.codegen.impl.OptimizingDataObjectBuilder;
import com.mrv.yangtools.codegen.impl.UnpackingDataObjectsBuilder;
import com.mrv.yangtools.codegen.impl.path.AbstractPathHandlerBuilder;
import com.mrv.yangtools.codegen.impl.postprocessor.MountPointPostProcessor;
import com.mrv.yangtools.codegen.impl.postprocessor.ReplaceEmptyWithParent;
import io.swagger.models.Info;
import io.swagger.models.Swagger;
Expand Down Expand Up @@ -57,6 +59,7 @@ public class IoCSwaggerGenerator {
private final Set<String> moduleNames;
private final ModuleUtils moduleUtils;
private Consumer<Swagger> postprocessor;
private Consumer<Swagger> mountPointPostProcessor;
private DataObjectBuilder dataObjectsBuilder;
private ObjectMapper mapper;
private int maxDepth = Integer.MAX_VALUE;
Expand Down Expand Up @@ -114,6 +117,15 @@ public IoCSwaggerGenerator(@Assisted EffectiveModelContext ctx, @Assisted Set<Mo
//assign default strategy
strategy(Strategy.optimizing);

try {
AbstractPathHandlerBuilder defaultBuilder = new com.mrv.yangtools.codegen.impl.path.rfc8040.PathHandlerBuilder();
defaultBuilder.useModuleName();
this.pathHandlerBuilder = defaultBuilder;
} catch (Throwable t) {
// fallback: leave null and allow caller to set pathHandler explicitly
this.pathHandlerBuilder = null;
}

//no exposed swagger API
target.info(new Info());

Expand Down Expand Up @@ -255,7 +267,22 @@ public IoCSwaggerGenerator produces(String produces) {
public IoCSwaggerGenerator maxDepth(int maxDepth) {
this.maxDepth = maxDepth;
return this;
}
}

/**
* Provide mappings for mount-point extension: label -> targets.
* <p>
* Safe to call more than once — replaces any previously registered
* {@link MountPointPostProcessor} instead of appending a second one.
*/
public IoCSwaggerGenerator yangmntMappings(MountPointMappings mappings) {
if(mappings != null && !mappings.isEmpty()) {
this.mountPointPostProcessor = new MountPointPostProcessor(mappings, ctx, moduleUtils, dataObjectsBuilder);
} else {
this.mountPointPostProcessor = null;
}
return this;
}

/**
* Run Swagger generation for configured modules. Write result to target. The file format
Expand Down Expand Up @@ -295,6 +322,15 @@ public Swagger generate() {

});
//initialize plugable path handler
if(pathHandlerBuilder == null) {
try {
AbstractPathHandlerBuilder defaultBuilder = new com.mrv.yangtools.codegen.impl.path.rfc8040.PathHandlerBuilder();
defaultBuilder.useModuleName();
pathHandlerBuilder = defaultBuilder;
} catch (Throwable t) {
throw new IllegalStateException("No PathHandlerBuilder configured and default builder could not be instantiated", t);
}
}
pathHandlerBuilder.configure(ctx, target, dataObjectsBuilder);

modules.forEach(m -> new ModuleGenerator(m).generate());
Expand All @@ -313,7 +349,8 @@ public Swagger generate() {

/**
* Replace empty definitions with their parents.
* Sort models (ref models first)
* Sort models (ref models first).
* Run mount-point post-processor if configured.
* @param target to work on
*/
protected void postProcessSwagger(Swagger target) {
Expand All @@ -322,6 +359,9 @@ protected void postProcessSwagger(Swagger target) {
return;
}
postprocessor.accept(target);
if(mountPointPostProcessor != null) {
mountPointPostProcessor.accept(target);
}
}

private class ModuleGenerator {
Expand Down Expand Up @@ -361,6 +401,20 @@ private void generate(RpcDefinition rpc) {
pathCtx = pathCtx.drop();
}

private void generateActions(ActionNodeContainer node) {
if(!toGenerate.contains(Elements.RPC)) return;

node.getActions().forEach(action -> {
pathCtx = new PathSegment(pathCtx)
.withName(action.getQName().getLocalName())
.withModule(moduleUtils.toModuleName(action));

handler.path(action, pathCtx);

pathCtx = pathCtx.drop();
});
}

private void generate(DataSchemaNode node, final int depth) {
if(depth == 0) {
log.debug("Maxmium depth level reached, skipping {} and it's childs", node.getPath());
Expand All @@ -382,6 +436,7 @@ private void generate(DataSchemaNode node, final int depth) {
.asReadOnly(!cN.isConfiguration());

handler.path(cN, pathCtx);
generateActions(cN);
cN.getChildNodes().forEach(n -> generate(n, depth-1));
dataObjectsBuilder.addModel(cN);

Expand All @@ -397,6 +452,7 @@ private void generate(DataSchemaNode node, final int depth) {
.withListNode(lN);

handler.path(lN, pathCtx);
generateActions(lN);
lN.getChildNodes().forEach(n -> generate(n, depth-1));
dataObjectsBuilder.addModel(lN);

Expand Down
Loading
Loading