Skip to content

Commit

Permalink
feat: Add support for message type generation from Json schemas (#77)
Browse files Browse the repository at this point in the history
* Add support for message type generation from Json schemas

* Fix typo

* Minor cleanup

* Remove FIXME as it is documented
  • Loading branch information
jimmarino authored Dec 4, 2024
1 parent 1f99bb5 commit 181ae8f
Show file tree
Hide file tree
Showing 21 changed files with 2,085 additions and 0 deletions.
10 changes: 10 additions & 0 deletions artifacts/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import org.eclipse.dsp.generation.SchemaTableGeneratorPlugin
import org.eclipse.dsp.generation.SchemaTableGeneratorPluginExtension

/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
Expand All @@ -17,10 +20,17 @@ plugins {
checkstyle
}

apply<SchemaTableGeneratorPlugin>();

repositories {
mavenCentral()
}

configure<SchemaTableGeneratorPluginExtension> {
schemaPrefix = "https://w3id.org/dspace/2024/1/"
schemaFileSuffix = "-schema.json"
}

dependencies {
implementation("com.networknt:json-schema-validator:1.5.2") {
exclude("com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml")
Expand Down
91 changes: 91 additions & 0 deletions artifacts/buildSrc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Introduction

This directory contains the Schema Table Generator plugin. The plugin generates readable type information in HTML for Json
schema definitions found in the project source set.

For example:

```html

<table class="message-table">
<tr>
<td class="message-class" colspan="3">MessageOffer</td>
</tr>
<tr>
<td class="message-properties-heading" colspan="3">Required properties</td>
</tr>
<tr>
<td class="code">@id</td>
<td>string</td>
<td></td>
</tr>
<tr>
<td class="code">@type</td>
<td>string</td>
<td>Value must be <span class="code">Offer</span></td>
</tr>
<tr>
<td class="message-properties-heading" colspan="3">Optional properties</td>
</tr>
<tr>
<td class="code">obligation</td>
<td>array</td>
<td></td>
</tr>
<tr>
<td class="code">permission</td>
<td>array</td>
<td></td>
</tr>
<tr>
<td class="code">profile</td>
<td>any</td>
<td></td>
</tr>
</table>
```

For each type, a generated HTML table is created and output to `<build dir>/generated/tables`. File names are the
typename in lowercase with an `.html` suffix. These files may then be imported into the ReSpec-based documentation using
the `aside` element in the appropriate Markdown file:

```html

<aside data-include="generated/tables/messageoffer.html"></aside>
```

## Implementation notes

The Json Schema Object Model parser does not yet support all Json Schema features such `anyOf` or `oneOf`.

# Build Setup

The plugin can be applied and configured as follows:

```kotlin
apply<SchemaTableGeneratorPlugin>();

configure<SchemaTableGeneratorPluginExtension> {
schemaPrefix = "https://w3id.org/dspace/2024/1/"
schemaFileSuffix = "-schema.json"
}
```

The `schemPrefix` property is used to specify the base URL for resolving schema references. This base URL will be mapped
relative to where the schema files reside on the local filesystem. The `schemaFileSuffix` propery is used to filter
schema files to include.

# Running

The generation process can be run by specifying the `generateTablesFromSchemas` task:

```
./gradlew generateTablesFromSchemas
```

To debug the generation process, use:

```
./gradlew -Dorg.gradle.debug=true --no-daemon generateTablesFromSchemas
```

37 changes: 37 additions & 0 deletions artifacts/buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Metaform Systems, Inc. - initial API and implementation
*
*/

plugins {
`java-library`
checkstyle
}

repositories {
mavenCentral()
}

dependencies {
implementation("com.networknt:json-schema-validator:1.5.2") {
exclude("com.fasterxml.jackson.dataformat", "jackson-dataformat-yaml")
}
testImplementation("org.assertj:assertj-core:3.26.3")
}

testing {
suites {
val test by getting(JvmTestSuite::class) {
useJUnitJupiter("5.8.1")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.dsp.generation;


import org.eclipse.dsp.generation.jsom.JsomParser;
import org.eclipse.dsp.generation.transformer.HtmlTableTransformer;
import org.eclipse.dsp.generation.transformer.SchemaTypeTransformer;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.tasks.SourceSet;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import static java.util.Objects.requireNonNull;

/**
* Generates a table of schema properties to be included in the specification text.
*/
public class SchemaTableGeneratorPlugin implements Plugin<Project> {
private static final String TASK_NAME = "generateTablesFromSchemas";
private static final String CONFIG_NAME = "schemaTableGenerator";
private static final String GENERATED = "generated";
private static final String TABLES = "tables";

private final SchemaTypeTransformer<String> htmlTransformer = new HtmlTableTransformer();

@Override
public void apply(@NotNull Project project) {
var extension = project.getExtensions().create(CONFIG_NAME, SchemaTableGeneratorPluginExtension.class);

project.task(TASK_NAME).doLast(task -> {
var tablesDir = task.getProject().getLayout().getBuildDirectory().dir(GENERATED).get().dir(TABLES).getAsFile();
//noinspection ResultOfMethodCallIgnored
tablesDir.mkdirs();
var sourceSet = requireNonNull(project.getExtensions()
.findByType(JavaPluginExtension.class)).getSourceSets().getByName("main");

var prefix = extension.getSchemaPrefix();
String resolvePath = getResolutionPath(sourceSet);

// parse the schema object model
var parser = new JsomParser(prefix, resolvePath);
var stream = sourceSet.getResources().getFiles().stream()
.filter(f -> f.getName().endsWith(extension.getSchemaFileSuffix()));
var schemaModel = parser.parseFiles(stream);

schemaModel.getSchemaTypes().stream()
.filter(type -> !type.isRootDefinition() && !type.isJsonBaseType()) // do not process built-in Json types and root schema types
.forEach(type -> {
var content = htmlTransformer.transform(type);
var destination = new File(tablesDir, type.getName().toLowerCase() + ".html");
try (var writer = new FileWriter(destination)) {
writer.write(content);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
task.getLogger().info("Completed generation");
});

}

private String getResolutionPath(SourceSet sourceSet) {
var files = sourceSet.getResources().getSourceDirectories().getFiles();
if (files.isEmpty()) {
throw new IllegalStateException("No schema resource directories found");
}
var path = files.iterator().next();
return path.getAbsolutePath().endsWith("/") ? path.getAbsolutePath() : path.getAbsolutePath() + "/";
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.dsp.generation;

/**
* Defines the plugin configuration.
*/
public class SchemaTableGeneratorPluginExtension {
private String schemaPrefix;
private String schemaFileSuffix = "-schema.json";

public SchemaTableGeneratorPluginExtension() {
}

public String getSchemaPrefix() {
return schemaPrefix;
}

public void setSchemaPrefix(String schemaPrefix) {
this.schemaPrefix = schemaPrefix;
}

public String getSchemaFileSuffix() {
return schemaFileSuffix;
}

public void setSchemaFileSuffix(String schemaFileSuffix) {
this.schemaFileSuffix = schemaFileSuffix;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2024 Metaform Systems, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Metaform Systems, Inc. - initial API and implementation
*
*/

package org.eclipse.dsp.generation.jsom;

import org.jetbrains.annotations.NotNull;

import java.util.Set;
import java.util.TreeSet;

/**
* Models an element such as {@code contains} object or an {@code items} object.
*/
public class ElementDefinition implements Comparable<ElementDefinition> {

public enum Type {
REFERENCE, CONSTANT
}

private final Type type;
private final String value;
private final Set<SchemaType> resolvedTypes = new TreeSet<>();

public ElementDefinition(Type type, String value) {
this.type = type;
this.value = value;
}

public Type getType() {
return type;
}

public String getValue() {
return value;
}

public Set<SchemaType> getResolvedTypes() {
return resolvedTypes;
}

public void resolvedType(SchemaType resolvedType) {
this.resolvedTypes.add(resolvedType);
}

@Override
public int compareTo(@NotNull ElementDefinition o) {
return value.compareTo(o.value);
}

}
Loading

0 comments on commit 181ae8f

Please sign in to comment.