Skip to content

Commit

Permalink
Add tutorial project for smithy gradle plugins (#80)
Browse files Browse the repository at this point in the history
* Add tutorial project for the smithy gradle plugins

---------

Co-authored-by: Jordon Phillips <[email protected]>
  • Loading branch information
hpmellema and JordonPhillips authored Mar 20, 2024
1 parent c073aa0 commit 2988d7e
Show file tree
Hide file tree
Showing 15 changed files with 337 additions and 0 deletions.
25 changes: 25 additions & 0 deletions gradle-plugin-examples/integ/tutorial/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

plugins {
id("java-library")
}

// The test project doesn't produce a JAR.
tasks["jar"].enabled = false

tasks.named<Test>("test") {
useJUnitPlatform()
systemProperty("buildFolder",
gradle.includedBuild("tutorial").projectDir.resolve("service").resolve("build").absolutePath)
}

tasks["test"].dependsOn(gradle.includedBuild("tutorial").task(":service:build"))

dependencies {
val smithyVersion: String by project

testImplementation("software.amazon.smithy:smithy-model:$smithyVersion")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
testImplementation("org.hamcrest:hamcrest:2.1")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.smithy.examples.plugins;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.validation.Severity;
import software.amazon.smithy.model.validation.ValidationEvent;

public class PluginTutorialTest {

static File smithyBuildInfo;
static List<ValidationEvent> validationEvents;

@BeforeAll
static void setup() throws FileNotFoundException {
Path buildFolderPath = Paths.get(System.getProperty("buildFolder"));
System.out.println(buildFolderPath);
smithyBuildInfo = buildFolderPath.resolve("smithyprojections")
.resolve("service")
.resolve("source")
.resolve("build-info")
.resolve("smithy-build-info.json")
.toFile();
var jsonOutput = Node.parse(new FileInputStream(smithyBuildInfo)).expectObjectNode();
validationEvents = jsonOutput.expectMember("validationEvents")
.expectArrayNode()
.getElementsAs(ValidationEvent::fromNode);
}

@Test
public void correctNumberOfValidations() {
var expectedMatches = 1;
assertEquals(validationEvents.size(), expectedMatches);
}

@Test
public void correctSeverityOfValidations() {
assertTrue(validationEvents.stream()
.allMatch(event -> event.getSeverity().equals(Severity.WARNING))
);
}

@Test
public void correctTypesOfValidations() {
assertEquals(validationEvents.stream()
.peek(System.out::println)
.filter(event -> event.getId().equals("DocUrl")).count(), 1);
}
}
26 changes: 26 additions & 0 deletions gradle-plugin-examples/tutorial/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
### Smithy Gradle Plugin Tutorial

An example Smithy project that demonstrates how to use [Smithy Gradle plugins](https://github.com/smithy-lang/smithy-gradle-plugin)
by creating a simple Cafe service model.

This project:
1. Sets up a `lib` project containing Smithy models that are shared across other Smithy projects.
2. Uses these shared models as a dependency of the `service` package to create a Cafe service model.
3. Create a custom linter in the `lib` package that can be used by consumers of the `lib` package to
check for `externalDocumentation` links that do not match an expected site address.

For more information on the use of the Smithy Gradle plugins see the [plugin guide](https://smithy.io/2.0/guides/gradle-plugin/index.html).

## Building
To build this package run the following from the root of the package:

```
./gradlew clean build
```

This will generate a `build` directory in both the `lib` and `service` subprojects
containing the build artifacts generated by gradle.

## Distribution
If you want to distribute either of the JAR files created by this package, consider adding the
[maven-publish](https://docs.gradle.org/current/userguide/publishing_maven.html) plugin to the project to publish the JAR to a maven repository.
9 changes: 9 additions & 0 deletions gradle-plugin-examples/tutorial/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[versions]
commons-math3 = "3.6.1"
guava = "32.1.2-jre"
junit-jupiter = "5.10.2"

[libraries]
commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
45 changes: 45 additions & 0 deletions gradle-plugin-examples/tutorial/lib/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java library project to get you started.
* For more details on building Java & JVM projects, please refer to https://docs.gradle.org/8.5/userguide/building_java_projects.html in the Gradle documentation.
*/

plugins {
// Apply the java-library plugin for API and implementation separation.
`java-library`
// Build Smithy models and add them to the generated library JAR.
id("software.amazon.smithy.gradle.smithy-jar").version("0.10.1")
}

repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}

dependencies {
// Use JUnit Jupiter for testing.
testImplementation(libs.junit.jupiter)

testRuntimeOnly("org.junit.platform:junit-platform-launcher")

// This dependency is exported to consumers, that is to say found on their compile classpath.
api(libs.commons.math3)

// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation(libs.guava)

implementation("software.amazon.smithy:smithy-model:1.45.0")
}

// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}

tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}
23 changes: 23 additions & 0 deletions gradle-plugin-examples/tutorial/lib/model/common.smithy
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
$version: "2.0"

namespace com.example.cafe.common

/// The price of a menu item should be a positive number
@range(min: 0.0)
bigDecimal Price

enum Style {
ICED
HOT
}

enum Type {
COFFEE
TEA
COCOA
}

/// ID used to Identify a menu item
@length(min: 1, max: 64)
@pattern("^[a-zA-Z0-9_]$")
string MenuItemId
3 changes: 3 additions & 0 deletions gradle-plugin-examples/tutorial/lib/smithy-build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": "1.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.example.cafe;

import java.util.List;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.traits.ExternalDocumentationTrait;
import software.amazon.smithy.model.validation.AbstractValidator;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.model.validation.ValidatorService;

public final class DocUrlValidator extends AbstractValidator {
private static final String CAFE_SITE_NAME = "cafe.example.smithy.io";

public static final class Provider extends ValidatorService.Provider {
public Provider() {
super(DocUrlValidator.class, DocUrlValidator::new);
}
}

@Override
public List<ValidationEvent> validate(Model model) {
// Find every shape that violates the linter and return a list
// of ValidationEvents.
return model.shapes()
.filter(shape -> shape.hasTrait(ExternalDocumentationTrait.class))
.flatMap(shape -> shape.expectTrait(ExternalDocumentationTrait.class)
// Get a stream of Name -> URL entries
.getUrls().entrySet().stream()
// Get all URLs that do not match expected internal site name
.filter(entry -> !entry.getValue().contains(CAFE_SITE_NAME))
// Emit a helpful warning message for each invalid URL
.map(entry -> warning(shape, String.format("External url `%s` for `%s` is invalid."
+ " Expected URL to start with " + CAFE_SITE_NAME, entry.getValue(), entry.getKey())))
).toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
com.example.cafe.DocUrlValidator$Provider

15 changes: 15 additions & 0 deletions gradle-plugin-examples/tutorial/service/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

plugins {
`java-library`
id("software.amazon.smithy.gradle.smithy-jar").version("0.10.1")
}

repositories {
mavenCentral()
}

dependencies {
// Use common models
implementation(project(":lib"))
}

58 changes: 58 additions & 0 deletions gradle-plugin-examples/tutorial/service/model/service.smithy
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
$version: "2.0"

metadata validators = [
{
name: "DocUrl"
}
]

namespace com.example.cafe.service

// Add common shapes from `lib` subproject
use com.example.cafe.common#MenuItemId

use com.example.cafe.common#Price
use com.example.cafe.common#Style
use com.example.cafe.common#Type

/// Provides an API for ordering coffee
service CoffeeService {
version: "2024-03-30"
resources: [
Order
]
}

@externalDocumentation(wikipedia: "https://en.wikipedia.org/wiki/Order")
resource Order {
identifiers: { id: MenuItemId }
properties: { price: Price, style: Style, type: Type }
create: CreateOrder
read: GetOrder
}

operation CreateOrder {
input := for Order {
$style
$type
}

output := for Order {
@required
$id
}
}

@readonly
operation GetOrder {
input := for Order {
@required
$id
}

output := for Order {
$price
$style
$type
}
}
3 changes: 3 additions & 0 deletions gradle-plugin-examples/tutorial/service/smithy-build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"version": "1.0"
}
15 changes: 15 additions & 0 deletions gradle-plugin-examples/tutorial/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
* For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.5/userguide/building_swift_projects.html in the Gradle documentation.
*/

plugins {
// Apply the foojay-resolver plugin to allow automatic download of JDKs
id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0"
}

rootProject.name = "smithy-gradle-tutorial"
include("lib")
include("service")
7 changes: 7 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,10 @@ include(":linting-and-validation-examples:integ:common-linting-configuration-tes
include(":linting-and-validation-examples:integ:custom-linter-test")
include(":linting-and-validation-examples:integ:custom-validator-test")
include(":linting-and-validation-examples:integ:decorators-test")

// ---- Gradle Plugin Examples -----
// Templates
includeBuild("gradle-plugin-examples/tutorial")

// Integ test
include(":gradle-plugin-examples:integ:tutorial")
13 changes: 13 additions & 0 deletions smithy-templates.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,19 @@
".gitignore",
".gitattributes"
]
},
"smithy-gradle-tutorial": {
"documentation": "Tutorial project that demonstrates how to use the Smithy Gradle plugins.",
"path": "gradle-plugin-examples/tutorial",
"include": [
"config/",
"gradle/",
"gradlew",
"gradlew.bat",
"gradle.properties",
".gitignore",
".gitattributes"
]
}
}
}

0 comments on commit 2988d7e

Please sign in to comment.