diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..d8b54d5 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @github/data-pipelines diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..73268a6 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,15 @@ +--- +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + ignore: + # private repo has trouble with access, plus we're using main anyways + - dependency-name: github/data-pipelines + reviewers: + - github/data-pipelines + groups: + actions: + patterns: ['*'] diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..d0859b6 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,29 @@ +# This workflow will build a Java project with Gradle +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + steps: + - uses: actions/checkout@v6 + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'zulu' + - name: Build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }} + run: ./gradlew build diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..85ac0b5 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,62 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '40 1 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + packages: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: java + build-mode: none + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up JDK 17 + uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'zulu' + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..113e22a --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,24 @@ +name: Publish to GitHub Packages + +on: + push: + branches: + - master + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-java@v5 + with: + java-version: '17' + distribution: 'zulu' + - name: Publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }} + run: ./gradlew publish diff --git a/README.md b/README.md index f7f438f..9f634b6 100644 --- a/README.md +++ b/README.md @@ -7,50 +7,113 @@ It supports the generation of Java based servers with the following flavours sup + [Spring Boot/Spring MVC](https://spring.io/projects/spring-boot "Spring Boot") + [Undertow](http://undertow.io/ "Undertow") ++ JAX-RS ([Jersey](https://eclipse-ee4j.github.io/jersey/), [Apache CFX](http://cxf.apache.org/)) ++ [Jakarta EE](https://jakarta.ee/ "Jakarta") + +# Contents + + + + + +- [Using](#using) + - [Runtime libraries](#runtime-libraries) + - [Protoc plugin](#protoc-plugin) + - [via gradle](#via-gradle) + - [via protoc](#via-protoc) +- [Development](#development) + - [Requirements](#requirements) + - [Modules](#modules) + - [Gradle commands](#gradle-commands) + - [Remote Debug](#remote-debug) +- [Guides](#guides) + + + +## Using + +### Runtime libraries + +Follow the [Working with the Gradle registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-gradle-registry) docs to set up authentication with GitHub packages. + +[Published](https://github.com/orgs/github/packages?repo_name=flit) runtime libraries example Gradle setup: +```groovy +repositories { + maven { + name = 'GithubPackages' + url 'https://maven.pkg.github.com/github/flit' + credentials { + username = project.findProperty('gpr.user') ?: System.getenv('GITHUB_ACTOR') + password = project.findProperty('gpr.key') ?: System.getenv('GITHUB_TOKEN') + } + content { + includeGroup('com.flit') + } + } +} -## Building & Running - -### Requirements - -The build has been tested with [Oracle's JDK](http://www.oracle.com/technetwork/java/javase/downloads/index.html "JDK Downloads") (version 1.8) - -The build uses gradle to generate the artifacts. No installation is required as the project uses the -[gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html "gradle wrapper") setup. - -For testing you will need an installation of the [protocol buffers compiler](https://github.com/google/protobuf/releases "protobuf releases"). - -### Modules +dependencies { + implementation("com.flit:flit-core-runtime:") + implementation("com.flit:flit-spring-runtime:") + implementation("com.flit:flit-jaxrs-runtime:") + implementation("com.flit:flit-jakarta-runtime:") + implementation("com.flit:flit-undertow-runtime:") +} +``` -The project is split into the following modules: +### Protoc plugin -| Module | Description | -|:------------------|:------------------------------------------------------| -| `plugin` | The `protoc` plugin | -| `runtime:core` | Core functionality required by generated code | -| `runtime:spring` | Runtime library for Spring MVC/Boot servers | -| `runtime:undertow`| Runtime library for Undertow servers | +Plugin `com.flit.flit-plugin` shadow jar can be downloaded from [here](https://github.com/github/flit/packages/1832284). +The flit plugin accepts the following plugin parameters: -### Build +| Name | Required | Type | Description | +|:----------|:---------:|:-------------------------------------------|:-------------------------------------------------------| +| `target` | Y | `enum[server]` | The type of target to generate e.g. server, client etc | +| `type` | Y | `enum[spring,undertow,boot,jakarta,jaxrs]` | Type of target to generate | +| `context` | N | `string` | Base context for routing, default is `/twirp` | +| `request` | N | `string` | If the request parameter should pass to the service | -To build the various components, run the following: +#### via gradle - git clone git@github.com:devork/flit.git - cd flit - ./gradlew clean build pack +The plugin can be called from the Gradle protobuf plugin via additional configuration. For example: -### Installation +```groovy +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:$protocVersion" + } + + plugins { + flit { + path = "${projectDir}/script/protoc-gen-flit" + } + } -Currently, the run script only supports *nix but the run script should be fairly easy to migrate to windows. + generateProtoTasks { + ofSourceSet('main')*.plugins { + flit { + option 'target=server' + option 'type=spring' + option 'request=Service' + } + } + } +} +``` +```shell +#!/usr/bin/env bash -After building: +DIR=$(dirname "$0") - cp plugin/build/package/flit-plugin.zip /usr/local/bin - cd /usr/local/bin - unzip flit-plugin.zip - chmod +x protoc-gen-flit +JAR=$(ls -c ${DIR}/flit-plugin-*.jar | head -1) +java ${FLIT_JAVA_OPTS} -jar $JAR $@ +``` +Where the plugin jar is located in the same directly as the script. -## Running +#### via protoc The plugin is executed as part of a protoc compilation step: @@ -60,58 +123,46 @@ The plugin is executed as part of a protoc compilation step: --flit_out=target=server,type=undertow:../java \ ./haberdasher.proto -### Options +## Development -The flit plugin accepts the following plugin parameters: +### Requirements -| Name | Required | Type | Description | -|:--------------|:---------:|:------------------------------|:----------------------------------------------------------| -| `target` | Y | `enum[server]` | The type of target to generate e.g. server, client etc | -| `type` | Y | `enum[spring,undertow,boot]` | Type of target to generate | -| `context` | N | `string` | Base context for routing, default is `/twirp` | +The build has been tested with [Zulu's OpenJDK](https://www.azul.com/downloads/#zulu "JDK Downloads") version 17. -# Development +The build uses gradle to generate the artifacts. No installation is required as the project uses the +[gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html "gradle wrapper") setup. -All development is done in Java using JDK 8 (as mentioned above). +Optional: to test you will need an installation of the [protocol buffers compiler](https://github.com/protocolbuffers/protobuf/releases "protobuf releases"). -Remote debugging can be performed as follows: +### Modules - export FLIT_JAVA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005,quiet=y" - protoc \ - --proto_path=. \ - --java_out=../java \ - --flit_out=target=server,type=undertow:../java \ - ./haberdasher.proto - -When running with the above options, the generator will enable a remote java debug session on port 5005. This is useful -for debugging a full generation step. - -## Test Fixture Generation - -The test resources contains a fake plugin that simply dumps the binary request to a file called `file.bin`. This utility -can be used to generate test fixtures which can be fed to tests to drive plugin generation, for example: - - $ protoc \ - --plugin=${PWD}/protoc-gen-dump \ - --dump_out=target=server,type=undertow:../java \ - ./helloworld.proto - $ mv file.bin helloworld.bin - -This can be run from the resources directory to generate a `CodeGeneratorRequest` protobuf file, which can then be read -by tests: - - PluginProtos.CodeGeneratorRequest request = null; - try (InputStream is = this.getClass().getClassLoader().getResource("helloworld.bin").openStream()) { - request = PluginProtos.CodeGeneratorRequest - .newBuilder() - .mergeFrom(is) - .build(); - } +The project is split into the following modules: + +| Module | Description | +|:-------------------|:----------------------------------------------| +| `plugin` | The `protoc` plugin | +| `runtime:core` | Core functionality required by generated code | +| `runtime:jakarta` | Runtime library for Jakarta servers | +| `runtime:jaxrs` | Runtime library for JAX-RS servers | +| `runtime:spring` | Runtime library for Spring MVC/Boot servers | +| `runtime:undertow` | Runtime library for Undertow servers | + +### Gradle commands + +* clean `./gradlew clean` +* build `./gradlew build` +* test `./gradlew test` +* publish `./gradlew publish` + +### Remote Debug + +Use remote JVM debugging by setting: + +```shell +export FLIT_JAVA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005,quiet=y" +``` - Plugin plugin = new Plugin(request); - plugin.process(); - -# Guides +## Guides | Platform | Document | |:----------|:--------------------------------------| diff --git a/build.gradle b/build.gradle index 9e30e8e..43e41f9 100644 --- a/build.gradle +++ b/build.gradle @@ -1,38 +1,73 @@ -buildscript { +plugins { + id 'java-library' + id 'maven-publish' + id 'dev.poolside.gradle.semantic-version' version '1.0.0' +} + +allprojects { + apply plugin: 'java-library' + apply plugin: 'maven-publish' + apply plugin: 'dev.poolside.gradle.semantic-version' + repositories { - jcenter() + mavenCentral() + } + + ext { + // third party + jakartaservletapiVersion = "6.0.0" + jakartawsrsapiVersion = "3.1.0" + javapoetVersion = "1.13.0" + javaxservletapiVersion = "4.0.1" + javaxwsrsapiVersion = "2.1.1" + protobufVersion = "3.25.5" + slf4jVersion = "2.0.17" + springVersion = "6.2.11" + undertowVersion = "2.3.20.Final" + + // testing + approvaltestsVersion = "24.22.0" + javaparserVersion = "3.27.0" + jerseyCommonJavaxVersion = "2.47" + jerseyCommonJakartaVersion = "3.1.10" + junitJupiterVersion = "5.12.2" + mockitoVersion = "5.18.0" + + // force version for security + // remove when upgrading other libraries + commonsLang3Version = "3.18.0" // caused by approvaltestsVersion } + + java { + group = "com.flit" + targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_17 + withSourcesJar() + } + + test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } + } + dependencies { - classpath('org.eclipse.jgit:org.eclipse.jgit:4.6.0.201612231935-r') + testImplementation(platform("org.junit:junit-bom:$junitJupiterVersion")) + testImplementation('org.junit.jupiter:junit-jupiter') + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } -} -import org.eclipse.jgit.lib.Repository -import org.eclipse.jgit.storage.file.FileRepositoryBuilder - -import java.time.Instant -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter - -static def scmInfo(String path) { - FileRepositoryBuilder builder = new FileRepositoryBuilder() - Repository repository = builder.setGitDir(new File("${path}/.git")) - .readEnvironment() - .findGitDir() - .build() - - def branch = repository.getBranch() - def head = repository.resolve("HEAD") - def revision = head == null ? "UNKNOWN" : head.name() - def date = Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) - def tstamp = Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")) - - return [ - branch : branch, - revision: revision, - date : date, - tstamp : tstamp - ] + publishing { + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/github/flit") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } + } + } } - -project.ext.scm = scmInfo(rootDir.getAbsolutePath()) \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1948b90..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 58c411e..2a84e18 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Sat Sep 08 13:15:19 CDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip diff --git a/gradlew b/gradlew index cccdd3d..23d15a9 100755 --- a/gradlew +++ b/gradlew @@ -1,78 +1,129 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,92 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f955316..5eed7ee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,84 +1,94 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/plugin/build.gradle b/plugin/build.gradle index db5e390..c379372 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -1,71 +1,31 @@ -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.3' - } +plugins { + id 'com.gradleup.shadow' version '9.3.1' } -project.ext.scm = scmInfo(rootDir.getAbsolutePath()) - -apply plugin: 'com.github.johnrengelman.shadow' -apply plugin: 'java' - -group 'com.flit' - -sourceCompatibility = 1.8 +dependencies { + implementation("com.google.protobuf:protobuf-java:$protobufVersion") + implementation("com.google.protobuf:protobuf-java-util:$protobufVersion") + implementation("com.squareup:javapoet:$javapoetVersion") -configurations { - zip + testImplementation("com.github.javaparser:javaparser-core:$javaparserVersion") + testImplementation("com.approvaltests:approvaltests:$approvaltestsVersion") + testImplementation("org.apache.commons:commons-lang3:$commonsLang3Version") } shadowJar { manifest { attributes( - 'Main-Class': "com.flit.protoc.Main" + 'Main-Class': "com.flit.protoc.Main" ) } } -repositories { - mavenCentral() -} - -dependencies { - compile 'com.google.guava:guava:25.1-jre' - compile 'org.slf4j:slf4j-api:1.7.25' - compile 'ch.qos.logback:logback-classic:1.2.3' - compile 'commons-io:commons-io:2.5' - compile 'com.google.protobuf:protobuf-java:3.5.1' - compile 'com.google.protobuf:protobuf-java-util:3.5.1' - compile 'com.squareup:javapoet:1.11.1' - - compileOnly('org.projectlombok:lombok:+') - - testCompile 'com.github.javaparser:javaparser-core:3.6.9' - testCompile 'junit:junit:4.12' - testCompile 'com.approvaltests:approvaltests:2.0.0' -} - -shadowJar.dependsOn compileJava - -task pack(type: Zip) { - archiveName = "flit-plugin.zip" - destinationDir = file("${buildDir}/package") - - from("${buildDir}/libs") { - include "plugin-*-all.jar" - } - - from("${projectDir}/src/main/shell") { - include("*") +publishing { + publications { + shadow(MavenPublication) { + artifactId = 'flit-plugin' + version = '2.0' + from components.shadow + } } } - -pack.dependsOn shadowJar - -artifacts { - zip pack -} - diff --git a/plugin/gradle.properties b/plugin/gradle.properties deleted file mode 100644 index beb72cc..0000000 --- a/plugin/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -version=1.0.0 \ No newline at end of file diff --git a/plugin/src/main/java/com/flit/protoc/Parameter.java b/plugin/src/main/java/com/flit/protoc/Parameter.java index 0b573f7..8131226 100644 --- a/plugin/src/main/java/com/flit/protoc/Parameter.java +++ b/plugin/src/main/java/com/flit/protoc/Parameter.java @@ -1,29 +1,43 @@ package com.flit.protoc; import com.flit.protoc.gen.GeneratorException; -import lombok.Getter; -import lombok.ToString; - import java.util.Arrays; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; -@Getter @ToString public class Parameter { +public class Parameter { public static final String PARAM_TARGET = "target"; public static final String PARAM_CLIENT = "client"; public static final String PARAM_TYPE = "type"; public static final String PARAM_CONTEXT = "context"; + public static final String PARAM_REQUEST = "request"; - private String key; - private String value; + private final String key; + private final String value; public Parameter(String[] strings) { this.key = strings[0]; this.value = strings[1]; } + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return "Parameter{" + + "key='" + key + '\'' + + ", value='" + value + '\'' + + '}'; + } + public static Map of(String value) { if (value == null || (value = value.trim()).isEmpty()) { throw new GeneratorException("Empty value passed to parameter builder"); diff --git a/plugin/src/main/java/com/flit/protoc/Plugin.java b/plugin/src/main/java/com/flit/protoc/Plugin.java index e238204..eeffe35 100644 --- a/plugin/src/main/java/com/flit/protoc/Plugin.java +++ b/plugin/src/main/java/com/flit/protoc/Plugin.java @@ -1,17 +1,22 @@ package com.flit.protoc; +import static com.flit.protoc.Parameter.PARAM_REQUEST; +import static com.flit.protoc.Parameter.PARAM_TARGET; +import static com.flit.protoc.Parameter.PARAM_TYPE; + import com.flit.protoc.gen.Generator; import com.flit.protoc.gen.GeneratorException; +import com.flit.protoc.gen.server.jakarta.JakartaGenerator; +import com.flit.protoc.gen.server.jaxrs.JaxrsGenerator; import com.flit.protoc.gen.server.spring.SpringGenerator; import com.flit.protoc.gen.server.undertow.UndertowGenerator; import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest; import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse; - +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Map; -import static com.flit.protoc.Parameter.PARAM_TARGET; -import static com.flit.protoc.Parameter.PARAM_TYPE; - public class Plugin { private final CodeGeneratorRequest request; @@ -22,7 +27,9 @@ public Plugin(CodeGeneratorRequest request) { public CodeGeneratorResponse process() { if (!request.hasParameter()) { - return CodeGeneratorResponse.newBuilder().setError("Usage: --flit_out=target=server,type=[spring|undertow]:").build(); + return CodeGeneratorResponse.newBuilder() + .setError("Usage: --flit_out=target=server,type=[spring|undertow|jaxrs][,request=[class(es)]]:") + .build(); } Map params = Parameter.of(request.getParameter()); @@ -42,14 +49,19 @@ private Generator resolveGenerator(Map params) { if (!params.containsKey(PARAM_TYPE)) { throw new GeneratorException("No argument specified for type"); } + List requestServices = getRequestServices(params); switch (params.get(PARAM_TARGET).getValue()) { case "server": switch (params.get(PARAM_TYPE).getValue()) { case "boot": case "spring": - return new SpringGenerator(); + return new SpringGenerator(requestServices); case "undertow": - return new UndertowGenerator(); + return new UndertowGenerator(requestServices); + case "jaxrs": + return new JaxrsGenerator(requestServices); + case "jakarta": + return new JakartaGenerator(requestServices); default: throw new GeneratorException("Unknown server type: " + params.get(PARAM_TYPE).getValue()); } @@ -57,4 +69,13 @@ private Generator resolveGenerator(Map params) { throw new GeneratorException("Unknown target type: " + params.get(PARAM_TARGET).getValue()); } } + + private List getRequestServices(Map params) { + Parameter requestServices = params.get(PARAM_REQUEST); + if (requestServices == null) { + return Collections.emptyList(); + } else { + return Arrays.asList(requestServices.getValue().split(";")); + } + } } diff --git a/plugin/src/main/java/com/flit/protoc/gen/server/BaseServerGenerator.java b/plugin/src/main/java/com/flit/protoc/gen/server/BaseServerGenerator.java index cb94509..4eed234 100644 --- a/plugin/src/main/java/com/flit/protoc/gen/server/BaseServerGenerator.java +++ b/plugin/src/main/java/com/flit/protoc/gen/server/BaseServerGenerator.java @@ -1,18 +1,18 @@ package com.flit.protoc.gen.server; +import static com.flit.protoc.Parameter.PARAM_CONTEXT; + import com.flit.protoc.Parameter; import com.flit.protoc.gen.Generator; import com.google.protobuf.DescriptorProtos.FileDescriptorProto; import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto; import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest; import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse; - +import com.squareup.javapoet.TypeName; import java.util.ArrayList; import java.util.List; import java.util.Map; -import static com.flit.protoc.Parameter.PARAM_CONTEXT; - /** * Implements the basic "generate a service interface + impl dispatcher". * @@ -21,13 +21,19 @@ */ public abstract class BaseServerGenerator implements Generator { + private final List requestServices; + + protected BaseServerGenerator(List requestServices) { + this.requestServices = requestServices; + } + @Override public List generate(CodeGeneratorRequest request, Map params) { List files = new ArrayList<>(); String context = getContext(params); TypeMapper mapper = new TypeMapper(request.getProtoFileList()); request.getProtoFileList().forEach(proto -> { proto.getServiceList().forEach(s -> { - files.addAll(new ServiceGenerator(proto, s, mapper).getFiles()); + files.addAll(new ServiceGenerator(proto, s, mapper, isRequestBasedClass(s), getHttpRequestTypeName()).getFiles()); files.addAll(getRpcGenerator(proto, s, context, mapper).getFiles()); }); }); @@ -41,5 +47,11 @@ private static String getContext(Map params) { return null; } + protected boolean isRequestBasedClass(ServiceDescriptorProto service) { + return requestServices.contains(service.getName()); + } + protected abstract BaseGenerator getRpcGenerator(FileDescriptorProto proto, ServiceDescriptorProto service, String context, TypeMapper mapper); + + protected abstract TypeName getHttpRequestTypeName(); } diff --git a/plugin/src/main/java/com/flit/protoc/gen/server/ServiceGenerator.java b/plugin/src/main/java/com/flit/protoc/gen/server/ServiceGenerator.java index 4eb993d..717bbc1 100644 --- a/plugin/src/main/java/com/flit/protoc/gen/server/ServiceGenerator.java +++ b/plugin/src/main/java/com/flit/protoc/gen/server/ServiceGenerator.java @@ -4,34 +4,48 @@ import com.google.protobuf.compiler.PluginProtos; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; - -import javax.lang.model.element.Modifier; import java.util.Collections; import java.util.List; +import javax.lang.model.element.Modifier; /** * Generates the `Rpc${SerivceName}` interface. * - * Currently this is the same interface across both undertow and spring. + * Currently, this is the same interface across both undertow and spring. */ public class ServiceGenerator extends BaseGenerator { private final TypeSpec.Builder rpcInterface; + private final boolean passRequest; + private final TypeName httpRequestType; - public ServiceGenerator(DescriptorProtos.FileDescriptorProto proto, DescriptorProtos.ServiceDescriptorProto s, TypeMapper mapper) { + public ServiceGenerator( + DescriptorProtos.FileDescriptorProto proto, + DescriptorProtos.ServiceDescriptorProto s, + TypeMapper mapper, + boolean passRequest, + TypeName httpRequestType + ) { super(proto, s, mapper); + this.passRequest = passRequest; + this.httpRequestType = httpRequestType; rpcInterface = TypeSpec.interfaceBuilder(ClassName.get(javaPackage, "Rpc" + service.getName())); rpcInterface.addModifiers(Modifier.PUBLIC); service.getMethodList().forEach(this::addHandleMethod); } private void addHandleMethod(DescriptorProtos.MethodDescriptorProto m) { - rpcInterface.addMethod(MethodSpec.methodBuilder("handle" + m.getName()) + MethodSpec.Builder builder = MethodSpec.methodBuilder("handle" + m.getName()); + if (passRequest) { + builder.addParameter(httpRequestType, "request"); + } + builder .addParameter(mapper.get(m.getInputType()), "in") .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) - .returns(mapper.get(m.getOutputType())) - .build()); + .returns(mapper.get(m.getOutputType())); + rpcInterface.addMethod(builder.build()); } @Override public List getFiles() { diff --git a/plugin/src/main/java/com/flit/protoc/gen/server/TypeMapper.java b/plugin/src/main/java/com/flit/protoc/gen/server/TypeMapper.java index 5a459d9..cb12e41 100644 --- a/plugin/src/main/java/com/flit/protoc/gen/server/TypeMapper.java +++ b/plugin/src/main/java/com/flit/protoc/gen/server/TypeMapper.java @@ -2,14 +2,14 @@ import com.google.protobuf.DescriptorProtos; import com.squareup.javapoet.ClassName; - +import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; public class TypeMapper { - // holds the qualified class name to the short class reference + // Maps the protobuf package and name to the fully qualified name of the generated Java class. private final Map mapping = new HashMap<>(); public TypeMapper() { @@ -21,7 +21,8 @@ public TypeMapper(List files) { public void add(DescriptorProtos.FileDescriptorProto proto) { proto.getMessageTypeList().forEach(m -> { - mapping.put("." + proto.getPackage() + "." + m.getName(), getClassname(proto) + "." + m.getName()); + mapping.put("." + proto.getPackage() + "." + m.getName(), + getOuterClassOrPackageName(proto) + "." + m.getName()); }); } @@ -29,48 +30,76 @@ public ClassName get(String protobufFqcn) { return ClassName.bestGuess(mapping.get(protobufFqcn)); } - public static String getClassname(DescriptorProtos.FileDescriptorProto proto) { - String clazz = proto.getOptions().getJavaOuterClassname(); + /** + * Determine where message or service in a given proto file will be generated. Depending on the + * java specific options in the spec, this could be either inside of an outer class, or at the top + * level of the package. + */ + public static String getOuterClassOrPackageName(DescriptorProtos.FileDescriptorProto proto) { + // If no 'java_package' option is provided, the protoc compiler will default to the protobuf + // package name. + String packageName = proto.getOptions().hasJavaPackage() ? + proto.getOptions().getJavaPackage() : proto.getPackage(); + + // If this option is enabled protoc will generate a class for each message/service at the top + // level of the given package space. Because message name is appended in the add method, this + // should just return the package in that case. If there are collisions protoc should give a + // warning/error. + if (proto.getOptions().getJavaMultipleFiles()) { + return packageName; + } - if (clazz == null || clazz.isEmpty()) { + // If an outer class name is provided it should be used, otherwise we need to infer one based + // on the same rules the protoc compiler uses. + String outerClass = proto.getOptions().hasJavaOuterClassname() ? + proto.getOptions().getJavaOuterClassname() : outerClassNameFromProtoName(proto); - char[] classname = proto.getName().substring(0, proto.getName().lastIndexOf('.')).toCharArray(); - StringBuilder sb = new StringBuilder(); + if (outerClass.isEmpty()) { + throw new IllegalArgumentException("'option java_outer_classname' cannot be set to \"\"."); + } - char previous = '_'; - for (char c : classname) { - if (c == '_') { - previous = c; - continue; - } + String fqName = String.join(".", packageName, outerClass); - if (previous == '_') { - sb.append(Character.toUpperCase(c)); - } else { - sb.append(c); - } + // check to see if there are any messages with this same class name as per java proto specs + // note that we also check the services too as the protoc compiler does that as well. + for (DescriptorProtos.DescriptorProto type : proto.getMessageTypeList()) { + if (type.getName().equals(outerClass)) { + return fqName + "OuterClass"; + } + } - previous = c; + for (DescriptorProtos.ServiceDescriptorProto service : proto.getServiceList()) { + if (service.getName().equals(outerClass)) { + return fqName + "OuterClass"; } + } - clazz = sb.toString(); + return fqName; + } + + private static String outerClassNameFromProtoName(DescriptorProtos.FileDescriptorProto proto) { + String basename = new File(proto.getName()).getName(); + char[] classname = basename.substring(0, basename.lastIndexOf('.')).toCharArray(); + StringBuilder sb = new StringBuilder(); - // check to see if there are any messages with this same class name as per java proto specs - // note that we also check the services too as the protoc compiler does that as well - for (DescriptorProtos.DescriptorProto type : proto.getMessageTypeList()) { - if (type.getName().equals(clazz)) { - return clazz + "OuterClass"; - } + char previous = '_'; + for (char c : classname) { + if (c == '_') { + previous = c; + continue; } - for (DescriptorProtos.ServiceDescriptorProto service : proto.getServiceList()) { - if (service.getName().equals(clazz)) { - return clazz + "OuterClass"; - } + if (previous == '_') { + sb.append(Character.toUpperCase(c)); + } else { + sb.append(c); } + + previous = c; } + String clazz = sb.toString(); + return clazz; } - } diff --git a/plugin/src/main/java/com/flit/protoc/gen/server/jakarta/JakartaGenerator.java b/plugin/src/main/java/com/flit/protoc/gen/server/jakarta/JakartaGenerator.java new file mode 100644 index 0000000..a3520f0 --- /dev/null +++ b/plugin/src/main/java/com/flit/protoc/gen/server/jakarta/JakartaGenerator.java @@ -0,0 +1,29 @@ +package com.flit.protoc.gen.server.jakarta; + +import static com.flit.protoc.gen.server.jakarta.RpcGenerator.HttpServletRequest; + +import com.flit.protoc.gen.server.BaseGenerator; +import com.flit.protoc.gen.server.BaseServerGenerator; +import com.flit.protoc.gen.server.TypeMapper; +import com.google.protobuf.DescriptorProtos.FileDescriptorProto; +import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto; +import com.squareup.javapoet.TypeName; +import java.util.List; + +public class JakartaGenerator extends BaseServerGenerator { + + public JakartaGenerator(List requestServices) { + super(requestServices); + } + + @Override + protected BaseGenerator getRpcGenerator(FileDescriptorProto proto, ServiceDescriptorProto service, + String context, TypeMapper mapper) { + return new RpcGenerator(proto, service, context, mapper, isRequestBasedClass(service)); + } + + @Override + protected TypeName getHttpRequestTypeName() { + return HttpServletRequest; + } +} diff --git a/plugin/src/main/java/com/flit/protoc/gen/server/jakarta/RpcGenerator.java b/plugin/src/main/java/com/flit/protoc/gen/server/jakarta/RpcGenerator.java new file mode 100644 index 0000000..3fbcf2a --- /dev/null +++ b/plugin/src/main/java/com/flit/protoc/gen/server/jakarta/RpcGenerator.java @@ -0,0 +1,133 @@ +package com.flit.protoc.gen.server.jakarta; + +import com.flit.protoc.gen.server.BaseGenerator; +import com.flit.protoc.gen.server.TypeMapper; +import com.flit.protoc.gen.server.Types; +import com.google.common.net.MediaType; +import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.DescriptorProtos.MethodDescriptorProto; +import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File; +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeSpec.Builder; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.Modifier; + +public class RpcGenerator extends BaseGenerator { + + static final ClassName PATH = ClassName.bestGuess("jakarta.ws.rs.Path"); + static final ClassName POST = ClassName.bestGuess("jakarta.ws.rs.POST"); + static final ClassName CONTEXT = ClassName.bestGuess("jakarta.ws.rs.core.Context"); + static final ClassName HttpServletRequest = ClassName.bestGuess("jakarta.servlet.http.HttpServletRequest"); + static final ClassName HttpServletResponse = ClassName.bestGuess("jakarta.servlet.http.HttpServletResponse"); + + private final Builder rpcResource; + private final boolean passRequest; + + RpcGenerator( + DescriptorProtos.FileDescriptorProto proto, + DescriptorProtos.ServiceDescriptorProto service, + String context, + TypeMapper mapper, + boolean passRequest + ) { + super(proto, service, mapper); + String prefix = getContext(context); + this.passRequest = passRequest; + this.rpcResource = TypeSpec.classBuilder(getResourceName(service)) + .addModifiers(Modifier.PUBLIC) + .addAnnotation( + AnnotationSpec.builder(PATH).addMember("value", "$S", + prefix + "/" + (proto.hasPackage() ? proto.getPackage() + "." : "") + service + .getName()).build()); + addInstanceFields(); + addConstructor(); + service.getMethodList().forEach(this::addHandleMethod); + } + + private void addConstructor() { + rpcResource.addMethod(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addParameter(getServiceInterface(), "service") + .addStatement("this.service = service").build()); + } + + private void addHandleMethod(MethodDescriptorProto mdp) { + ClassName inputType = mapper.get(mdp.getInputType()); + ClassName outputType = mapper.get(mdp.getOutputType()); + rpcResource.addMethod(MethodSpec.methodBuilder("handle" + mdp.getName()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(POST) + .addAnnotation(AnnotationSpec.builder(PATH) + .addMember("value", "$S", "/" + mdp.getName()) + .build()) + .addParameter(ParameterSpec.builder(HttpServletRequest, "request") + .addAnnotation(CONTEXT).build()) + .addParameter(ParameterSpec.builder(HttpServletResponse, "response") + .addAnnotation(CONTEXT).build()) + .addException(Types.Exception) + .addStatement("boolean json = false") + .addStatement("final $T data", inputType) + .beginControlFlow("if (request.getContentType().equals($S))", MediaType.PROTOBUF.toString()) + .addStatement("data = $T.parseFrom(request.getInputStream())", inputType) + .nextControlFlow("else if (request.getContentType().startsWith($S))", "application/json") + .addStatement("json = true") + .addStatement("$T.Builder builder = $T.newBuilder()", inputType, inputType) + // try with resources + .beginControlFlow("try ($T reader = new $T(request.getInputStream(), $T.UTF_8))", + Types.InputStreamReader, + Types.InputStreamReader, + Types.StandardCharsets) + .addStatement("$T.parser().merge(reader, builder)", + Types.JsonFormat) + .endControlFlow() + .addStatement("data = builder.build()") + .nextControlFlow("else") + .addStatement("response.setStatus(415)") + .addStatement("response.flushBuffer()") + .addStatement("return") + .endControlFlow() + // route to the service + .addStatement(getRouteToService(), outputType, mdp.getName()) + .addStatement("response.setStatus(200)") + // send the response + .beginControlFlow("if (json)") + .addStatement("response.setContentType($S)", MediaType.JSON_UTF_8.toString()) + .addStatement("response.getOutputStream().write($T.printer().omittingInsignificantWhitespace().print(retval).getBytes($T.UTF_8))", + Types.JsonFormat, + Types.StandardCharsets) + .nextControlFlow("else") + .addStatement("response.setContentType($S)", MediaType.PROTOBUF.toString()) + .addStatement("retval.writeTo(response.getOutputStream())") + .endControlFlow() + .addStatement("response.flushBuffer()") + .build()); + } + + private String getRouteToService() { + if (passRequest) { + return "$T retval = service.handle$L(request, data)"; + } else { + return "$T retval = service.handle$L(data)"; + } + } + + private ClassName getResourceName(DescriptorProtos.ServiceDescriptorProto service) { + return ClassName.get(javaPackage, "Rpc" + service.getName() + "Resource"); + } + + private void addInstanceFields() { + rpcResource.addField(FieldSpec.builder(getServiceInterface(), "service") + .addModifiers(Modifier.PRIVATE, Modifier.FINAL).build()); + } + + @Override + public List getFiles() { + return Collections.singletonList(toFile(getResourceName(service), rpcResource.build())); + } +} diff --git a/plugin/src/main/java/com/flit/protoc/gen/server/jaxrs/JaxrsGenerator.java b/plugin/src/main/java/com/flit/protoc/gen/server/jaxrs/JaxrsGenerator.java new file mode 100644 index 0000000..c4e0332 --- /dev/null +++ b/plugin/src/main/java/com/flit/protoc/gen/server/jaxrs/JaxrsGenerator.java @@ -0,0 +1,29 @@ +package com.flit.protoc.gen.server.jaxrs; + +import static com.flit.protoc.gen.server.jaxrs.RpcGenerator.HttpServletRequest; + +import com.flit.protoc.gen.server.BaseGenerator; +import com.flit.protoc.gen.server.BaseServerGenerator; +import com.flit.protoc.gen.server.TypeMapper; +import com.google.protobuf.DescriptorProtos.FileDescriptorProto; +import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto; +import com.squareup.javapoet.TypeName; +import java.util.List; + +public class JaxrsGenerator extends BaseServerGenerator { + + public JaxrsGenerator(List requestServices) { + super(requestServices); + } + + @Override + protected BaseGenerator getRpcGenerator(FileDescriptorProto proto, ServiceDescriptorProto service, + String context, TypeMapper mapper) { + return new RpcGenerator(proto, service, context, mapper, isRequestBasedClass(service)); + } + + @Override + protected TypeName getHttpRequestTypeName() { + return HttpServletRequest; + } +} diff --git a/plugin/src/main/java/com/flit/protoc/gen/server/jaxrs/RpcGenerator.java b/plugin/src/main/java/com/flit/protoc/gen/server/jaxrs/RpcGenerator.java new file mode 100644 index 0000000..bd14e0b --- /dev/null +++ b/plugin/src/main/java/com/flit/protoc/gen/server/jaxrs/RpcGenerator.java @@ -0,0 +1,133 @@ +package com.flit.protoc.gen.server.jaxrs; + +import com.flit.protoc.gen.server.BaseGenerator; +import com.flit.protoc.gen.server.TypeMapper; +import com.flit.protoc.gen.server.Types; +import com.google.common.net.MediaType; +import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.DescriptorProtos.MethodDescriptorProto; +import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File; +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeSpec.Builder; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.Modifier; + +public class RpcGenerator extends BaseGenerator { + + static final ClassName PATH = ClassName.bestGuess("javax.ws.rs.Path"); + static final ClassName POST = ClassName.bestGuess("javax.ws.rs.POST"); + static final ClassName CONTEXT = ClassName.bestGuess("javax.ws.rs.core.Context"); + static final ClassName HttpServletRequest = ClassName.bestGuess("javax.servlet.http.HttpServletRequest"); + static final ClassName HttpServletResponse = ClassName.bestGuess("javax.servlet.http.HttpServletResponse"); + + private final Builder rpcResource; + private final boolean passRequest; + + RpcGenerator( + DescriptorProtos.FileDescriptorProto proto, + DescriptorProtos.ServiceDescriptorProto service, + String context, + TypeMapper mapper, + boolean passRequest + ) { + super(proto, service, mapper); + String prefix = getContext(context); + this.passRequest = passRequest; + this.rpcResource = TypeSpec.classBuilder(getResourceName(service)) + .addModifiers(Modifier.PUBLIC) + .addAnnotation( + AnnotationSpec.builder(PATH).addMember("value", "$S", + prefix + "/" + (proto.hasPackage() ? proto.getPackage() + "." : "") + service + .getName()).build()); + addInstanceFields(); + addConstructor(); + service.getMethodList().forEach(this::addHandleMethod); + } + + private void addConstructor() { + rpcResource.addMethod(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addParameter(getServiceInterface(), "service") + .addStatement("this.service = service").build()); + } + + private void addHandleMethod(MethodDescriptorProto mdp) { + ClassName inputType = mapper.get(mdp.getInputType()); + ClassName outputType = mapper.get(mdp.getOutputType()); + rpcResource.addMethod(MethodSpec.methodBuilder("handle" + mdp.getName()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(POST) + .addAnnotation(AnnotationSpec.builder(PATH) + .addMember("value", "$S", "/" + mdp.getName()) + .build()) + .addParameter(ParameterSpec.builder(HttpServletRequest, "request") + .addAnnotation(CONTEXT).build()) + .addParameter(ParameterSpec.builder(HttpServletResponse, "response") + .addAnnotation(CONTEXT).build()) + .addException(Types.Exception) + .addStatement("boolean json = false") + .addStatement("final $T data", inputType) + .beginControlFlow("if (request.getContentType().equals($S))", MediaType.PROTOBUF.toString()) + .addStatement("data = $T.parseFrom(request.getInputStream())", inputType) + .nextControlFlow("else if (request.getContentType().startsWith($S))", "application/json") + .addStatement("json = true") + .addStatement("$T.Builder builder = $T.newBuilder()", inputType, inputType) + // try with resources + .beginControlFlow("try ($T reader = new $T(request.getInputStream(), $T.UTF_8))", + Types.InputStreamReader, + Types.InputStreamReader, + Types.StandardCharsets) + .addStatement("$T.parser().merge(reader, builder)", + Types.JsonFormat) + .endControlFlow() + .addStatement("data = builder.build()") + .nextControlFlow("else") + .addStatement("response.setStatus(415)") + .addStatement("response.flushBuffer()") + .addStatement("return") + .endControlFlow() + // route to the service + .addStatement(getRouteToService(), outputType, mdp.getName()) + .addStatement("response.setStatus(200)") + // send the response + .beginControlFlow("if (json)") + .addStatement("response.setContentType($S)", MediaType.JSON_UTF_8.toString()) + .addStatement("response.getOutputStream().write($T.printer().omittingInsignificantWhitespace().print(retval).getBytes($T.UTF_8))", + Types.JsonFormat, + Types.StandardCharsets) + .nextControlFlow("else") + .addStatement("response.setContentType($S)", MediaType.PROTOBUF.toString()) + .addStatement("retval.writeTo(response.getOutputStream())") + .endControlFlow() + .addStatement("response.flushBuffer()") + .build()); + } + + private String getRouteToService() { + if (passRequest) { + return "$T retval = service.handle$L(request, data)"; + } else { + return "$T retval = service.handle$L(data)"; + } + } + + private ClassName getResourceName(DescriptorProtos.ServiceDescriptorProto service) { + return ClassName.get(javaPackage, "Rpc" + service.getName() + "Resource"); + } + + private void addInstanceFields() { + rpcResource.addField(FieldSpec.builder(getServiceInterface(), "service") + .addModifiers(Modifier.PRIVATE, Modifier.FINAL).build()); + } + + @Override + public List getFiles() { + return Collections.singletonList(toFile(getResourceName(service), rpcResource.build())); + } +} diff --git a/plugin/src/main/java/com/flit/protoc/gen/server/spring/RpcGenerator.java b/plugin/src/main/java/com/flit/protoc/gen/server/spring/RpcGenerator.java index 2ea7459..44e1595 100644 --- a/plugin/src/main/java/com/flit/protoc/gen/server/spring/RpcGenerator.java +++ b/plugin/src/main/java/com/flit/protoc/gen/server/spring/RpcGenerator.java @@ -3,6 +3,7 @@ import com.flit.protoc.gen.server.BaseGenerator; import com.flit.protoc.gen.server.TypeMapper; import com.flit.protoc.gen.server.Types; +import com.google.common.net.MediaType; import com.google.protobuf.DescriptorProtos; import com.google.protobuf.compiler.PluginProtos; import com.squareup.javapoet.*; @@ -13,18 +14,26 @@ class RpcGenerator extends BaseGenerator { - public static final ClassName RestController = ClassName.bestGuess("org.springframework.web.bind.annotation.RestController"); - public static final ClassName Autowired = ClassName.bestGuess("org.springframework.beans.factory.annotation.Autowired"); - public static final ClassName PostMapping = ClassName.bestGuess("org.springframework.web.bind.annotation.PostMapping"); - public static final ClassName HttpServletRequest = ClassName.bestGuess("javax.servlet.http.HttpServletRequest"); - public static final ClassName HttpServletResponse = ClassName.bestGuess("javax.servlet.http.HttpServletResponse"); + static final ClassName RestController = ClassName.bestGuess("org.springframework.web.bind.annotation.RestController"); + static final ClassName Autowired = ClassName.bestGuess("org.springframework.beans.factory.annotation.Autowired"); + static final ClassName PostMapping = ClassName.bestGuess("org.springframework.web.bind.annotation.PostMapping"); + static final ClassName HttpServletRequest = ClassName.bestGuess("jakarta.servlet.http.HttpServletRequest"); + static final ClassName HttpServletResponse = ClassName.bestGuess("jakarta.servlet.http.HttpServletResponse"); private final String context; private final TypeSpec.Builder rpcController; + private final boolean passRequest; - RpcGenerator(DescriptorProtos.FileDescriptorProto proto, DescriptorProtos.ServiceDescriptorProto service, String context, TypeMapper mapper) { + RpcGenerator( + DescriptorProtos.FileDescriptorProto proto, + DescriptorProtos.ServiceDescriptorProto service, + String context, + TypeMapper mapper, + boolean passRequest + ) { super(proto, service, mapper); this.context = getContext(context); + this.passRequest = passRequest; rpcController = TypeSpec.classBuilder(getControllerName()).addModifiers(Modifier.PUBLIC).addAnnotation(RestController); addInstanceFields(); service.getMethodList().forEach(this::addHandleMethod); @@ -47,35 +56,46 @@ private void addHandleMethod(DescriptorProtos.MethodDescriptorProto m) { .addAnnotation(AnnotationSpec.builder(PostMapping).addMember("value", "$S", route).build()) .addStatement("boolean json = false") .addStatement("final $T data", inputType) - .beginControlFlow("if (request.getContentType().equals($S))", "application/protobuf") + .beginControlFlow("if (request.getContentType().equals($S))", MediaType.PROTOBUF.toString()) .addStatement("data = $T.parseFrom(request.getInputStream())", inputType) .nextControlFlow("else if (request.getContentType().startsWith($S))", "application/json") .addStatement("json = true") .addStatement("$T.Builder builder = $T.newBuilder()", inputType, inputType) - .addStatement("$T.parser().merge(new $T(request.getInputStream(), $T.UTF_8), builder)", - Types.JsonFormat, + // try with resources + .beginControlFlow("try ($T reader = new $T(request.getInputStream(), $T.UTF_8))", + Types.InputStreamReader, Types.InputStreamReader, Types.StandardCharsets) + .addStatement("$T.parser().merge(reader, builder)", + Types.JsonFormat) + .endControlFlow() .addStatement("data = builder.build()") .nextControlFlow("else") .addStatement("response.setStatus(415)") .addStatement("return") .endControlFlow() // route to the service - .addStatement("$T retval = service.handle$L(data)", outputType, m.getName()) + .addStatement(getRouteToService(), outputType, m.getName()) .addStatement("response.setStatus(200)") // send the response .beginControlFlow("if (json)") - .addStatement("response.setContentType($S)", "application/json;charset=UTF-8") + .addStatement("response.setContentType($S)", MediaType.JSON_UTF_8.toString()) .addStatement("response.getOutputStream().write($T.printer().omittingInsignificantWhitespace().print(retval).getBytes($T.UTF_8))", Types.JsonFormat, Types.StandardCharsets) .nextControlFlow("else") - .addStatement("response.setContentType($S)", "application/protobuf") + .addStatement("response.setContentType($S)", MediaType.PROTOBUF.toString()) .addStatement("retval.writeTo(response.getOutputStream())") .endControlFlow() .build()); + } + private String getRouteToService() { + if (passRequest) { + return "$T retval = service.handle$L(request, data)"; + } else { + return "$T retval = service.handle$L(data)"; + } } private ClassName getControllerName() { diff --git a/plugin/src/main/java/com/flit/protoc/gen/server/spring/SpringGenerator.java b/plugin/src/main/java/com/flit/protoc/gen/server/spring/SpringGenerator.java index abfe4f3..3a8ce18 100644 --- a/plugin/src/main/java/com/flit/protoc/gen/server/spring/SpringGenerator.java +++ b/plugin/src/main/java/com/flit/protoc/gen/server/spring/SpringGenerator.java @@ -1,19 +1,32 @@ package com.flit.protoc.gen.server.spring; +import static com.flit.protoc.gen.server.spring.RpcGenerator.HttpServletRequest; + import com.flit.protoc.gen.server.BaseGenerator; import com.flit.protoc.gen.server.BaseServerGenerator; import com.flit.protoc.gen.server.TypeMapper; import com.google.protobuf.DescriptorProtos.FileDescriptorProto; import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto; +import com.squareup.javapoet.TypeName; +import java.util.List; /** * Spring specific generator that will output MVC style routes. */ public class SpringGenerator extends BaseServerGenerator { - @Override protected BaseGenerator getRpcGenerator( + public SpringGenerator(List requestServices) { + super(requestServices); + } + + @Override + protected BaseGenerator getRpcGenerator( FileDescriptorProto proto, ServiceDescriptorProto service, String context, TypeMapper mapper) { - return new RpcGenerator(proto, service, context, mapper); + return new RpcGenerator(proto, service, context, mapper, isRequestBasedClass(service)); } + @Override + protected TypeName getHttpRequestTypeName() { + return HttpServletRequest; + } } diff --git a/plugin/src/main/java/com/flit/protoc/gen/server/undertow/RpcGenerator.java b/plugin/src/main/java/com/flit/protoc/gen/server/undertow/RpcGenerator.java index c751944..160ac88 100644 --- a/plugin/src/main/java/com/flit/protoc/gen/server/undertow/RpcGenerator.java +++ b/plugin/src/main/java/com/flit/protoc/gen/server/undertow/RpcGenerator.java @@ -1,30 +1,52 @@ package com.flit.protoc.gen.server.undertow; +import static com.flit.protoc.gen.server.Types.ErrorCode; +import static com.flit.protoc.gen.server.Types.ErrorWriter; +import static com.flit.protoc.gen.server.Types.Exception; +import static com.flit.protoc.gen.server.Types.FlitException; +import static com.flit.protoc.gen.server.Types.FlitHandler; +import static com.flit.protoc.gen.server.Types.Headers; +import static com.flit.protoc.gen.server.Types.HttpServerExchange; +import static com.flit.protoc.gen.server.Types.InputStreamReader; +import static com.flit.protoc.gen.server.Types.JsonFormat; +import static com.flit.protoc.gen.server.Types.Logger; +import static com.flit.protoc.gen.server.Types.LoggerFactory; +import static com.flit.protoc.gen.server.Types.Override; +import static com.flit.protoc.gen.server.Types.StandardCharsets; +import static javax.lang.model.element.Modifier.FINAL; +import static javax.lang.model.element.Modifier.PRIVATE; +import static javax.lang.model.element.Modifier.PUBLIC; +import static javax.lang.model.element.Modifier.STATIC; + import com.flit.protoc.gen.server.BaseGenerator; import com.flit.protoc.gen.server.TypeMapper; import com.flit.protoc.gen.server.Types; +import com.google.common.net.MediaType; import com.google.protobuf.DescriptorProtos; import com.google.protobuf.compiler.PluginProtos; -import com.squareup.javapoet.*; - -import java.lang.Override; -import java.lang.String; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; import java.util.Collections; import java.util.List; -import static com.flit.protoc.gen.server.Types.*; -import static com.flit.protoc.gen.server.Types.Exception; -import static com.flit.protoc.gen.server.Types.Override; -import static javax.lang.model.element.Modifier.*; - class RpcGenerator extends BaseGenerator { private final String context; private final TypeSpec.Builder rpcHandler; + private final boolean passRequest; - RpcGenerator(DescriptorProtos.FileDescriptorProto proto, DescriptorProtos.ServiceDescriptorProto service, String context, TypeMapper mapper) { + RpcGenerator( + DescriptorProtos.FileDescriptorProto proto, + DescriptorProtos.ServiceDescriptorProto service, + String context, TypeMapper mapper, + boolean passRequest + ) { super(proto, service, mapper); this.context = getContext(context); + this.passRequest = passRequest; rpcHandler = TypeSpec.classBuilder(getHandlerName(service)) .addModifiers(PUBLIC) .addSuperinterface(ClassName.bestGuess("io.undertow.server.HttpHandler")); @@ -110,31 +132,46 @@ private void writeHandleMethod(DescriptorProtos.MethodDescriptorProto m) { .addStatement("boolean json = false") .addStatement("final $T data", inputType) .addStatement("final String contentType = exchange.getRequestHeaders().get($T.CONTENT_TYPE).getFirst()", Headers) - .beginControlFlow("if (contentType.equals($S))", "application/protobuf") + .beginControlFlow("if (contentType.equals($S))", MediaType.PROTOBUF.toString()) .addStatement("data = $T.parseFrom(exchange.getInputStream())", inputType) .nextControlFlow("else if (contentType.startsWith($S))", "application/json") .addStatement("json = true") .addStatement("$T.Builder builder = $T.newBuilder()", inputType, inputType) - .addStatement("$T.parser().merge(new $T(exchange.getInputStream(), $T.UTF_8), builder)", JsonFormat, InputStreamReader, StandardCharsets) + // try with resources + .beginControlFlow("try ($T reader = new $T(exchange.getInputStream(), $T.UTF_8))", + InputStreamReader, + InputStreamReader, + StandardCharsets) + .addStatement("$T.parser().merge(reader, builder)", + JsonFormat) + .endControlFlow() .addStatement("data = builder.build()") .nextControlFlow("else") .addStatement("exchange.setStatusCode(415)") .addStatement("return") .endControlFlow() // data is populated, now route to the service - .addStatement("$T response = service.handle$L(data)", outputType, m.getName()) + .addStatement(getRouteToService(), outputType, m.getName()) .addStatement("exchange.setStatusCode(200)") // put the result on the wire .beginControlFlow("if (json)") - .addStatement("exchange.getResponseHeaders().put($T.CONTENT_TYPE, $S)", Headers, "application/json;charset=UTF-8") + .addStatement("exchange.getResponseHeaders().put($T.CONTENT_TYPE, $S)", Headers, MediaType.JSON_UTF_8.toString()) .addStatement("exchange.getResponseSender().send($T.printer().omittingInsignificantWhitespace().print(response))", JsonFormat) .nextControlFlow("else") - .addStatement("exchange.getResponseHeaders().put($T.CONTENT_TYPE, $S)", Headers, "application/protobuf") + .addStatement("exchange.getResponseHeaders().put($T.CONTENT_TYPE, $S)", Headers, MediaType.PROTOBUF.toString()) .addStatement("response.writeTo(exchange.getOutputStream())") .endControlFlow() .build()); } + private String getRouteToService() { + if (passRequest) { + return "$T response = service.handle$L(exchange, data)"; + } else { + return "$T response = service.handle$L(data)"; + } + } + @Override public List getFiles() { return Collections.singletonList(toFile(getHandlerName(service), rpcHandler.build())); } diff --git a/plugin/src/main/java/com/flit/protoc/gen/server/undertow/UndertowGenerator.java b/plugin/src/main/java/com/flit/protoc/gen/server/undertow/UndertowGenerator.java index fa11771..95f9323 100644 --- a/plugin/src/main/java/com/flit/protoc/gen/server/undertow/UndertowGenerator.java +++ b/plugin/src/main/java/com/flit/protoc/gen/server/undertow/UndertowGenerator.java @@ -1,16 +1,29 @@ package com.flit.protoc.gen.server.undertow; +import static com.flit.protoc.gen.server.Types.HttpServerExchange; + import com.flit.protoc.gen.server.BaseGenerator; import com.flit.protoc.gen.server.BaseServerGenerator; import com.flit.protoc.gen.server.TypeMapper; import com.google.protobuf.DescriptorProtos.FileDescriptorProto; import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto; +import com.squareup.javapoet.TypeName; +import java.util.List; public class UndertowGenerator extends BaseServerGenerator { - @Override protected BaseGenerator getRpcGenerator( + public UndertowGenerator(List requestServices) { + super(requestServices); + } + + @Override + protected BaseGenerator getRpcGenerator( FileDescriptorProto proto, ServiceDescriptorProto service, String context, TypeMapper mapper) { - return new RpcGenerator(proto, service, context, mapper); + return new RpcGenerator(proto, service, context, mapper, isRequestBasedClass(service)); } + @Override + protected TypeName getHttpRequestTypeName() { + return HttpServerExchange; + } } diff --git a/plugin/src/test/java/com/flit/protoc/PluginTest.java b/plugin/src/test/java/com/flit/protoc/PluginTest.java index 49499f1..0109fd7 100644 --- a/plugin/src/test/java/com/flit/protoc/PluginTest.java +++ b/plugin/src/test/java/com/flit/protoc/PluginTest.java @@ -1,76 +1,96 @@ package com.flit.protoc; -import com.google.protobuf.compiler.PluginProtos; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; -import static junit.framework.TestCase.assertTrue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import com.google.protobuf.compiler.PluginProtos; +import org.junit.jupiter.api.Test; public class PluginTest { - @Test public void test_NoParameters() { + @Test + public void test_NoParameters() { Plugin plugin = new Plugin(PluginProtos.CodeGeneratorRequest.newBuilder().build()); PluginProtos.CodeGeneratorResponse response = plugin.process(); - assertTrue("Expected an error for no parameters", response.hasError()); - assertEquals("Incorrect error message", "Usage: --flit_out=target=server,type=[spring|undertow]:", response.getError()); + assertTrue(response.hasError(), "Expected an error for no parameters"); + assertEquals( + "Usage: --flit_out=target=server,type=[spring|undertow|jaxrs][,request=[class(es)]]:", + response.getError()); } - @Test public void test_NoTargetSpecified() { - Plugin plugin = new Plugin(PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("unknown=unknown").build()); + @Test + public void test_NoTargetSpecified() { + Plugin plugin = new Plugin( + PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("unknown=unknown").build()); PluginProtos.CodeGeneratorResponse response = plugin.process(); - assertTrue("Expected an error for unknown target type", response.hasError()); - assertEquals("Incorrect error message", "No argument specified for target", response.getError()); + assertTrue(response.hasError(), "Expected an error for unknown target type"); + assertEquals("No argument specified for target", response.getError()); } - @Test public void test_UnknownTargetType() { - Plugin plugin = new Plugin(PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("target=unknown,type=boot").build()); + @Test + public void test_UnknownTargetType() { + Plugin plugin = new Plugin( + PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("target=unknown,type=boot") + .build()); PluginProtos.CodeGeneratorResponse response = plugin.process(); - assertTrue("Expected an error for unknown target type", response.hasError()); - assertEquals("Incorrect error message", "Unknown target type: unknown", response.getError()); + assertTrue(response.hasError(), "Expected an error for unknown target type"); + assertEquals("Unknown target type: unknown", response.getError()); } - @Test public void test_EmptyTargetType() { - Plugin plugin = new Plugin(PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("target=").build()); + @Test + public void test_EmptyTargetType() { + Plugin plugin = new Plugin( + PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("target=").build()); PluginProtos.CodeGeneratorResponse response = plugin.process(); - assertTrue("Expected an error for unknown target type", response.hasError()); - assertEquals("Incorrect error message", "No argument specified for target", response.getError()); + assertTrue(response.hasError(), "Expected an error for unknown target type"); + assertEquals("No argument specified for target", response.getError()); } - @Test public void test_MissingTargetType() { - Plugin plugin = new Plugin(PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("target=server").build()); + @Test + public void test_MissingTargetType() { + Plugin plugin = new Plugin( + PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("target=server").build()); PluginProtos.CodeGeneratorResponse response = plugin.process(); - assertTrue("Expected an error for unknown server type", response.hasError()); - assertEquals("Incorrect error message", "No argument specified for type", response.getError()); + assertTrue(response.hasError(), "Expected an error for unknown server type"); + assertEquals("No argument specified for type", response.getError()); } - @Test public void test_UnknownServerType() { - Plugin plugin = new Plugin(PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("target=server,type=unknown").build()); + @Test + public void test_UnknownServerType() { + Plugin plugin = new Plugin( + PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("target=server,type=unknown") + .build()); PluginProtos.CodeGeneratorResponse response = plugin.process(); - assertTrue("Expected an error for unknown server type", response.hasError()); - assertEquals("Incorrect error message", "Unknown server type: unknown", response.getError()); + assertTrue(response.hasError(), "Expected an error for unknown server type"); + assertEquals("Unknown server type: unknown", response.getError()); } - @Test public void test_MissingServerType() { - Plugin plugin = new Plugin(PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("target=server,type=").build()); + @Test + public void test_MissingServerType() { + Plugin plugin = new Plugin( + PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("target=server,type=").build()); PluginProtos.CodeGeneratorResponse response = plugin.process(); - assertTrue("Expected an error for unknown server type", response.hasError()); - assertEquals("Incorrect error message", "No argument specified for type", response.getError()); + assertTrue(response.hasError(), "Expected an error for unknown server type"); + assertEquals("No argument specified for type", response.getError()); } - @Test public void test_EmptyProtoList() { - Plugin plugin = new Plugin(PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("target=server,type=boot").build()); + @Test + public void test_EmptyProtoList() { + Plugin plugin = new Plugin( + PluginProtos.CodeGeneratorRequest.newBuilder().setParameter("target=server,type=boot") + .build()); PluginProtos.CodeGeneratorResponse response = plugin.process(); - assertFalse("No error expected for empty file list", response.hasError()); - assertEquals("Expected no files generated", 0, response.getFileCount()); + assertFalse(response.hasError(), "No error expected for empty file list"); + assertEquals(0, response.getFileCount()); } } diff --git a/plugin/src/test/java/com/flit/protoc/gen/BaseGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/BaseGeneratorTest.java index 9bddea4..3dcc7b7 100644 --- a/plugin/src/test/java/com/flit/protoc/gen/BaseGeneratorTest.java +++ b/plugin/src/test/java/com/flit/protoc/gen/BaseGeneratorTest.java @@ -1,35 +1,31 @@ package com.flit.protoc.gen; -import com.github.javaparser.JavaParser; -import com.google.protobuf.MessageOrBuilder; +import com.github.javaparser.StaticJavaParser; import com.google.protobuf.compiler.PluginProtos; import com.google.protobuf.util.JsonFormat; -import org.apache.commons.io.FileUtils; - -import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; -import static java.nio.charset.StandardCharsets.UTF_8; - public abstract class BaseGeneratorTest { public static PluginProtos.CodeGeneratorRequest loadJson(String resource) throws Exception { + return loadJson(resource, null); + } + + public static PluginProtos.CodeGeneratorRequest loadJson(String resource, String parameterOverride) throws Exception { try (InputStream is = BaseGeneratorTest.class.getClassLoader().getResource(resource).openStream()) { PluginProtos.CodeGeneratorRequest.Builder b = PluginProtos.CodeGeneratorRequest.newBuilder(); JsonFormat.parser().merge(new InputStreamReader(is), b); + if (parameterOverride != null) { + b.setParameter(parameterOverride); + } return b.build(); } } - // For converting initial .bin dumps over to .json - public static void saveAsJson(MessageOrBuilder request, String fileName) throws Exception { - FileUtils.writeStringToFile(new File(fileName), JsonFormat.printer().print(request), UTF_8); - } - protected static void assertParses(PluginProtos.CodeGeneratorResponse.File file) { try { - JavaParser.parse(file.getContent()); + StaticJavaParser.parse(file.getContent()); } catch (Exception e) { throw new RuntimeException("Could not parse " + file.getName(), e); } diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/TypeMapperTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/TypeMapperTest.java new file mode 100644 index 0000000..57e7bc9 --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/TypeMapperTest.java @@ -0,0 +1,205 @@ +package com.flit.protoc.gen.server; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.protobuf.DescriptorProtos; +import com.squareup.javapoet.ClassName; +import org.junit.jupiter.api.Test; + +public class TypeMapperTest { + + private static final String PROTO_PACKAGE = "flit.test"; + private static final String JAVA_PACKAGE = "com.flit.test"; + + private static final DescriptorProtos.DescriptorProto MAP_MESSAGE = DescriptorProtos.DescriptorProto + .newBuilder() + .setName("Map") + .build(); + + private static final DescriptorProtos.DescriptorProto MAPPER_MESSAGE = DescriptorProtos.DescriptorProto + .newBuilder() + .setName("Mapper") + .build(); + + @Test + public void protoPackage() { + DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder() + .setJavaMultipleFiles(false) + .build(); + + DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder() + .setPackage(PROTO_PACKAGE) + .setName("mapper.proto") + .setOptions(options) + .addMessageType(MAP_MESSAGE) + .build(); + + TypeMapper mapper = new TypeMapper(); + mapper.add(proto); + + ClassName result = mapper.get(".flit.test.Map"); + assertEquals(PROTO_PACKAGE, result.packageName()); + assertEquals("Mapper", result.enclosingClassName().simpleName()); + assertEquals("Map", result.simpleName()); + } + + @Test + public void protoPackageNameCollision() { + DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder() + .setJavaMultipleFiles(false) + .build(); + + DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder() + .setPackage(PROTO_PACKAGE) + .setName("mapper.proto") + .setOptions(options) + .addMessageType(MAPPER_MESSAGE) + .build(); + + TypeMapper mapper = new TypeMapper(); + mapper.add(proto); + + ClassName result = mapper.get(".flit.test.Mapper"); + assertEquals(PROTO_PACKAGE, result.packageName()); + assertEquals("MapperOuterClass", result.enclosingClassName().simpleName()); + assertEquals("Mapper", result.simpleName()); + } + + @Test + public void protoPackageWithOuterClass() { + DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder() + .setJavaMultipleFiles(false) + .setJavaOuterClassname("Mapper") + .build(); + + DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder() + .setPackage(PROTO_PACKAGE) + .setName("mapper.proto") + .setOptions(options) + .addMessageType(MAP_MESSAGE) + .build(); + + TypeMapper mapper = new TypeMapper(); + mapper.add(proto); + + ClassName result = mapper.get(".flit.test.Map"); + assertEquals(PROTO_PACKAGE, result.packageName()); + assertEquals("Mapper", result.enclosingClassName().simpleName()); + assertEquals("Map", result.simpleName()); + } + + @Test + public void javaPackage() { + DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder() + .setJavaMultipleFiles(false) + .setJavaPackage(JAVA_PACKAGE) + .build(); + + DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder() + .setPackage(PROTO_PACKAGE) + .setName("mapper.proto") + .setOptions(options) + .addMessageType(MAP_MESSAGE) + .build(); + + TypeMapper mapper = new TypeMapper(); + mapper.add(proto); + + ClassName result = mapper.get(".flit.test.Map"); + assertEquals(JAVA_PACKAGE, result.packageName()); + assertEquals("Mapper", result.enclosingClassName().simpleName()); + assertEquals("Map", result.simpleName()); + } + + @Test + public void javaPackageNameCollision() { + DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder() + .setJavaMultipleFiles(false) + .setJavaPackage(JAVA_PACKAGE) + .build(); + + DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder() + .setPackage(PROTO_PACKAGE) + .setName("mapper.proto") + .setOptions(options) + .addMessageType(MAPPER_MESSAGE) + .build(); + + TypeMapper mapper = new TypeMapper(); + mapper.add(proto); + + ClassName result = mapper.get(".flit.test.Mapper"); + assertEquals(JAVA_PACKAGE, result.packageName()); + assertEquals("MapperOuterClass", result.enclosingClassName().simpleName()); + assertEquals("Mapper", result.simpleName()); + } + + @Test + public void javaPackageWithOuterClass() { + DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder() + .setJavaMultipleFiles(false) + .setJavaOuterClassname("Mapper") + .setJavaPackage(JAVA_PACKAGE) + .build(); + + DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder() + .setPackage(PROTO_PACKAGE) + .setName("mapper.proto") + .setOptions(options) + .addMessageType(MAP_MESSAGE) + .build(); + + TypeMapper mapper = new TypeMapper(); + mapper.add(proto); + + ClassName result = mapper.get(".flit.test.Map"); + assertEquals(JAVA_PACKAGE, result.packageName()); + assertEquals("Mapper", result.enclosingClassName().simpleName()); + assertEquals("Map", result.simpleName()); + } + + @Test + public void javaPackageWithOuterClassEmpty() { + DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder() + .setJavaMultipleFiles(false) + .setJavaOuterClassname("") + .setJavaPackage(JAVA_PACKAGE) + .build(); + + DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder() + .setPackage(PROTO_PACKAGE) + .setName("mapper.proto") + .setOptions(options) + .addMessageType(MAP_MESSAGE) + .build(); + + TypeMapper mapper = new TypeMapper(); + assertThrows(IllegalArgumentException.class, () -> mapper.add(proto)); + } + + @Test + public void javaPackageWithOuterClassMultiFile() { + DescriptorProtos.FileOptions options = DescriptorProtos.FileOptions.newBuilder() + .setJavaMultipleFiles(true) + .setJavaOuterClassname("Mapper") + .setJavaPackage(JAVA_PACKAGE) + .build(); + + DescriptorProtos.FileDescriptorProto proto = DescriptorProtos.FileDescriptorProto.newBuilder() + .setPackage(PROTO_PACKAGE) + .setName("mapper.proto") + .setOptions(options) + .addMessageType(MAP_MESSAGE) + .build(); + + TypeMapper mapper = new TypeMapper(); + mapper.add(proto); + + ClassName result = mapper.get(".flit.test.Map"); + assertEquals(JAVA_PACKAGE, result.packageName()); + assertNull(result.enclosingClassName()); + assertEquals("Map", result.simpleName()); + } +} diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/ContextGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/ContextGeneratorTest.java new file mode 100644 index 0000000..676661b --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/ContextGeneratorTest.java @@ -0,0 +1,67 @@ +package com.flit.protoc.gen.server.jakarta; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.flit.protoc.Plugin; +import com.flit.protoc.gen.BaseGeneratorTest; +import com.google.protobuf.compiler.PluginProtos; +import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +/** + * Tests the generation of a service that has core definition imported from another file + */ +public class ContextGeneratorTest extends BaseGeneratorTest { + + @Test + public void test_GenerateWithMissingRoot() throws Exception { + test_Route("context.missing.jakarta.json", "/twirp/com.example.context.NullService"); + } + + @Test + public void test_GenerateWithEmptyRoot() throws Exception { + test_Route("context.empty.jakarta.json", "/twirp/com.example.context.NullService"); + } + + @Test + public void test_GenerateWithSlashOnlyRoot() throws Exception { + test_Route("context.slash.jakarta.json", "/com.example.context.NullService"); + } + + @Test + public void test_GenerateWithSlashRoot() throws Exception { + test_Route("context.root.jakarta.json", "/root/com.example.context.NullService"); + } + + @Test + public void test_GenerateWithNameRoot() throws Exception { + test_Route("context.name.jakarta.json", "/fibble/com.example.context.NullService"); + } + + private void test_Route(String file, String route) throws Exception { + PluginProtos.CodeGeneratorRequest request = loadJson(file); + + Plugin plugin = new Plugin(request); + PluginProtos.CodeGeneratorResponse response = plugin.process(); + + assertNotNull(response); + assertEquals(2, response.getFileCount()); + + Map files = response.getFileList() + .stream() + .collect(Collectors + .toMap(File::getName, Function.identity())); + + assertTrue(files.containsKey("com/example/context/rpc/RpcNullService.java")); + assertTrue(files.containsKey("com/example/context/rpc/RpcNullServiceResource.java")); + + assertTrue(files.get("com/example/context/rpc/RpcNullServiceResource.java") + .getContent() + .contains(String.format("@Path(\"%s\")", route))); + } +} diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/HelloworldGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/HelloworldGeneratorTest.java new file mode 100644 index 0000000..b719eac --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/HelloworldGeneratorTest.java @@ -0,0 +1,47 @@ +package com.flit.protoc.gen.server.jakarta; + +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.flit.protoc.Plugin; +import com.flit.protoc.gen.BaseGeneratorTest; +import com.google.protobuf.compiler.PluginProtos; +import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File; +import org.approvaltests.Approvals; +import org.junit.jupiter.api.Test; + +public class HelloworldGeneratorTest extends BaseGeneratorTest { + + @Test + public void test_Generate() throws Exception { + PluginProtos.CodeGeneratorRequest request = loadJson("helloworld.jakarta.json"); + + Plugin plugin = new Plugin(request); + PluginProtos.CodeGeneratorResponse response = plugin.process(); + + assertNotNull(response); + assertEquals(2, response.getFileCount()); + assertEquals(response.getFile(0).getName(), "com/example/helloworld/RpcHelloWorld.java"); + assertEquals(response.getFile(1).getName(), "com/example/helloworld/RpcHelloWorldResource.java"); + + Approvals.verifyAll("", response.getFileList().stream().map(File::getContent).collect(toList())); + response.getFileList().forEach(BaseGeneratorTest::assertParses); + } + + @Test + public void test_GenerateWithRequest() throws Exception { + PluginProtos.CodeGeneratorRequest request = loadJson("helloworld.jakarta.json", "target=server,type=jakarta,request=HelloWorld"); + + Plugin plugin = new Plugin(request); + PluginProtos.CodeGeneratorResponse response = plugin.process(); + + assertNotNull(response); + assertEquals(2, response.getFileCount()); + assertEquals(response.getFile(0).getName(), "com/example/helloworld/RpcHelloWorld.java"); + assertEquals(response.getFile(1).getName(), "com/example/helloworld/RpcHelloWorldResource.java"); + + Approvals.verifyAll("", response.getFileList().stream().map(File::getContent).collect(toList())); + response.getFileList().forEach(BaseGeneratorTest::assertParses); + } +} diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/HelloworldGeneratorTest.test_Generate.approved.txt b/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/HelloworldGeneratorTest.test_Generate.approved.txt new file mode 100644 index 0000000..9393d6f --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/HelloworldGeneratorTest.test_Generate.approved.txt @@ -0,0 +1,93 @@ +[0] = package com.example.helloworld; + +public interface RpcHelloWorld { + Helloworld.HelloResp handleHello(Helloworld.HelloReq in); + + Helloworld.HelloResp handleHelloAgain(Helloworld.HelloReq in); +} + +[1] = package com.example.helloworld; + +import com.google.protobuf.util.JsonFormat; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Context; +import java.io.InputStreamReader; +import java.lang.Exception; +import java.nio.charset.StandardCharsets; + +@Path("/twirp/com.example.helloworld.HelloWorld") +public class RpcHelloWorldResource { + private final RpcHelloWorld service; + + public RpcHelloWorldResource(RpcHelloWorld service) { + this.service = service; + } + + @POST + @Path("/Hello") + public void handleHello(@Context HttpServletRequest request, + @Context HttpServletResponse response) throws Exception { + boolean json = false; + final Helloworld.HelloReq data; + if (request.getContentType().equals("application/protobuf")) { + data = Helloworld.HelloReq.parseFrom(request.getInputStream()); + } else if (request.getContentType().startsWith("application/json")) { + json = true; + Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + response.setStatus(415); + response.flushBuffer(); + return; + } + Helloworld.HelloResp retval = service.handleHello(data); + response.setStatus(200); + if (json) { + response.setContentType("application/json; charset=utf-8"); + response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); + } else { + response.setContentType("application/protobuf"); + retval.writeTo(response.getOutputStream()); + } + response.flushBuffer(); + } + + @POST + @Path("/HelloAgain") + public void handleHelloAgain(@Context HttpServletRequest request, + @Context HttpServletResponse response) throws Exception { + boolean json = false; + final Helloworld.HelloReq data; + if (request.getContentType().equals("application/protobuf")) { + data = Helloworld.HelloReq.parseFrom(request.getInputStream()); + } else if (request.getContentType().startsWith("application/json")) { + json = true; + Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + response.setStatus(415); + response.flushBuffer(); + return; + } + Helloworld.HelloResp retval = service.handleHelloAgain(data); + response.setStatus(200); + if (json) { + response.setContentType("application/json; charset=utf-8"); + response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); + } else { + response.setContentType("application/protobuf"); + retval.writeTo(response.getOutputStream()); + } + response.flushBuffer(); + } +} + diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/HelloworldGeneratorTest.test_GenerateWithRequest.approved.txt b/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/HelloworldGeneratorTest.test_GenerateWithRequest.approved.txt new file mode 100644 index 0000000..a787e36 --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/HelloworldGeneratorTest.test_GenerateWithRequest.approved.txt @@ -0,0 +1,95 @@ +[0] = package com.example.helloworld; + +import jakarta.servlet.http.HttpServletRequest; + +public interface RpcHelloWorld { + Helloworld.HelloResp handleHello(HttpServletRequest request, Helloworld.HelloReq in); + + Helloworld.HelloResp handleHelloAgain(HttpServletRequest request, Helloworld.HelloReq in); +} + +[1] = package com.example.helloworld; + +import com.google.protobuf.util.JsonFormat; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Context; +import java.io.InputStreamReader; +import java.lang.Exception; +import java.nio.charset.StandardCharsets; + +@Path("/twirp/com.example.helloworld.HelloWorld") +public class RpcHelloWorldResource { + private final RpcHelloWorld service; + + public RpcHelloWorldResource(RpcHelloWorld service) { + this.service = service; + } + + @POST + @Path("/Hello") + public void handleHello(@Context HttpServletRequest request, + @Context HttpServletResponse response) throws Exception { + boolean json = false; + final Helloworld.HelloReq data; + if (request.getContentType().equals("application/protobuf")) { + data = Helloworld.HelloReq.parseFrom(request.getInputStream()); + } else if (request.getContentType().startsWith("application/json")) { + json = true; + Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + response.setStatus(415); + response.flushBuffer(); + return; + } + Helloworld.HelloResp retval = service.handleHello(request, data); + response.setStatus(200); + if (json) { + response.setContentType("application/json; charset=utf-8"); + response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); + } else { + response.setContentType("application/protobuf"); + retval.writeTo(response.getOutputStream()); + } + response.flushBuffer(); + } + + @POST + @Path("/HelloAgain") + public void handleHelloAgain(@Context HttpServletRequest request, + @Context HttpServletResponse response) throws Exception { + boolean json = false; + final Helloworld.HelloReq data; + if (request.getContentType().equals("application/protobuf")) { + data = Helloworld.HelloReq.parseFrom(request.getInputStream()); + } else if (request.getContentType().startsWith("application/json")) { + json = true; + Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + response.setStatus(415); + response.flushBuffer(); + return; + } + Helloworld.HelloResp retval = service.handleHelloAgain(request, data); + response.setStatus(200); + if (json) { + response.setContentType("application/json; charset=utf-8"); + response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); + } else { + response.setContentType("application/protobuf"); + retval.writeTo(response.getOutputStream()); + } + response.flushBuffer(); + } +} + diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/StatusGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/StatusGeneratorTest.java new file mode 100644 index 0000000..07d3ffb --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/StatusGeneratorTest.java @@ -0,0 +1,36 @@ +package com.flit.protoc.gen.server.jakarta; + +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.flit.protoc.Plugin; +import com.flit.protoc.gen.BaseGeneratorTest; +import com.google.protobuf.compiler.PluginProtos; +import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File; +import org.approvaltests.Approvals; +import org.junit.jupiter.api.Test; + +/** + * Tests the generation of a service that has core definition imported from another file + */ +public class StatusGeneratorTest extends BaseGeneratorTest { + + @Test + public void test_Generate() throws Exception { + PluginProtos.CodeGeneratorRequest request = loadJson("status.jakarta.json"); + + Plugin plugin = new Plugin(request); + PluginProtos.CodeGeneratorResponse response = plugin.process(); + + assertNotNull(response); + assertEquals(2, response.getFileCount()); + + assertEquals(response.getFile(0).getName(), "com/example/helloworld/RpcStatus.java"); + assertEquals(response.getFile(1).getName(), "com/example/helloworld/RpcStatusResource.java"); + + Approvals.verifyAll("", response.getFileList().stream().map(File::getContent).collect(toList())); + response.getFileList().forEach(BaseGeneratorTest::assertParses); + } + +} diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/StatusGeneratorTest.test_Generate.approved.txt b/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/StatusGeneratorTest.test_Generate.approved.txt new file mode 100644 index 0000000..65361e6 --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/jakarta/StatusGeneratorTest.test_Generate.approved.txt @@ -0,0 +1,59 @@ +[0] = package com.example.helloworld; + +public interface RpcStatus { + StatusOuterClass.StatusResponse handleGetStatus(Core.Empty in); +} + +[1] = package com.example.helloworld; + +import com.google.protobuf.util.JsonFormat; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Context; +import java.io.InputStreamReader; +import java.lang.Exception; +import java.nio.charset.StandardCharsets; + +@Path("/twirp/com.example.helloworld.Status") +public class RpcStatusResource { + private final RpcStatus service; + + public RpcStatusResource(RpcStatus service) { + this.service = service; + } + + @POST + @Path("/GetStatus") + public void handleGetStatus(@Context HttpServletRequest request, + @Context HttpServletResponse response) throws Exception { + boolean json = false; + final Core.Empty data; + if (request.getContentType().equals("application/protobuf")) { + data = Core.Empty.parseFrom(request.getInputStream()); + } else if (request.getContentType().startsWith("application/json")) { + json = true; + Core.Empty.Builder builder = Core.Empty.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + response.setStatus(415); + response.flushBuffer(); + return; + } + StatusOuterClass.StatusResponse retval = service.handleGetStatus(data); + response.setStatus(200); + if (json) { + response.setContentType("application/json; charset=utf-8"); + response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); + } else { + response.setContentType("application/protobuf"); + retval.writeTo(response.getOutputStream()); + } + response.flushBuffer(); + } +} + diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/ContextGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/ContextGeneratorTest.java new file mode 100644 index 0000000..dab7a3b --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/ContextGeneratorTest.java @@ -0,0 +1,67 @@ +package com.flit.protoc.gen.server.jaxrs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.flit.protoc.Plugin; +import com.flit.protoc.gen.BaseGeneratorTest; +import com.google.protobuf.compiler.PluginProtos; +import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +/** + * Tests the generation of a service that has core definition imported from another file + */ +public class ContextGeneratorTest extends BaseGeneratorTest { + + @Test + public void test_GenerateWithMissingRoot() throws Exception { + test_Route("context.missing.jaxrs.json", "/twirp/com.example.context.NullService"); + } + + @Test + public void test_GenerateWithEmptyRoot() throws Exception { + test_Route("context.empty.jaxrs.json", "/twirp/com.example.context.NullService"); + } + + @Test + public void test_GenerateWithSlashOnlyRoot() throws Exception { + test_Route("context.slash.jaxrs.json", "/com.example.context.NullService"); + } + + @Test + public void test_GenerateWithSlashRoot() throws Exception { + test_Route("context.root.jaxrs.json", "/root/com.example.context.NullService"); + } + + @Test + public void test_GenerateWithNameRoot() throws Exception { + test_Route("context.name.jaxrs.json", "/fibble/com.example.context.NullService"); + } + + private void test_Route(String file, String route) throws Exception { + PluginProtos.CodeGeneratorRequest request = loadJson(file); + + Plugin plugin = new Plugin(request); + PluginProtos.CodeGeneratorResponse response = plugin.process(); + + assertNotNull(response); + assertEquals(2, response.getFileCount()); + + Map files = response.getFileList() + .stream() + .collect(Collectors + .toMap(PluginProtos.CodeGeneratorResponse.File::getName, Function.identity())); + + assertTrue(files.containsKey("com/example/context/rpc/RpcNullService.java")); + assertTrue(files.containsKey("com/example/context/rpc/RpcNullServiceResource.java")); + + assertTrue(files.get("com/example/context/rpc/RpcNullServiceResource.java") + .getContent() + .contains(String.format("@Path(\"%s\")", route))); + } +} diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/HelloworldGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/HelloworldGeneratorTest.java new file mode 100644 index 0000000..f5da202 --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/HelloworldGeneratorTest.java @@ -0,0 +1,47 @@ +package com.flit.protoc.gen.server.jaxrs; + +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.flit.protoc.Plugin; +import com.flit.protoc.gen.BaseGeneratorTest; +import com.google.protobuf.compiler.PluginProtos; +import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File; +import org.approvaltests.Approvals; +import org.junit.jupiter.api.Test; + +public class HelloworldGeneratorTest extends BaseGeneratorTest { + + @Test + public void test_Generate() throws Exception { + PluginProtos.CodeGeneratorRequest request = loadJson("helloworld.jaxrs.json"); + + Plugin plugin = new Plugin(request); + PluginProtos.CodeGeneratorResponse response = plugin.process(); + + assertNotNull(response); + assertEquals(2, response.getFileCount()); + assertEquals(response.getFile(0).getName(), "com/example/helloworld/RpcHelloWorld.java"); + assertEquals(response.getFile(1).getName(), "com/example/helloworld/RpcHelloWorldResource.java"); + + Approvals.verifyAll("", response.getFileList().stream().map(File::getContent).collect(toList())); + response.getFileList().forEach(BaseGeneratorTest::assertParses); + } + + @Test + public void test_GenerateWithRequest() throws Exception { + PluginProtos.CodeGeneratorRequest request = loadJson("helloworld.jaxrs.json", "target=server,type=jaxrs,request=HelloWorld"); + + Plugin plugin = new Plugin(request); + PluginProtos.CodeGeneratorResponse response = plugin.process(); + + assertNotNull(response); + assertEquals(2, response.getFileCount()); + assertEquals(response.getFile(0).getName(), "com/example/helloworld/RpcHelloWorld.java"); + assertEquals(response.getFile(1).getName(), "com/example/helloworld/RpcHelloWorldResource.java"); + + Approvals.verifyAll("", response.getFileList().stream().map(File::getContent).collect(toList())); + response.getFileList().forEach(BaseGeneratorTest::assertParses); + } +} diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/HelloworldGeneratorTest.test_Generate.approved.txt b/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/HelloworldGeneratorTest.test_Generate.approved.txt new file mode 100644 index 0000000..d39fd52 --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/HelloworldGeneratorTest.test_Generate.approved.txt @@ -0,0 +1,93 @@ +[0] = package com.example.helloworld; + +public interface RpcHelloWorld { + Helloworld.HelloResp handleHello(Helloworld.HelloReq in); + + Helloworld.HelloResp handleHelloAgain(Helloworld.HelloReq in); +} + +[1] = package com.example.helloworld; + +import com.google.protobuf.util.JsonFormat; +import java.io.InputStreamReader; +import java.lang.Exception; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.Context; + +@Path("/twirp/com.example.helloworld.HelloWorld") +public class RpcHelloWorldResource { + private final RpcHelloWorld service; + + public RpcHelloWorldResource(RpcHelloWorld service) { + this.service = service; + } + + @POST + @Path("/Hello") + public void handleHello(@Context HttpServletRequest request, + @Context HttpServletResponse response) throws Exception { + boolean json = false; + final Helloworld.HelloReq data; + if (request.getContentType().equals("application/protobuf")) { + data = Helloworld.HelloReq.parseFrom(request.getInputStream()); + } else if (request.getContentType().startsWith("application/json")) { + json = true; + Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + response.setStatus(415); + response.flushBuffer(); + return; + } + Helloworld.HelloResp retval = service.handleHello(data); + response.setStatus(200); + if (json) { + response.setContentType("application/json; charset=utf-8"); + response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); + } else { + response.setContentType("application/protobuf"); + retval.writeTo(response.getOutputStream()); + } + response.flushBuffer(); + } + + @POST + @Path("/HelloAgain") + public void handleHelloAgain(@Context HttpServletRequest request, + @Context HttpServletResponse response) throws Exception { + boolean json = false; + final Helloworld.HelloReq data; + if (request.getContentType().equals("application/protobuf")) { + data = Helloworld.HelloReq.parseFrom(request.getInputStream()); + } else if (request.getContentType().startsWith("application/json")) { + json = true; + Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + response.setStatus(415); + response.flushBuffer(); + return; + } + Helloworld.HelloResp retval = service.handleHelloAgain(data); + response.setStatus(200); + if (json) { + response.setContentType("application/json; charset=utf-8"); + response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); + } else { + response.setContentType("application/protobuf"); + retval.writeTo(response.getOutputStream()); + } + response.flushBuffer(); + } +} + diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/HelloworldGeneratorTest.test_GenerateWithRequest.approved.txt b/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/HelloworldGeneratorTest.test_GenerateWithRequest.approved.txt new file mode 100644 index 0000000..d882f8c --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/HelloworldGeneratorTest.test_GenerateWithRequest.approved.txt @@ -0,0 +1,95 @@ +[0] = package com.example.helloworld; + +import javax.servlet.http.HttpServletRequest; + +public interface RpcHelloWorld { + Helloworld.HelloResp handleHello(HttpServletRequest request, Helloworld.HelloReq in); + + Helloworld.HelloResp handleHelloAgain(HttpServletRequest request, Helloworld.HelloReq in); +} + +[1] = package com.example.helloworld; + +import com.google.protobuf.util.JsonFormat; +import java.io.InputStreamReader; +import java.lang.Exception; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.Context; + +@Path("/twirp/com.example.helloworld.HelloWorld") +public class RpcHelloWorldResource { + private final RpcHelloWorld service; + + public RpcHelloWorldResource(RpcHelloWorld service) { + this.service = service; + } + + @POST + @Path("/Hello") + public void handleHello(@Context HttpServletRequest request, + @Context HttpServletResponse response) throws Exception { + boolean json = false; + final Helloworld.HelloReq data; + if (request.getContentType().equals("application/protobuf")) { + data = Helloworld.HelloReq.parseFrom(request.getInputStream()); + } else if (request.getContentType().startsWith("application/json")) { + json = true; + Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + response.setStatus(415); + response.flushBuffer(); + return; + } + Helloworld.HelloResp retval = service.handleHello(request, data); + response.setStatus(200); + if (json) { + response.setContentType("application/json; charset=utf-8"); + response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); + } else { + response.setContentType("application/protobuf"); + retval.writeTo(response.getOutputStream()); + } + response.flushBuffer(); + } + + @POST + @Path("/HelloAgain") + public void handleHelloAgain(@Context HttpServletRequest request, + @Context HttpServletResponse response) throws Exception { + boolean json = false; + final Helloworld.HelloReq data; + if (request.getContentType().equals("application/protobuf")) { + data = Helloworld.HelloReq.parseFrom(request.getInputStream()); + } else if (request.getContentType().startsWith("application/json")) { + json = true; + Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + response.setStatus(415); + response.flushBuffer(); + return; + } + Helloworld.HelloResp retval = service.handleHelloAgain(request, data); + response.setStatus(200); + if (json) { + response.setContentType("application/json; charset=utf-8"); + response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); + } else { + response.setContentType("application/protobuf"); + retval.writeTo(response.getOutputStream()); + } + response.flushBuffer(); + } +} + diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/StatusGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/StatusGeneratorTest.java new file mode 100644 index 0000000..9b802b3 --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/StatusGeneratorTest.java @@ -0,0 +1,36 @@ +package com.flit.protoc.gen.server.jaxrs; + +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.flit.protoc.Plugin; +import com.flit.protoc.gen.BaseGeneratorTest; +import com.google.protobuf.compiler.PluginProtos; +import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.File; +import org.approvaltests.Approvals; +import org.junit.jupiter.api.Test; + +/** + * Tests the generation of a service that has core definition imported from another file + */ +public class StatusGeneratorTest extends BaseGeneratorTest { + + @Test + public void test_Generate() throws Exception { + PluginProtos.CodeGeneratorRequest request = loadJson("status.jaxrs.json"); + + Plugin plugin = new Plugin(request); + PluginProtos.CodeGeneratorResponse response = plugin.process(); + + assertNotNull(response); + assertEquals(2, response.getFileCount()); + + assertEquals(response.getFile(0).getName(), "com/example/helloworld/RpcStatus.java"); + assertEquals(response.getFile(1).getName(), "com/example/helloworld/RpcStatusResource.java"); + + Approvals.verifyAll("", response.getFileList().stream().map(File::getContent).collect(toList())); + response.getFileList().forEach(BaseGeneratorTest::assertParses); + } + +} diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/StatusGeneratorTest.test_Generate.approved.txt b/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/StatusGeneratorTest.test_Generate.approved.txt new file mode 100644 index 0000000..8b5157d --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/jaxrs/StatusGeneratorTest.test_Generate.approved.txt @@ -0,0 +1,59 @@ +[0] = package com.example.helloworld; + +public interface RpcStatus { + StatusOuterClass.StatusResponse handleGetStatus(Core.Empty in); +} + +[1] = package com.example.helloworld; + +import com.google.protobuf.util.JsonFormat; +import java.io.InputStreamReader; +import java.lang.Exception; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.Context; + +@Path("/twirp/com.example.helloworld.Status") +public class RpcStatusResource { + private final RpcStatus service; + + public RpcStatusResource(RpcStatus service) { + this.service = service; + } + + @POST + @Path("/GetStatus") + public void handleGetStatus(@Context HttpServletRequest request, + @Context HttpServletResponse response) throws Exception { + boolean json = false; + final Core.Empty data; + if (request.getContentType().equals("application/protobuf")) { + data = Core.Empty.parseFrom(request.getInputStream()); + } else if (request.getContentType().startsWith("application/json")) { + json = true; + Core.Empty.Builder builder = Core.Empty.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + response.setStatus(415); + response.flushBuffer(); + return; + } + StatusOuterClass.StatusResponse retval = service.handleGetStatus(data); + response.setStatus(200); + if (json) { + response.setContentType("application/json; charset=utf-8"); + response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); + } else { + response.setContentType("application/protobuf"); + retval.writeTo(response.getOutputStream()); + } + response.flushBuffer(); + } +} + diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/spring/ContextGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/spring/ContextGeneratorTest.java index b16a601..06fb89b 100644 --- a/plugin/src/test/java/com/flit/protoc/gen/server/spring/ContextGeneratorTest.java +++ b/plugin/src/test/java/com/flit/protoc/gen/server/spring/ContextGeneratorTest.java @@ -1,38 +1,44 @@ package com.flit.protoc.gen.server.spring; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.flit.protoc.Plugin; import com.flit.protoc.gen.BaseGeneratorTest; import com.google.protobuf.compiler.PluginProtos; -import org.junit.Test; - import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; - -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; /** * Tests the generation of a service that has core definition imported from another file */ public class ContextGeneratorTest extends BaseGeneratorTest { - @Test public void test_GenerateWithMissingRoot() throws Exception { + @Test + public void test_GenerateWithMissingRoot() throws Exception { test_Route("context.missing.spring.json", "/twirp/com.example.context.NullService/SayNull"); } - @Test public void test_GenerateWithEmptyRoot() throws Exception { + @Test + public void test_GenerateWithEmptyRoot() throws Exception { test_Route("context.empty.spring.json", "/twirp/com.example.context.NullService/SayNull"); } - @Test public void test_GenerateWithSlashOnlyRoot() throws Exception { + @Test + public void test_GenerateWithSlashOnlyRoot() throws Exception { test_Route("context.slash.spring.json", "/com.example.context.NullService/SayNull"); } - @Test public void test_GenerateWithSlashRoot() throws Exception { + @Test + public void test_GenerateWithSlashRoot() throws Exception { test_Route("context.root.spring.json", "/root/com.example.context.NullService/SayNull"); } - @Test public void test_GenerateWithNameRoot() throws Exception { + @Test + public void test_GenerateWithNameRoot() throws Exception { test_Route("context.name.spring.json", "/fibble/com.example.context.NullService/SayNull"); } diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/spring/HelloworldGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/spring/HelloworldGeneratorTest.java index 1ad9a37..5fcdba2 100644 --- a/plugin/src/test/java/com/flit/protoc/gen/server/spring/HelloworldGeneratorTest.java +++ b/plugin/src/test/java/com/flit/protoc/gen/server/spring/HelloworldGeneratorTest.java @@ -1,18 +1,19 @@ package com.flit.protoc.gen.server.spring; +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import com.flit.protoc.Plugin; import com.flit.protoc.gen.BaseGeneratorTest; import com.google.protobuf.compiler.PluginProtos; import org.approvaltests.Approvals; -import org.junit.Test; - -import static java.util.stream.Collectors.toList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import org.junit.jupiter.api.Test; public class HelloworldGeneratorTest extends BaseGeneratorTest { - @Test public void test_Generate() throws Exception { + @Test + public void test_Generate() throws Exception { PluginProtos.CodeGeneratorRequest request = loadJson("helloworld.spring.json"); Plugin plugin = new Plugin(request); @@ -27,4 +28,20 @@ public class HelloworldGeneratorTest extends BaseGeneratorTest { response.getFileList().forEach(f -> assertParses(f)); } + @Test + public void test_GenerateWithRequest() throws Exception { + PluginProtos.CodeGeneratorRequest request = loadJson("helloworld.spring.json", "target=server,type=spring,request=HelloWorld"); + + Plugin plugin = new Plugin(request); + PluginProtos.CodeGeneratorResponse response = plugin.process(); + + assertNotNull(response); + assertEquals(2, response.getFileCount()); + assertEquals(response.getFile(0).getName(), "com/example/helloworld/RpcHelloWorld.java"); + assertEquals(response.getFile(1).getName(), "com/example/helloworld/RpcHelloWorldController.java"); + + Approvals.verifyAll("", response.getFileList().stream().map(f -> f.getContent()).collect(toList())); + response.getFileList().forEach(f -> assertParses(f)); + } + } diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/spring/HelloworldGeneratorTest.test_Generate.approved.txt b/plugin/src/test/java/com/flit/protoc/gen/server/spring/HelloworldGeneratorTest.test_Generate.approved.txt index 273bcad..ee260ea 100644 --- a/plugin/src/test/java/com/flit/protoc/gen/server/spring/HelloworldGeneratorTest.test_Generate.approved.txt +++ b/plugin/src/test/java/com/flit/protoc/gen/server/spring/HelloworldGeneratorTest.test_Generate.approved.txt @@ -9,11 +9,11 @@ public interface RpcHelloWorld { [1] = package com.example.helloworld; import com.google.protobuf.util.JsonFormat; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.InputStreamReader; import java.lang.Exception; import java.nio.charset.StandardCharsets; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @@ -33,7 +33,9 @@ public class RpcHelloWorldController { } else if (request.getContentType().startsWith("application/json")) { json = true; Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); - JsonFormat.parser().merge(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8), builder); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } data = builder.build(); } else { response.setStatus(415); @@ -42,7 +44,7 @@ public class RpcHelloWorldController { Helloworld.HelloResp retval = service.handleHello(data); response.setStatus(200); if (json) { - response.setContentType("application/json;charset=UTF-8"); + response.setContentType("application/json; charset=utf-8"); response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); } else { response.setContentType("application/protobuf"); @@ -60,7 +62,9 @@ public class RpcHelloWorldController { } else if (request.getContentType().startsWith("application/json")) { json = true; Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); - JsonFormat.parser().merge(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8), builder); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } data = builder.build(); } else { response.setStatus(415); @@ -69,7 +73,7 @@ public class RpcHelloWorldController { Helloworld.HelloResp retval = service.handleHelloAgain(data); response.setStatus(200); if (json) { - response.setContentType("application/json;charset=UTF-8"); + response.setContentType("application/json; charset=utf-8"); response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); } else { response.setContentType("application/protobuf"); diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/spring/HelloworldGeneratorTest.test_GenerateWithRequest.approved.txt b/plugin/src/test/java/com/flit/protoc/gen/server/spring/HelloworldGeneratorTest.test_GenerateWithRequest.approved.txt new file mode 100644 index 0000000..3589ff7 --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/spring/HelloworldGeneratorTest.test_GenerateWithRequest.approved.txt @@ -0,0 +1,86 @@ +[0] = package com.example.helloworld; + +import jakarta.servlet.http.HttpServletRequest; + +public interface RpcHelloWorld { + Helloworld.HelloResp handleHello(HttpServletRequest request, Helloworld.HelloReq in); + + Helloworld.HelloResp handleHelloAgain(HttpServletRequest request, Helloworld.HelloReq in); +} + +[1] = package com.example.helloworld; + +import com.google.protobuf.util.JsonFormat; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.InputStreamReader; +import java.lang.Exception; +import java.nio.charset.StandardCharsets; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class RpcHelloWorldController { + @Autowired + private RpcHelloWorld service; + + @PostMapping("/twirp/com.example.helloworld.HelloWorld/Hello") + public void handleHello(HttpServletRequest request, HttpServletResponse response) throws + Exception { + boolean json = false; + final Helloworld.HelloReq data; + if (request.getContentType().equals("application/protobuf")) { + data = Helloworld.HelloReq.parseFrom(request.getInputStream()); + } else if (request.getContentType().startsWith("application/json")) { + json = true; + Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + response.setStatus(415); + return; + } + Helloworld.HelloResp retval = service.handleHello(request, data); + response.setStatus(200); + if (json) { + response.setContentType("application/json; charset=utf-8"); + response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); + } else { + response.setContentType("application/protobuf"); + retval.writeTo(response.getOutputStream()); + } + } + + @PostMapping("/twirp/com.example.helloworld.HelloWorld/HelloAgain") + public void handleHelloAgain(HttpServletRequest request, HttpServletResponse response) throws + Exception { + boolean json = false; + final Helloworld.HelloReq data; + if (request.getContentType().equals("application/protobuf")) { + data = Helloworld.HelloReq.parseFrom(request.getInputStream()); + } else if (request.getContentType().startsWith("application/json")) { + json = true; + Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + response.setStatus(415); + return; + } + Helloworld.HelloResp retval = service.handleHelloAgain(request, data); + response.setStatus(200); + if (json) { + response.setContentType("application/json; charset=utf-8"); + response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); + } else { + response.setContentType("application/protobuf"); + retval.writeTo(response.getOutputStream()); + } + } +} + diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/spring/StatusGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/spring/StatusGeneratorTest.java index 690ec1f..924f122 100644 --- a/plugin/src/test/java/com/flit/protoc/gen/server/spring/StatusGeneratorTest.java +++ b/plugin/src/test/java/com/flit/protoc/gen/server/spring/StatusGeneratorTest.java @@ -1,21 +1,22 @@ package com.flit.protoc.gen.server.spring; +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import com.flit.protoc.Plugin; import com.flit.protoc.gen.BaseGeneratorTest; import com.google.protobuf.compiler.PluginProtos; import org.approvaltests.Approvals; -import org.junit.Test; - -import static java.util.stream.Collectors.toList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import org.junit.jupiter.api.Test; /** * Tests the generation of a service that has core definition imported from another file */ public class StatusGeneratorTest extends BaseGeneratorTest { - @Test public void test_Generate() throws Exception { + @Test + public void test_Generate() throws Exception { PluginProtos.CodeGeneratorRequest request = loadJson("status.spring.json"); Plugin plugin = new Plugin(request); diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/spring/StatusGeneratorTest.test_Generate.approved.txt b/plugin/src/test/java/com/flit/protoc/gen/server/spring/StatusGeneratorTest.test_Generate.approved.txt index de9ed27..db9111e 100644 --- a/plugin/src/test/java/com/flit/protoc/gen/server/spring/StatusGeneratorTest.test_Generate.approved.txt +++ b/plugin/src/test/java/com/flit/protoc/gen/server/spring/StatusGeneratorTest.test_Generate.approved.txt @@ -7,11 +7,11 @@ public interface RpcStatus { [1] = package com.example.helloworld; import com.google.protobuf.util.JsonFormat; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.InputStreamReader; import java.lang.Exception; import java.nio.charset.StandardCharsets; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @@ -31,7 +31,9 @@ public class RpcStatusController { } else if (request.getContentType().startsWith("application/json")) { json = true; Core.Empty.Builder builder = Core.Empty.newBuilder(); - JsonFormat.parser().merge(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8), builder); + try (InputStreamReader reader = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } data = builder.build(); } else { response.setStatus(415); @@ -40,7 +42,7 @@ public class RpcStatusController { StatusOuterClass.StatusResponse retval = service.handleGetStatus(data); response.setStatus(200); if (json) { - response.setContentType("application/json;charset=UTF-8"); + response.setContentType("application/json; charset=utf-8"); response.getOutputStream().write(JsonFormat.printer().omittingInsignificantWhitespace().print(retval).getBytes(StandardCharsets.UTF_8)); } else { response.setContentType("application/protobuf"); diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/undertow/ContextGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/undertow/ContextGeneratorTest.java index cb835d4..64b1bbc 100644 --- a/plugin/src/test/java/com/flit/protoc/gen/server/undertow/ContextGeneratorTest.java +++ b/plugin/src/test/java/com/flit/protoc/gen/server/undertow/ContextGeneratorTest.java @@ -1,38 +1,44 @@ package com.flit.protoc.gen.server.undertow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.flit.protoc.Plugin; import com.flit.protoc.gen.BaseGeneratorTest; import com.google.protobuf.compiler.PluginProtos; -import org.junit.Test; - import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; - -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; /** * Tests the generation of a service that has core definition imported from another file */ public class ContextGeneratorTest extends BaseGeneratorTest { - @Test public void test_GenerateWithMissingRoot() throws Exception { + @Test + public void test_GenerateWithMissingRoot() throws Exception { test_Route("context.missing.undertow.json", "/twirp/com.example.context.NullService"); } - @Test public void test_GenerateWithEmptyRoot() throws Exception { + @Test + public void test_GenerateWithEmptyRoot() throws Exception { test_Route("context.empty.undertow.json", "/twirp/com.example.context.NullService"); } - @Test public void test_GenerateWithSlashOnlyRoot() throws Exception { + @Test + public void test_GenerateWithSlashOnlyRoot() throws Exception { test_Route("context.slash.undertow.json", "/com.example.context.NullService"); } - @Test public void test_GenerateWithSlashRoot() throws Exception { + @Test + public void test_GenerateWithSlashRoot() throws Exception { test_Route("context.root.undertow.json", "/root/com.example.context.NullService"); } - @Test public void test_GenerateWithNameRoot() throws Exception { + @Test + public void test_GenerateWithNameRoot() throws Exception { test_Route("context.name.undertow.json", "/fibble/com.example.context.NullService"); } diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/undertow/HelloworldGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/undertow/HelloworldGeneratorTest.java index d45c36c..e0eaee3 100644 --- a/plugin/src/test/java/com/flit/protoc/gen/server/undertow/HelloworldGeneratorTest.java +++ b/plugin/src/test/java/com/flit/protoc/gen/server/undertow/HelloworldGeneratorTest.java @@ -1,18 +1,19 @@ package com.flit.protoc.gen.server.undertow; +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import com.flit.protoc.Plugin; import com.flit.protoc.gen.BaseGeneratorTest; import com.google.protobuf.compiler.PluginProtos; import org.approvaltests.Approvals; -import org.junit.Test; - -import static java.util.stream.Collectors.toList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import org.junit.jupiter.api.Test; public class HelloworldGeneratorTest extends BaseGeneratorTest { - @Test public void test_Generate() throws Exception { + @Test + public void test_Generate() throws Exception { PluginProtos.CodeGeneratorRequest request = loadJson("helloworld.undertow.json"); Plugin plugin = new Plugin(request); @@ -27,4 +28,20 @@ public class HelloworldGeneratorTest extends BaseGeneratorTest { response.getFileList().forEach(f -> assertParses(f)); } + @Test + public void test_GenerateWithRequest() throws Exception { + PluginProtos.CodeGeneratorRequest request = loadJson("helloworld.undertow.json", "target=server,type=undertow,request=HelloWorld"); + + Plugin plugin = new Plugin(request); + PluginProtos.CodeGeneratorResponse response = plugin.process(); + + assertNotNull(response); + assertEquals(2, response.getFileCount()); + assertEquals(response.getFile(0).getName(), "com/example/helloworld/RpcHelloWorld.java"); + assertEquals(response.getFile(1).getName(), "com/example/helloworld/RpcHelloWorldHandler.java"); + + Approvals.verifyAll("", response.getFileList().stream().map(f -> f.getContent()).collect(toList())); + response.getFileList().forEach(f -> assertParses(f)); + } + } diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/undertow/HelloworldGeneratorTest.test_Generate.approved.txt b/plugin/src/test/java/com/flit/protoc/gen/server/undertow/HelloworldGeneratorTest.test_Generate.approved.txt index e286593..0db3aa3 100644 --- a/plugin/src/test/java/com/flit/protoc/gen/server/undertow/HelloworldGeneratorTest.test_Generate.approved.txt +++ b/plugin/src/test/java/com/flit/protoc/gen/server/undertow/HelloworldGeneratorTest.test_Generate.approved.txt @@ -69,7 +69,9 @@ public class RpcHelloWorldHandler implements HttpHandler { } else if (contentType.startsWith("application/json")) { json = true; Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); - JsonFormat.parser().merge(new InputStreamReader(exchange.getInputStream(), StandardCharsets.UTF_8), builder); + try (InputStreamReader reader = new InputStreamReader(exchange.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } data = builder.build(); } else { exchange.setStatusCode(415); @@ -78,7 +80,7 @@ public class RpcHelloWorldHandler implements HttpHandler { Helloworld.HelloResp response = service.handleHello(data); exchange.setStatusCode(200); if (json) { - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json;charset=UTF-8"); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json; charset=utf-8"); exchange.getResponseSender().send(JsonFormat.printer().omittingInsignificantWhitespace().print(response)); } else { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/protobuf"); @@ -95,7 +97,9 @@ public class RpcHelloWorldHandler implements HttpHandler { } else if (contentType.startsWith("application/json")) { json = true; Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); - JsonFormat.parser().merge(new InputStreamReader(exchange.getInputStream(), StandardCharsets.UTF_8), builder); + try (InputStreamReader reader = new InputStreamReader(exchange.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } data = builder.build(); } else { exchange.setStatusCode(415); @@ -104,7 +108,7 @@ public class RpcHelloWorldHandler implements HttpHandler { Helloworld.HelloResp response = service.handleHelloAgain(data); exchange.setStatusCode(200); if (json) { - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json;charset=UTF-8"); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json; charset=utf-8"); exchange.getResponseSender().send(JsonFormat.printer().omittingInsignificantWhitespace().print(response)); } else { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/protobuf"); diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/undertow/HelloworldGeneratorTest.test_GenerateWithRequest.approved.txt b/plugin/src/test/java/com/flit/protoc/gen/server/undertow/HelloworldGeneratorTest.test_GenerateWithRequest.approved.txt new file mode 100644 index 0000000..760a38e --- /dev/null +++ b/plugin/src/test/java/com/flit/protoc/gen/server/undertow/HelloworldGeneratorTest.test_GenerateWithRequest.approved.txt @@ -0,0 +1,121 @@ +[0] = package com.example.helloworld; + +import io.undertow.server.HttpServerExchange; + +public interface RpcHelloWorld { + Helloworld.HelloResp handleHello(HttpServerExchange request, Helloworld.HelloReq in); + + Helloworld.HelloResp handleHelloAgain(HttpServerExchange request, Helloworld.HelloReq in); +} + +[1] = package com.example.helloworld; + +import com.flit.runtime.ErrorCode; +import com.flit.runtime.FlitException; +import com.flit.runtime.undertow.ErrorWriter; +import com.flit.runtime.undertow.FlitHandler; +import com.google.protobuf.util.JsonFormat; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.Headers; +import java.io.InputStreamReader; +import java.lang.Exception; +import java.lang.Override; +import java.lang.String; +import java.nio.charset.StandardCharsets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RpcHelloWorldHandler implements HttpHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(RpcHelloWorldHandler.class); + + public static final String ROUTE = "/twirp/com.example.helloworld.HelloWorld"; + + private final RpcHelloWorld service; + + private final ErrorWriter errorWriter; + + public RpcHelloWorldHandler(RpcHelloWorld service) { + this.service = service; + this.errorWriter = new ErrorWriter(); + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + if (exchange.isInIoThread()) { + exchange.dispatch(this); + return; + } + exchange.startBlocking(); + String method = exchange.getAttachment(FlitHandler.KEY_METHOD); + try { + switch (method) { + case "Hello": handleHello(exchange); break; + case "HelloAgain": handleHelloAgain(exchange); break; + default: throw FlitException.builder().withErrorCode(ErrorCode.BAD_ROUTE).withMessage("No such route").build(); + } + } catch (FlitException e) { + errorWriter.write(e, exchange); + } catch (Exception e) { + LOGGER.error("Exception caught at handler: {}", e.getMessage(), e); + errorWriter.write(e, exchange); + } + } + + private void handleHello(HttpServerExchange exchange) throws Exception { + boolean json = false; + final Helloworld.HelloReq data; + final String contentType = exchange.getRequestHeaders().get(Headers.CONTENT_TYPE).getFirst(); + if (contentType.equals("application/protobuf")) { + data = Helloworld.HelloReq.parseFrom(exchange.getInputStream()); + } else if (contentType.startsWith("application/json")) { + json = true; + Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(exchange.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + exchange.setStatusCode(415); + return; + } + Helloworld.HelloResp response = service.handleHello(exchange, data); + exchange.setStatusCode(200); + if (json) { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json; charset=utf-8"); + exchange.getResponseSender().send(JsonFormat.printer().omittingInsignificantWhitespace().print(response)); + } else { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/protobuf"); + response.writeTo(exchange.getOutputStream()); + } + } + + private void handleHelloAgain(HttpServerExchange exchange) throws Exception { + boolean json = false; + final Helloworld.HelloReq data; + final String contentType = exchange.getRequestHeaders().get(Headers.CONTENT_TYPE).getFirst(); + if (contentType.equals("application/protobuf")) { + data = Helloworld.HelloReq.parseFrom(exchange.getInputStream()); + } else if (contentType.startsWith("application/json")) { + json = true; + Helloworld.HelloReq.Builder builder = Helloworld.HelloReq.newBuilder(); + try (InputStreamReader reader = new InputStreamReader(exchange.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } + data = builder.build(); + } else { + exchange.setStatusCode(415); + return; + } + Helloworld.HelloResp response = service.handleHelloAgain(exchange, data); + exchange.setStatusCode(200); + if (json) { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json; charset=utf-8"); + exchange.getResponseSender().send(JsonFormat.printer().omittingInsignificantWhitespace().print(response)); + } else { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/protobuf"); + response.writeTo(exchange.getOutputStream()); + } + } +} + diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/undertow/StatusGeneratorTest.java b/plugin/src/test/java/com/flit/protoc/gen/server/undertow/StatusGeneratorTest.java index 706ffc3..91ebad4 100644 --- a/plugin/src/test/java/com/flit/protoc/gen/server/undertow/StatusGeneratorTest.java +++ b/plugin/src/test/java/com/flit/protoc/gen/server/undertow/StatusGeneratorTest.java @@ -1,21 +1,22 @@ package com.flit.protoc.gen.server.undertow; +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import com.flit.protoc.Plugin; import com.flit.protoc.gen.BaseGeneratorTest; import com.google.protobuf.compiler.PluginProtos; import org.approvaltests.Approvals; -import org.junit.Test; - -import static java.util.stream.Collectors.toList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import org.junit.jupiter.api.Test; /** * Tests the generation of a service that has core definition imported from another file */ public class StatusGeneratorTest extends BaseGeneratorTest { - @Test public void test_Generate() throws Exception { + @Test + public void test_Generate() throws Exception { PluginProtos.CodeGeneratorRequest request = loadJson("status.undertow.json"); Plugin plugin = new Plugin(request); diff --git a/plugin/src/test/java/com/flit/protoc/gen/server/undertow/StatusGeneratorTest.test_Generate.approved.txt b/plugin/src/test/java/com/flit/protoc/gen/server/undertow/StatusGeneratorTest.test_Generate.approved.txt index 65f5b4d..2784862 100644 --- a/plugin/src/test/java/com/flit/protoc/gen/server/undertow/StatusGeneratorTest.test_Generate.approved.txt +++ b/plugin/src/test/java/com/flit/protoc/gen/server/undertow/StatusGeneratorTest.test_Generate.approved.txt @@ -66,7 +66,9 @@ public class RpcStatusHandler implements HttpHandler { } else if (contentType.startsWith("application/json")) { json = true; Core.Empty.Builder builder = Core.Empty.newBuilder(); - JsonFormat.parser().merge(new InputStreamReader(exchange.getInputStream(), StandardCharsets.UTF_8), builder); + try (InputStreamReader reader = new InputStreamReader(exchange.getInputStream(), StandardCharsets.UTF_8)) { + JsonFormat.parser().merge(reader, builder); + } data = builder.build(); } else { exchange.setStatusCode(415); @@ -75,7 +77,7 @@ public class RpcStatusHandler implements HttpHandler { StatusOuterClass.StatusResponse response = service.handleGetStatus(data); exchange.setStatusCode(200); if (json) { - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json;charset=UTF-8"); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json; charset=utf-8"); exchange.getResponseSender().send(JsonFormat.printer().omittingInsignificantWhitespace().print(response)); } else { exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/protobuf"); diff --git a/plugin/src/test/resources/context.empty.jakarta.json b/plugin/src/test/resources/context.empty.jakarta.json new file mode 100644 index 0000000..08e10d9 --- /dev/null +++ b/plugin/src/test/resources/context.empty.jakarta.json @@ -0,0 +1,29 @@ +{ + "fileToGenerate": ["context.proto"], + "parameter": "target=server,type=jakarta,context=", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "context.proto", + "package": "com.example.context", + "messageType": [{ + "name": "Empty" + }], + "service": [{ + "name": "NullService", + "method": [{ + "name": "SayNull", + "inputType": ".com.example.context.Empty", + "outputType": ".com.example.context.Empty" + }] + }], + "options": { + "javaPackage": "com.example.context.rpc" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/context.empty.jaxrs.json b/plugin/src/test/resources/context.empty.jaxrs.json new file mode 100644 index 0000000..a014f19 --- /dev/null +++ b/plugin/src/test/resources/context.empty.jaxrs.json @@ -0,0 +1,29 @@ +{ + "fileToGenerate": ["context.proto"], + "parameter": "target=server,type=jaxrs,context=", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "context.proto", + "package": "com.example.context", + "messageType": [{ + "name": "Empty" + }], + "service": [{ + "name": "NullService", + "method": [{ + "name": "SayNull", + "inputType": ".com.example.context.Empty", + "outputType": ".com.example.context.Empty" + }] + }], + "options": { + "javaPackage": "com.example.context.rpc" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/context.missing.jakarta.json b/plugin/src/test/resources/context.missing.jakarta.json new file mode 100644 index 0000000..08e10d9 --- /dev/null +++ b/plugin/src/test/resources/context.missing.jakarta.json @@ -0,0 +1,29 @@ +{ + "fileToGenerate": ["context.proto"], + "parameter": "target=server,type=jakarta,context=", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "context.proto", + "package": "com.example.context", + "messageType": [{ + "name": "Empty" + }], + "service": [{ + "name": "NullService", + "method": [{ + "name": "SayNull", + "inputType": ".com.example.context.Empty", + "outputType": ".com.example.context.Empty" + }] + }], + "options": { + "javaPackage": "com.example.context.rpc" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/context.missing.jaxrs.json b/plugin/src/test/resources/context.missing.jaxrs.json new file mode 100644 index 0000000..a014f19 --- /dev/null +++ b/plugin/src/test/resources/context.missing.jaxrs.json @@ -0,0 +1,29 @@ +{ + "fileToGenerate": ["context.proto"], + "parameter": "target=server,type=jaxrs,context=", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "context.proto", + "package": "com.example.context", + "messageType": [{ + "name": "Empty" + }], + "service": [{ + "name": "NullService", + "method": [{ + "name": "SayNull", + "inputType": ".com.example.context.Empty", + "outputType": ".com.example.context.Empty" + }] + }], + "options": { + "javaPackage": "com.example.context.rpc" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/context.name.jakarta.json b/plugin/src/test/resources/context.name.jakarta.json new file mode 100644 index 0000000..c5941cf --- /dev/null +++ b/plugin/src/test/resources/context.name.jakarta.json @@ -0,0 +1,29 @@ +{ + "fileToGenerate": ["context.proto"], + "parameter": "target=server,type=jakarta,context=fibble", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "context.proto", + "package": "com.example.context", + "messageType": [{ + "name": "Empty" + }], + "service": [{ + "name": "NullService", + "method": [{ + "name": "SayNull", + "inputType": ".com.example.context.Empty", + "outputType": ".com.example.context.Empty" + }] + }], + "options": { + "javaPackage": "com.example.context.rpc" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/context.name.jaxrs.json b/plugin/src/test/resources/context.name.jaxrs.json new file mode 100644 index 0000000..ca6e699 --- /dev/null +++ b/plugin/src/test/resources/context.name.jaxrs.json @@ -0,0 +1,29 @@ +{ + "fileToGenerate": ["context.proto"], + "parameter": "target=server,type=jaxrs,context=fibble", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "context.proto", + "package": "com.example.context", + "messageType": [{ + "name": "Empty" + }], + "service": [{ + "name": "NullService", + "method": [{ + "name": "SayNull", + "inputType": ".com.example.context.Empty", + "outputType": ".com.example.context.Empty" + }] + }], + "options": { + "javaPackage": "com.example.context.rpc" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/context.root.jakarta.json b/plugin/src/test/resources/context.root.jakarta.json new file mode 100644 index 0000000..2e00fdb --- /dev/null +++ b/plugin/src/test/resources/context.root.jakarta.json @@ -0,0 +1,29 @@ +{ + "fileToGenerate": ["context.proto"], + "parameter": "target=server,type=jakarta,context=/root", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "context.proto", + "package": "com.example.context", + "messageType": [{ + "name": "Empty" + }], + "service": [{ + "name": "NullService", + "method": [{ + "name": "SayNull", + "inputType": ".com.example.context.Empty", + "outputType": ".com.example.context.Empty" + }] + }], + "options": { + "javaPackage": "com.example.context.rpc" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/context.root.jaxrs.json b/plugin/src/test/resources/context.root.jaxrs.json new file mode 100644 index 0000000..b8bf8cc --- /dev/null +++ b/plugin/src/test/resources/context.root.jaxrs.json @@ -0,0 +1,29 @@ +{ + "fileToGenerate": ["context.proto"], + "parameter": "target=server,type=jaxrs,context=/root", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "context.proto", + "package": "com.example.context", + "messageType": [{ + "name": "Empty" + }], + "service": [{ + "name": "NullService", + "method": [{ + "name": "SayNull", + "inputType": ".com.example.context.Empty", + "outputType": ".com.example.context.Empty" + }] + }], + "options": { + "javaPackage": "com.example.context.rpc" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/context.slash.jakarta.json b/plugin/src/test/resources/context.slash.jakarta.json new file mode 100644 index 0000000..45bbad1 --- /dev/null +++ b/plugin/src/test/resources/context.slash.jakarta.json @@ -0,0 +1,29 @@ +{ + "fileToGenerate": ["context.proto"], + "parameter": "target=server,type=jakarta,context=/", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "context.proto", + "package": "com.example.context", + "messageType": [{ + "name": "Empty" + }], + "service": [{ + "name": "NullService", + "method": [{ + "name": "SayNull", + "inputType": ".com.example.context.Empty", + "outputType": ".com.example.context.Empty" + }] + }], + "options": { + "javaPackage": "com.example.context.rpc" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/context.slash.jaxrs.json b/plugin/src/test/resources/context.slash.jaxrs.json new file mode 100644 index 0000000..ff61342 --- /dev/null +++ b/plugin/src/test/resources/context.slash.jaxrs.json @@ -0,0 +1,29 @@ +{ + "fileToGenerate": ["context.proto"], + "parameter": "target=server,type=jaxrs,context=/", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "context.proto", + "package": "com.example.context", + "messageType": [{ + "name": "Empty" + }], + "service": [{ + "name": "NullService", + "method": [{ + "name": "SayNull", + "inputType": ".com.example.context.Empty", + "outputType": ".com.example.context.Empty" + }] + }], + "options": { + "javaPackage": "com.example.context.rpc" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/helloworld.jakarta.json b/plugin/src/test/resources/helloworld.jakarta.json new file mode 100644 index 0000000..36986ab --- /dev/null +++ b/plugin/src/test/resources/helloworld.jakarta.json @@ -0,0 +1,49 @@ +{ + "fileToGenerate": ["helloworld.proto"], + "parameter": "target=server,type=jakarta", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "helloworld.proto", + "package": "com.example.helloworld", + "messageType": [{ + "name": "HelloReq", + "field": [{ + "name": "subject", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_STRING", + "jsonName": "subject" + }] + }, { + "name": "HelloResp", + "field": [{ + "name": "text", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_STRING", + "jsonName": "text" + }] + }], + "service": [{ + "name": "HelloWorld", + "method": [{ + "name": "Hello", + "inputType": ".com.example.helloworld.HelloReq", + "outputType": ".com.example.helloworld.HelloResp" + }, { + "name": "HelloAgain", + "inputType": ".com.example.helloworld.HelloReq", + "outputType": ".com.example.helloworld.HelloResp" + }] + }], + "options": { + "javaPackage": "com.example.helloworld" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/helloworld.jaxrs.json b/plugin/src/test/resources/helloworld.jaxrs.json new file mode 100644 index 0000000..0e5478e --- /dev/null +++ b/plugin/src/test/resources/helloworld.jaxrs.json @@ -0,0 +1,49 @@ +{ + "fileToGenerate": ["helloworld.proto"], + "parameter": "target=server,type=jaxrs", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "helloworld.proto", + "package": "com.example.helloworld", + "messageType": [{ + "name": "HelloReq", + "field": [{ + "name": "subject", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_STRING", + "jsonName": "subject" + }] + }, { + "name": "HelloResp", + "field": [{ + "name": "text", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_STRING", + "jsonName": "text" + }] + }], + "service": [{ + "name": "HelloWorld", + "method": [{ + "name": "Hello", + "inputType": ".com.example.helloworld.HelloReq", + "outputType": ".com.example.helloworld.HelloResp" + }, { + "name": "HelloAgain", + "inputType": ".com.example.helloworld.HelloReq", + "outputType": ".com.example.helloworld.HelloResp" + }] + }], + "options": { + "javaPackage": "com.example.helloworld" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/status.jakarta.json b/plugin/src/test/resources/status.jakarta.json new file mode 100644 index 0000000..2bd0ee0 --- /dev/null +++ b/plugin/src/test/resources/status.jakarta.json @@ -0,0 +1,79 @@ +{ + "fileToGenerate": ["core.proto", "status.proto"], + "parameter": "target=server,type=jakarta", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "core.proto", + "package": "com.example.helloworld", + "messageType": [{ + "name": "Empty" + }], + "options": { + "javaPackage": "com.example.helloworld" + }, + "syntax": "proto3" + }, { + "name": "status.proto", + "package": "com.example.helloworld", + "dependency": ["core.proto"], + "messageType": [{ + "name": "StatusResponse", + "field": [{ + "name": "status", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_ENUM", + "typeName": ".com.example.helloworld.StatusResponse.StatusType", + "jsonName": "status" + }, { + "name": "sha", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_STRING", + "jsonName": "sha" + }, { + "name": "date", + "number": 3, + "label": "LABEL_OPTIONAL", + "type": "TYPE_STRING", + "jsonName": "date" + }, { + "name": "version", + "number": 4, + "label": "LABEL_OPTIONAL", + "type": "TYPE_STRING", + "jsonName": "version" + }], + "enumType": [{ + "name": "StatusType", + "value": [{ + "name": "UNKNOWN", + "number": 0 + }, { + "name": "RUNNING", + "number": 1 + }, { + "name": "ERROR", + "number": 2 + }] + }] + }], + "service": [{ + "name": "Status", + "method": [{ + "name": "GetStatus", + "inputType": ".com.example.helloworld.Empty", + "outputType": ".com.example.helloworld.StatusResponse" + }] + }], + "options": { + "javaPackage": "com.example.helloworld" + }, + "syntax": "proto3" + }] +} diff --git a/plugin/src/test/resources/status.jaxrs.json b/plugin/src/test/resources/status.jaxrs.json new file mode 100644 index 0000000..130f66a --- /dev/null +++ b/plugin/src/test/resources/status.jaxrs.json @@ -0,0 +1,79 @@ +{ + "fileToGenerate": ["core.proto", "status.proto"], + "parameter": "target=server,type=jaxrs", + "compilerVersion": { + "major": 3, + "minor": 5, + "patch": 1, + "suffix": "" + }, + "protoFile": [{ + "name": "core.proto", + "package": "com.example.helloworld", + "messageType": [{ + "name": "Empty" + }], + "options": { + "javaPackage": "com.example.helloworld" + }, + "syntax": "proto3" + }, { + "name": "status.proto", + "package": "com.example.helloworld", + "dependency": ["core.proto"], + "messageType": [{ + "name": "StatusResponse", + "field": [{ + "name": "status", + "number": 1, + "label": "LABEL_OPTIONAL", + "type": "TYPE_ENUM", + "typeName": ".com.example.helloworld.StatusResponse.StatusType", + "jsonName": "status" + }, { + "name": "sha", + "number": 2, + "label": "LABEL_OPTIONAL", + "type": "TYPE_STRING", + "jsonName": "sha" + }, { + "name": "date", + "number": 3, + "label": "LABEL_OPTIONAL", + "type": "TYPE_STRING", + "jsonName": "date" + }, { + "name": "version", + "number": 4, + "label": "LABEL_OPTIONAL", + "type": "TYPE_STRING", + "jsonName": "version" + }], + "enumType": [{ + "name": "StatusType", + "value": [{ + "name": "UNKNOWN", + "number": 0 + }, { + "name": "RUNNING", + "number": 1 + }, { + "name": "ERROR", + "number": 2 + }] + }] + }], + "service": [{ + "name": "Status", + "method": [{ + "name": "GetStatus", + "inputType": ".com.example.helloworld.Empty", + "outputType": ".com.example.helloworld.StatusResponse" + }] + }], + "options": { + "javaPackage": "com.example.helloworld" + }, + "syntax": "proto3" + }] +} diff --git a/runtime/core/build.gradle b/runtime/core/build.gradle index ee87f0f..d9f4b55 100644 --- a/runtime/core/build.gradle +++ b/runtime/core/build.gradle @@ -1,19 +1,15 @@ -plugins { - id 'java' -} - -group 'com.flit' -archivesBaseName = 'flit-core-runtime' -sourceCompatibility = 1.8 -def protobufVersion = '3.5.1' - -repositories { - mavenCentral() +dependencies { + api("com.google.protobuf:protobuf-java:${protobufVersion}") + api("com.google.protobuf:protobuf-java-util:${protobufVersion}") } -dependencies { - compile "com.google.protobuf:protobuf-java:${protobufVersion}" - compile "com.google.protobuf:protobuf-java-util:${protobufVersion}" +publishing { + publications { + maven(MavenPublication) { + artifactId = 'flit-core-runtime' + version = "2.0" - testCompile 'junit:junit:4.12' + from components.java + } + } } diff --git a/runtime/jakarta/build.gradle b/runtime/jakarta/build.gradle new file mode 100644 index 0000000..c82aeb9 --- /dev/null +++ b/runtime/jakarta/build.gradle @@ -0,0 +1,20 @@ +dependencies { + api(project(':runtime:core')) + implementation("jakarta.servlet:jakarta.servlet-api:$jakartaservletapiVersion") + implementation("jakarta.ws.rs:jakarta.ws.rs-api:$jakartawsrsapiVersion") + implementation("org.slf4j:slf4j-api:$slf4jVersion") + + testImplementation("org.glassfish.jersey.core:jersey-common:$jerseyCommonJakartaVersion") + testImplementation("org.mockito:mockito-junit-jupiter:$mockitoVersion") +} + +publishing { + publications { + maven(MavenPublication) { + artifactId = 'flit-jakarta-runtime' + version = "2.0" + + from components.java + } + } +} diff --git a/runtime/jakarta/src/main/java/com/flit/runtime/jakarta/FlitExceptionMapper.java b/runtime/jakarta/src/main/java/com/flit/runtime/jakarta/FlitExceptionMapper.java new file mode 100644 index 0000000..865ad18 --- /dev/null +++ b/runtime/jakarta/src/main/java/com/flit/runtime/jakarta/FlitExceptionMapper.java @@ -0,0 +1,41 @@ +package com.flit.runtime.jakarta; + +import com.flit.runtime.FlitException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Provider +public class FlitExceptionMapper implements ExceptionMapper { + + private static final Logger LOGGER = LoggerFactory.getLogger(FlitExceptionMapper.class); + + @Context + private HttpServletRequest request; + + @Override + public Response toResponse(FlitException exception) { + LOGGER.error("Flit exception: request = {}, method = {}, code = {}, msg = {}", + request.getRequestURI(), request.getMethod(), exception.getErrorCode(), + exception.getMessage(), exception); + + Map response = new HashMap<>(); + response.put("code", exception.getErrorCode().getErrorCode()); + response.put("msg", exception.getMessage()); + + if (exception.hasMeta()) { + response.put("meta", exception.getMeta()); + } + return Response.status(exception.getErrorCode().getHttpStatus()) + .type(MediaType.APPLICATION_JSON) + .entity(response) + .build(); + } +} diff --git a/runtime/jakarta/src/main/java/com/flit/runtime/jakarta/InvalidProtobufExceptionMapper.java b/runtime/jakarta/src/main/java/com/flit/runtime/jakarta/InvalidProtobufExceptionMapper.java new file mode 100644 index 0000000..7052585 --- /dev/null +++ b/runtime/jakarta/src/main/java/com/flit/runtime/jakarta/InvalidProtobufExceptionMapper.java @@ -0,0 +1,36 @@ +package com.flit.runtime.jakarta; + +import com.flit.runtime.ErrorCode; +import com.google.protobuf.InvalidProtocolBufferException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class InvalidProtobufExceptionMapper implements + ExceptionMapper { + + private static final Logger LOGGER = LoggerFactory + .getLogger(InvalidProtobufExceptionMapper.class); + + @Context private HttpServletRequest request; + + @Override + public Response toResponse(InvalidProtocolBufferException exception) { + LOGGER.error("InvalidProtocolBufferException: request = {}, method = {}, msg= {}", + request.getRequestURI(), request.getMethod(), exception.getMessage(), exception); + + Map response = new HashMap<>(); + response.put("code", ErrorCode.INVALID_ARGUMENT.getErrorCode()); + response.put("msg", exception.getMessage()); + return Response.status(ErrorCode.INVALID_ARGUMENT.getHttpStatus()) + .type(MediaType.APPLICATION_JSON) + .entity(response) + .build(); + } +} diff --git a/runtime/jakarta/src/test/java/com/flit/runtime/jakarta/FlitExceptionMapperTest.java b/runtime/jakarta/src/test/java/com/flit/runtime/jakarta/FlitExceptionMapperTest.java new file mode 100644 index 0000000..aaf6306 --- /dev/null +++ b/runtime/jakarta/src/test/java/com/flit/runtime/jakarta/FlitExceptionMapperTest.java @@ -0,0 +1,40 @@ +package com.flit.runtime.jakarta; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.flit.runtime.ErrorCode; +import com.flit.runtime.FlitException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.Status; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class FlitExceptionMapperTest { + + @Mock + private HttpServletRequest request; + + @InjectMocks + private FlitExceptionMapper flitExceptionMapper = new FlitExceptionMapper(); + + @Test + void testToResponse() { + FlitException flit = FlitException.builder() + .withCause(new RuntimeException("Failed")) + .withErrorCode(ErrorCode.INTERNAL) + .withMessage("with this message") + .build(); + Response response = flitExceptionMapper.toResponse(flit); + assertEquals(response.getStatus(), Status.INTERNAL_SERVER_ERROR.getStatusCode()); + Map expectedResult = Map.of("msg", "with this message", "code", "internal"); + assertEquals(response.getEntity(), expectedResult); + assertEquals(response.getMediaType(), MediaType.APPLICATION_JSON_TYPE); + } +} \ No newline at end of file diff --git a/runtime/jakarta/src/test/java/com/flit/runtime/jakarta/InvalidProtobufExceptionMapperTest.java b/runtime/jakarta/src/test/java/com/flit/runtime/jakarta/InvalidProtobufExceptionMapperTest.java new file mode 100644 index 0000000..9e26163 --- /dev/null +++ b/runtime/jakarta/src/test/java/com/flit/runtime/jakarta/InvalidProtobufExceptionMapperTest.java @@ -0,0 +1,36 @@ +package com.flit.runtime.jakarta; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.google.protobuf.InvalidProtocolBufferException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class InvalidProtobufExceptionMapperTest { + + @Mock + private HttpServletRequest request; + + @InjectMocks + private InvalidProtobufExceptionMapper mapper = new InvalidProtobufExceptionMapper(); + + @Test + void testToResponse() { + InvalidProtocolBufferException exception = new InvalidProtocolBufferException( + "something happened"); + Response response = mapper.toResponse(exception); + assertEquals(response.getStatus(), 400); + Map expectedResult = Map + .of("msg", "something happened", "code", "invalid_argument"); + assertEquals(response.getEntity(), expectedResult); + assertEquals(response.getMediaType(), MediaType.APPLICATION_JSON_TYPE); + } +} diff --git a/runtime/jaxrs/build.gradle b/runtime/jaxrs/build.gradle new file mode 100644 index 0000000..7aa4d82 --- /dev/null +++ b/runtime/jaxrs/build.gradle @@ -0,0 +1,20 @@ +dependencies { + api(project(':runtime:core')) + implementation("javax.servlet:javax.servlet-api:$javaxservletapiVersion") + implementation("javax.ws.rs:javax.ws.rs-api:$javaxwsrsapiVersion") + implementation("org.slf4j:slf4j-api:$slf4jVersion") + + testImplementation("org.glassfish.jersey.core:jersey-common:$jerseyCommonJavaxVersion") + testImplementation("org.mockito:mockito-junit-jupiter:$mockitoVersion") +} + +publishing { + publications { + maven(MavenPublication) { + artifactId = 'flit-jaxrs-runtime' + version = "2.0" + + from components.java + } + } +} diff --git a/runtime/jaxrs/src/main/java/com/flit/runtime/jaxrs/FlitExceptionMapper.java b/runtime/jaxrs/src/main/java/com/flit/runtime/jaxrs/FlitExceptionMapper.java new file mode 100644 index 0000000..6687229 --- /dev/null +++ b/runtime/jaxrs/src/main/java/com/flit/runtime/jaxrs/FlitExceptionMapper.java @@ -0,0 +1,40 @@ +package com.flit.runtime.jaxrs; + +import com.flit.runtime.FlitException; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Provider +public class FlitExceptionMapper implements ExceptionMapper { + + private static final Logger LOGGER = LoggerFactory.getLogger(FlitExceptionMapper.class); + + @Context private HttpServletRequest request; + + @Override + public Response toResponse(FlitException exception) { + LOGGER.error("Flit exception: request = {}, method = {}, code = {}, msg = {}", + request.getRequestURI(), request.getMethod(), exception.getErrorCode(), + exception.getMessage(), exception); + + Map response = new HashMap<>(); + response.put("code", exception.getErrorCode().getErrorCode()); + response.put("msg", exception.getMessage()); + + if (exception.hasMeta()) { + response.put("meta", exception.getMeta()); + } + return Response.status(exception.getErrorCode().getHttpStatus()) + .type(MediaType.APPLICATION_JSON) + .entity(response) + .build(); + } +} diff --git a/runtime/jaxrs/src/main/java/com/flit/runtime/jaxrs/InvalidProtobufExceptionMapper.java b/runtime/jaxrs/src/main/java/com/flit/runtime/jaxrs/InvalidProtobufExceptionMapper.java new file mode 100644 index 0000000..75f6011 --- /dev/null +++ b/runtime/jaxrs/src/main/java/com/flit/runtime/jaxrs/InvalidProtobufExceptionMapper.java @@ -0,0 +1,36 @@ +package com.flit.runtime.jaxrs; + +import com.flit.runtime.ErrorCode; +import com.google.protobuf.InvalidProtocolBufferException; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class InvalidProtobufExceptionMapper implements + ExceptionMapper { + + private static final Logger LOGGER = LoggerFactory + .getLogger(InvalidProtobufExceptionMapper.class); + + @Context private HttpServletRequest request; + + @Override + public Response toResponse(InvalidProtocolBufferException exception) { + LOGGER.error("InvalidProtocolBufferException: request = {}, method = {}, msg= {}", + request.getRequestURI(), request.getMethod(), exception.getMessage(), exception); + + Map response = new HashMap<>(); + response.put("code", ErrorCode.INVALID_ARGUMENT.getErrorCode()); + response.put("msg", exception.getMessage()); + return Response.status(ErrorCode.INVALID_ARGUMENT.getHttpStatus()) + .type(MediaType.APPLICATION_JSON) + .entity(response) + .build(); + } +} diff --git a/runtime/jaxrs/src/test/java/com/flit/runtime/jaxrs/FlitExceptionMapperTest.java b/runtime/jaxrs/src/test/java/com/flit/runtime/jaxrs/FlitExceptionMapperTest.java new file mode 100644 index 0000000..d09b89e --- /dev/null +++ b/runtime/jaxrs/src/test/java/com/flit/runtime/jaxrs/FlitExceptionMapperTest.java @@ -0,0 +1,40 @@ +package com.flit.runtime.jaxrs; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.flit.runtime.ErrorCode; +import com.flit.runtime.FlitException; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class FlitExceptionMapperTest { + + @Mock + private HttpServletRequest request; + + @InjectMocks + private FlitExceptionMapper flitExceptionMapper = new FlitExceptionMapper(); + + @Test + public void testToResponse() { + FlitException flit = FlitException.builder() + .withCause(new RuntimeException("Failed")) + .withErrorCode(ErrorCode.INTERNAL) + .withMessage("with this message") + .build(); + Response response = flitExceptionMapper.toResponse(flit); + assertEquals(response.getStatus(), Status.INTERNAL_SERVER_ERROR.getStatusCode()); + Map expectedResult = Map.of("msg", "with this message", "code", "internal"); + assertEquals(response.getEntity(), expectedResult); + assertEquals(response.getMediaType(), MediaType.APPLICATION_JSON_TYPE); + } +} diff --git a/runtime/jaxrs/src/test/java/com/flit/runtime/jaxrs/InvalidProtobufExceptionMapperTest.java b/runtime/jaxrs/src/test/java/com/flit/runtime/jaxrs/InvalidProtobufExceptionMapperTest.java new file mode 100644 index 0000000..aa9f5b4 --- /dev/null +++ b/runtime/jaxrs/src/test/java/com/flit/runtime/jaxrs/InvalidProtobufExceptionMapperTest.java @@ -0,0 +1,36 @@ +package com.flit.runtime.jaxrs; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.google.protobuf.InvalidProtocolBufferException; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class InvalidProtobufExceptionMapperTest { + + @Mock + private HttpServletRequest request; + + @InjectMocks + private InvalidProtobufExceptionMapper mapper = new InvalidProtobufExceptionMapper(); + + @Test + public void testToResponse() { + InvalidProtocolBufferException exception = new InvalidProtocolBufferException( + "something happened"); + Response response = mapper.toResponse(exception); + assertEquals(response.getStatus(), 400); + Map expectedResult = Map + .of("msg", "something happened", "code", "invalid_argument"); + assertEquals(response.getEntity(), expectedResult); + assertEquals(response.getMediaType(), MediaType.APPLICATION_JSON_TYPE); + } +} diff --git a/runtime/spring/build.gradle b/runtime/spring/build.gradle index 28f463c..d9af59a 100644 --- a/runtime/spring/build.gradle +++ b/runtime/spring/build.gradle @@ -1,21 +1,17 @@ -plugins { - id 'java' -} - -group 'com.flit' -archivesBaseName = 'flit-undertow-runtime' -sourceCompatibility = 1.8 - -repositories { - mavenCentral() -} - dependencies { + api(project(':runtime:core')) + compileOnly("org.slf4j:slf4j-api:$slf4jVersion") + compileOnly("jakarta.servlet:jakarta.servlet-api:$jakartaservletapiVersion") + compileOnly("org.springframework:spring-webmvc:$springVersion") +} - compileOnly project(':runtime:core') - compileOnly 'org.slf4j:slf4j-api:1.7.25' - compileOnly 'javax.servlet:javax.servlet-api:4.0.1' - compileOnly 'org.springframework:spring-webmvc:5.0.6.RELEASE' +publishing { + publications { + maven(MavenPublication) { + artifactId = 'flit-spring-runtime' + version = "2.0" - testCompile 'junit:junit:4.12' + from components.java + } + } } diff --git a/runtime/spring/gradle.properties b/runtime/spring/gradle.properties deleted file mode 100644 index beb72cc..0000000 --- a/runtime/spring/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -version=1.0.0 \ No newline at end of file diff --git a/runtime/spring/src/main/java/com/flit/runtime/spring/FlitExceptionHandler.java b/runtime/spring/src/main/java/com/flit/runtime/spring/FlitExceptionHandler.java index a57c3c9..494a820 100644 --- a/runtime/spring/src/main/java/com/flit/runtime/spring/FlitExceptionHandler.java +++ b/runtime/spring/src/main/java/com/flit/runtime/spring/FlitExceptionHandler.java @@ -2,6 +2,10 @@ import com.flit.runtime.ErrorCode; import com.flit.runtime.FlitException; +import com.google.protobuf.InvalidProtocolBufferException; +import jakarta.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; @@ -11,17 +15,12 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; -import javax.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Map; - @ControllerAdvice @Component public class FlitExceptionHandler extends ResponseEntityExceptionHandler { private static final Logger LOGGER = LoggerFactory.getLogger(FlitExceptionHandler.class); - @ExceptionHandler(Exception.class) public ResponseEntity handleException(HttpServletRequest request, Exception e) { LOGGER.error( @@ -40,6 +39,21 @@ public ResponseEntity handleException(HttpServletRequest request, Exception e .body(response); } + @ExceptionHandler(InvalidProtocolBufferException.class) + public ResponseEntity handleInvalidProtocolBufferException(HttpServletRequest request, Exception e) { + LOGGER.error("InvalidProtocolBufferException: request = {}, method = {}, msg= {}", + request.getRequestURI(), request.getMethod(), e.getMessage(), e); + + Map response = new HashMap<>(); + response.put("code", ErrorCode.INVALID_ARGUMENT); + response.put("msg", e.getMessage()); + + return ResponseEntity + .status(ErrorCode.INVALID_ARGUMENT.getHttpStatus()) + .contentType(MediaType.APPLICATION_JSON) + .body(response); + } + @ExceptionHandler(FlitException.class) public ResponseEntity handleFlitException(HttpServletRequest request, FlitException e) { diff --git a/runtime/spring/src/main/java/com/flit/runtime/spring/FlitHandlerExceptionResolver.java b/runtime/spring/src/main/java/com/flit/runtime/spring/FlitHandlerExceptionResolver.java index 0cf1635..1a1bf2c 100644 --- a/runtime/spring/src/main/java/com/flit/runtime/spring/FlitHandlerExceptionResolver.java +++ b/runtime/spring/src/main/java/com/flit/runtime/spring/FlitHandlerExceptionResolver.java @@ -1,26 +1,26 @@ package com.flit.runtime.spring; import com.flit.runtime.ErrorCode; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; +import org.springframework.lang.Nullable; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.View; import org.springframework.web.servlet.view.json.MappingJackson2JsonView; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - public class FlitHandlerExceptionResolver implements HandlerExceptionResolver, Ordered { private static final Logger LOGGER = LoggerFactory.getLogger(FlitHandlerExceptionResolver.class); - private View view = new MappingJackson2JsonView(); + private final View view = new MappingJackson2JsonView(); @Override - public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { LOGGER.warn("Handling internal exception: error = {}", ex.getMessage()); diff --git a/runtime/spring/src/main/java/com/flit/runtime/spring/package-info.java b/runtime/spring/src/main/java/com/flit/runtime/spring/package-info.java new file mode 100644 index 0000000..19335ad --- /dev/null +++ b/runtime/spring/src/main/java/com/flit/runtime/spring/package-info.java @@ -0,0 +1,6 @@ +@NonNullApi +@NonNullFields +package com.flit.runtime.spring; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; \ No newline at end of file diff --git a/runtime/spring/src/main/resources/application.properties b/runtime/spring/src/main/resources/application.properties deleted file mode 100644 index a2ae767..0000000 --- a/runtime/spring/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -spring.mvc.throw-exception-if-no-handler-found=true -spring.resources.add-mappings=false -server.error.whitelabel.enabled=false \ No newline at end of file diff --git a/runtime/spring/src/main/resources/application.yaml b/runtime/spring/src/main/resources/application.yaml new file mode 100644 index 0000000..9e19a3c --- /dev/null +++ b/runtime/spring/src/main/resources/application.yaml @@ -0,0 +1,3 @@ +spring.mvc.throw-exception-if-no-handler-found: true +spring.resources.add-mappings: false +server.error.whitelabel.enabled: false diff --git a/runtime/undertow/build.gradle b/runtime/undertow/build.gradle index 7f96269..aedf51d 100644 --- a/runtime/undertow/build.gradle +++ b/runtime/undertow/build.gradle @@ -1,60 +1,16 @@ -plugins { - id 'java' - id 'idea' - id "com.google.protobuf" version "0.8.6" -} - -group 'com.flit' -archivesBaseName = 'flit-undertow-runtime' -sourceCompatibility = 1.8 - -repositories { - mavenCentral() -} - -// Make a flit configuration for testing our runtime -configurations { - testFlit -} - -def protobufVersion = '3.5.1' -def flitDir = "$buildDir/flit" - dependencies { - compile project(':runtime:core') - compile 'org.slf4j:slf4j-api:1.7.25' - compile 'io.undertow:undertow-core:2.0.9.Final' - compile 'com.google.code.gson:gson:2.8.5' - - testCompile 'junit:junit:4.12' - - testFlit project(path: ':plugin', configuration: 'zip') + api(project(':runtime:core')) + compileOnly("org.slf4j:slf4j-api:$slf4jVersion") + compileOnly("io.undertow:undertow-core:$undertowVersion") } -protobuf { - protoc { - artifact = "com.google.protobuf:protoc:${protobufVersion}" - } - plugins { - flit { - path = "${flitDir}/protoc-gen-flit" - } - } - generateProtoTasks { - all().each { task -> - task.plugins { - flit { - option 'target=server' - option 'type=undertow' - } - } - task.dependsOn unzipFlit +publishing { + publications { + maven(MavenPublication) { + artifactId = 'flit-undertow-runtime' + version = "2.0" + + from components.java } } } - -task unzipFlit(type: Copy) { - inputs.files configurations.testFlit - from configurations.testFlit.findAll { it.name.endsWith('zip') }.collect { zipTree(it) } - into flitDir -} diff --git a/runtime/undertow/gradle.properties b/runtime/undertow/gradle.properties deleted file mode 100644 index beb72cc..0000000 --- a/runtime/undertow/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -version=1.0.0 \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index f7e38a2..0b7bbe3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,5 @@ include "plugin" include 'runtime:core' include 'runtime:spring' include 'runtime:undertow' - +include 'runtime:jaxrs' +include 'runtime:jakarta'