From 3974ad2f6372bd486d45a2d34f1a5832fabd871f Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 13:37:44 +0530 Subject: [PATCH 01/21] Initiate the Gradle project --- .gitattributes | 8 + build-config/resources/Ballerina.toml | 22 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 63721 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 +++++++++++++++++++++++ gradlew.bat | 92 +++++++++ pull_request_template.md | 52 ----- 7 files changed, 378 insertions(+), 52 deletions(-) create mode 100644 .gitattributes create mode 100644 build-config/resources/Ballerina.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat delete mode 100644 pull_request_template.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..afd59d8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml new file mode 100644 index 0000000..4287cea --- /dev/null +++ b/build-config/resources/Ballerina.toml @@ -0,0 +1,22 @@ +[package] +org = "ballerinax" +name = "confluent.cavroserdes" +version = "@toml.version@" +authors = ["Ballerina"] +export=["confluent.cavroserdes"] +keywords = ["confluent", "schema_registry", "avro", "serdes"] +repository = "https://github.com/ballerina-platform/module-ballerinax-confluent.cregistry" +license = ["Apache-2.0"] +distribution = "2201.8.6" + +[build-options] +observabilityIncluded = true + +[platform.java17] +graalvmCompatible = true + +[[platform.java17.dependency]] +groupId = "io.ballerina.lib" +artifactId = "confluent.cavroserdes-native" +version = "@toml.version@" +path = "../native/build/libs/confluent.cavroserdes-native-@project.version@.jar" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7f93135c49b765f8051ef9d0a6055ff8e46073d8 GIT binary patch literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9f4197d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..0adc8e1 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/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. +# + +############################################################################## +# +# 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/subprojects/plugins/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 +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 + +# 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 "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +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 ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# 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 + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + 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" && ! "$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=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=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +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" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + 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 + # 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 +fi + + +# 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 $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# 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 new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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 + +@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. +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 execute + +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 + +: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 %* + +: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/pull_request_template.md b/pull_request_template.md deleted file mode 100644 index 9b32185..0000000 --- a/pull_request_template.md +++ /dev/null @@ -1,52 +0,0 @@ -## Purpose -> Describe the problems, issues, or needs driving this feature/fix and include links to related issues in the following format: Resolves issue1, issue2, etc. - -## Goals -> Describe the solutions that this feature/fix will introduce to resolve the problems described above - -## Approach -> Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email documentation@wso2.com to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here. - -## User stories -> Summary of user stories addressed by this change> - -## Release note -> Brief description of the new feature or bug fix as it will appear in the release notes - -## Documentation -> Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter “N/A” plus brief explanation of why there’s no doc impact - -## Training -> Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable - -## Certification -> Type “Sent” when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to certification@wso2.com and NOT pasted in this PR. If there is no impact on certification exams, type “N/A” and explain why. - -## Marketing -> Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable - -## Automation tests - - Unit tests - > Code coverage information - - Integration tests - > Details about the test cases and coverage - -## Security checks - - Followed secure coding standards in http://wso2.com/technical-reports/wso2-secure-engineering-guidelines? yes/no - - Ran FindSecurityBugs plugin and verified report? yes/no - - Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets? yes/no - -## Samples -> Provide high-level details about the samples related to this feature - -## Related PRs -> List any other related PRs - -## Migrations (if applicable) -> Describe migration steps and platforms on which migration has been tested - -## Test environment -> List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested - -## Learning -> Describe the research phase and any blog posts, patterns, libraries, or add-ons you used to solve the problem. \ No newline at end of file From bbb231d5655f8a6e935535e59774d5bd9e6b66b3 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 13:38:02 +0530 Subject: [PATCH 02/21] Add github workflow files --- .../workflows/build-with-bal-test-graalvm.yml | 19 ++++++++++++++++ .github/workflows/ci.yml | 18 +++++++++++++++ .github/workflows/daily-build.yml | 14 ++++++++++++ .github/workflows/dev-stg-release.yml | 22 +++++++++++++++++++ .github/workflows/pull-request.yml | 16 ++++++++++++++ .github/workflows/release.yml | 16 ++++++++++++++ .github/workflows/trivy-scan.yml | 15 +++++++++++++ 7 files changed, 120 insertions(+) create mode 100644 .github/workflows/build-with-bal-test-graalvm.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/daily-build.yml create mode 100644 .github/workflows/dev-stg-release.yml create mode 100644 .github/workflows/pull-request.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/trivy-scan.yml diff --git a/.github/workflows/build-with-bal-test-graalvm.yml b/.github/workflows/build-with-bal-test-graalvm.yml new file mode 100644 index 0000000..e879182 --- /dev/null +++ b/.github/workflows/build-with-bal-test-graalvm.yml @@ -0,0 +1,19 @@ +name: GraalVM Check + +on: + schedule: + - cron: "30 12 * * *" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +jobs: + call_stdlib_workflow: + name: Run StdLib Workflow + if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/build-with-bal-test-graalvm-connector-template.yml@main + secrets: inherit + with: + additional-build-flags: "-x :confluent.cavroserdes-examples:build" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7d8b017 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,18 @@ +name: Build + +on: + push: + branches: + - main + - 2201.[0-9]+.x + repository_dispatch: + types: check_connector_for_breaking_changes + +jobs: + call_workflow: + name: Run Connector Build Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/build-connector-template.yml@main + secrets: inherit + with: + repo-name: module-ballerinax-confluent.cavroserdes diff --git a/.github/workflows/daily-build.yml b/.github/workflows/daily-build.yml new file mode 100644 index 0000000..b814c2c --- /dev/null +++ b/.github/workflows/daily-build.yml @@ -0,0 +1,14 @@ +name: Daily build + +on: + schedule: + - cron: "30 0 * * *" + +jobs: + call_workflow: + name: Run Daily Build Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/daily-build-connector-template.yml@main + secrets: inherit + with: + repo-name: module-ballerinax-confluent.cavroserdes diff --git a/.github/workflows/dev-stg-release.yml b/.github/workflows/dev-stg-release.yml new file mode 100644 index 0000000..f9d6e5e --- /dev/null +++ b/.github/workflows/dev-stg-release.yml @@ -0,0 +1,22 @@ +name: Publish to the Ballerina Dev\Stage Central + +on: + workflow_dispatch: + inputs: + environment: + type: choice + description: Select Environment + required: true + options: + - DEV CENTRAL + - STAGE CENTRAL + +jobs: + call_workflow: + name: Run Dev\Stage Central Publish Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/dev-stage-central-publish-connector-template.yml@main + secrets: inherit + with: + environment: ${{ github.event.inputs.environment }} + additional-publish-flags: "-x :confluent.cavroserdes-examples:build" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..bcbb743 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,16 @@ +name: PR Build + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +on: pull_request + +jobs: + call_workflow: + name: Run PR Build Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/pr-build-connector-template.yml@main + secrets: inherit + with: + additional-test-flags: ${{ github.event.pull_request.head.repo.full_name != github.repository && '-x test' || ''}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a742da2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,16 @@ +name: Publish Release + +on: + workflow_dispatch: + repository_dispatch: + types: [ stdlib-release-pipeline ] + +jobs: + call_workflow: + name: Run Release Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/release-package-connector-template.yml@main + secrets: inherit + with: + package-name: confluent.cavroserdes + package-org: ballerinax diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml new file mode 100644 index 0000000..b7d057f --- /dev/null +++ b/.github/workflows/trivy-scan.yml @@ -0,0 +1,15 @@ +name: Trivy + +on: + workflow_dispatch: + schedule: + - cron: "30 22 * * *" + +jobs: + call_workflow: + name: Run Trivy Scan Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-standard-library/.github/workflows/trivy-scan-template.yml@main + secrets: inherit + with: + additional-build-flags: "-x :confluent.cavroserdes-examples:build" From d447a9c0197ab1d2defe48fe2ca74de29f9c9b7d Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 13:38:49 +0530 Subject: [PATCH 03/21] Add Gradle build files to the project --- ballerina/build.gradle | 88 +++++++++++++++++++++ build-config/checkstyle/build.gradle | 49 ++++++++++++ build.gradle | 98 +++++++++++++++++++++++ gradle.properties | 23 ++++++ native/build.gradle | 113 +++++++++++++++++++++++++++ settings.gradle | 60 ++++++++++++++ 6 files changed, 431 insertions(+) create mode 100644 ballerina/build.gradle create mode 100644 build-config/checkstyle/build.gradle create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 native/build.gradle create mode 100644 settings.gradle diff --git a/ballerina/build.gradle b/ballerina/build.gradle new file mode 100644 index 0000000..ef8e232 --- /dev/null +++ b/ballerina/build.gradle @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you 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 + * + * http://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. + */ + +import org.apache.tools.ant.taskdefs.condition.Os + +plugins { + id 'io.ballerina.plugin' +} + +description = 'Confluent Avro Serialization - Ballerina' + +def packageName = "confluent.cavroserdes" +def packageOrg = "ballerinax" +def tomlVersion = stripBallerinaExtensionVersion("${project.version}") +def ballerinaTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/Ballerina.toml") +def ballerinaTomlFile = new File("$project.projectDir/Ballerina.toml") + +def stripBallerinaExtensionVersion(String extVersion) { + if (extVersion.matches(project.ext.timestampedVersionRegex)) { + def splitVersion = extVersion.split('-') + if (splitVersion.length > 3) { + def strippedValues = splitVersion[0..-4] + return strippedValues.join('-') + } else { + return extVersion + } + } else { + return extVersion.replace("${project.ext.snapshotVersion}", "") + } +} + +ballerina { + packageOrganization = packageOrg + module = packageName + testCoverageParam = "--code-coverage --coverage-format=xml" + isConnector = true + langVersion = ballerinaLangVersion + platform = "java17" +} + +task updateTomlFiles { + doLast { + def newBallerinaToml = ballerinaTomlFilePlaceHolder.text.replace("@project.version@", project.version) + newBallerinaToml = newBallerinaToml.replace("@toml.version@", tomlVersion) + ballerinaTomlFile.text = newBallerinaToml + } +} + +task commitTomlFiles { + doLast { + project.exec { + ignoreExitValue true + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine 'cmd', '/c', "git commit -m \"[Automated] Update the toml files\" Ballerina.toml Dependencies.toml" + } else { + commandLine 'sh', '-c', "git commit -m '[Automated] Update the toml files' Ballerina.toml Dependencies.toml" + } + } + } +} + +clean { + delete 'build' +} + +updateTomlFiles.dependsOn copyStdlibs +build.dependsOn copyToLib + +test.dependsOn ":${packageName}-native:build" +build.dependsOn ":${packageName}-native:build" + +publishToMavenLocal.dependsOn build +publish.dependsOn build diff --git a/build-config/checkstyle/build.gradle b/build-config/checkstyle/build.gradle new file mode 100644 index 0000000..ac499db --- /dev/null +++ b/build-config/checkstyle/build.gradle @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you 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 + * + * http://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. + */ + +plugins { + id "de.undercouch.download" +} + +apply plugin: 'java' + +task downloadCheckstyleRuleFiles(type: Download) { + src([ + 'https://raw.githubusercontent.com/wso2/code-quality-tools/v1.4/checkstyle/jdk-17/checkstyle.xml', + 'https://raw.githubusercontent.com/wso2/code-quality-tools/v1.4/checkstyle/jdk-17/suppressions.xml' + ]) + overwrite false + onlyIfNewer true + dest buildDir +} + +jar { + enabled = false +} + +clean { + enabled = false +} + +artifacts.add('default', file("$project.buildDir/checkstyle.xml")) { + builtBy('downloadCheckstyleRuleFiles') +} + +artifacts.add('default', file("$project.buildDir/suppressions.xml")) { + builtBy('downloadCheckstyleRuleFiles') +} diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..7301168 --- /dev/null +++ b/build.gradle @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you 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 + * + * http://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. + */ + +import org.apache.tools.ant.taskdefs.condition.Os + +plugins { + id "com.github.spotbugs-base" + id "com.github.johnrengelman.shadow" + id "de.undercouch.download" + id "net.researchgate.release" +} + +description = 'Ballerina - Avro Serialization for Confluent Schema Registry' + +allprojects { + group = project.group + version = project.version + + apply plugin: 'jacoco' + apply plugin: 'maven-publish' + + repositories { + mavenLocal() + mavenCentral() + maven { + url = 'https://maven.wso2.org/nexus/content/groups/wso2-public/' + } + + maven { + url = 'https://packages.confluent.io/maven/' + } + + maven { + url = 'https://repo.maven.apache.org/maven2' + } + + maven { + url = 'https://maven.pkg.github.com/ballerina-platform/ballerina-lang' + credentials { + username System.getenv("packageUser") + password System.getenv("packagePAT") + } + } + } + + ext { + snapshotVersion = '-SNAPSHOT' + timestampedVersionRegex = '.*-\\d{8}-\\d{6}-\\w.*\$' + } +} + +subprojects { + configurations { + ballerinaStdLibs + jbalTools + } + dependencies { + /* JBallerina Tools */ + jbalTools ("org.ballerinalang:jballerina-tools:${ballerinaLangVersion}") { + transitive = false + } + + ballerinaStdLibs "io.ballerina.stdlib:io-ballerina:${stdlibIoVersion}" + } +} + +task build { + dependsOn(":confluent.cavroserdes-native:build") + dependsOn(":confluent.cavroserdes-ballerina:build") +} + +def moduleVersion = project.version.replace("-SNAPSHOT", "") + +release { + buildTasks = ['build'] + failOnSnapshotDependencies = true + versionPropertyFile = 'gradle.properties' + tagTemplate = 'v${version}' + git { + requireBranch = "release-${moduleVersion}" + pushToRemote = 'origin' + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..9f85486 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +org.gradle.caching=true +group=io.ballerina.lib +version=0.1.0-SNAPSHOT +ballerinaLangVersion=2201.8.6 + +checkstylePluginVersion=10.12.0 +spotbugsPluginVersion=5.0.14 +shadowJarPluginVersion=8.1.1 +downloadPluginVersion=5.4.0 +releasePluginVersion=2.8.0 +ballerinaGradlePluginVersion=2.2.4 +jacocoVersion=0.8.10 + +slf4jVersion=1.7.21 +jacksonVersion=2.17.0 +avroVersion=1.11.3 +kafkaAvroVersion=5.3.0 +kafkaClientVersion=3.7.0 +kafkaSchemaRegistryVersion=5.3.2 +commonConfigVersion=4.1.0 +commonUtilsVersion=5.3.0 + +stdlibIoVersion=1.6.0 diff --git a/native/build.gradle b/native/build.gradle new file mode 100644 index 0000000..e468c9b --- /dev/null +++ b/native/build.gradle @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you 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 + * + * http://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. + */ + +plugins { + id 'java' + id 'checkstyle' + id 'com.github.spotbugs' +} + +description = 'Ballerina - Confluent Avro Ser/Des Native' + +configurations { + dist { + transitive true + } +} + +dependencies { + checkstyle project(":checkstyle") + checkstyle "com.puppycrawl.tools:checkstyle:${checkstylePluginVersion}" + + implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" + implementation group: 'org.ballerinalang', name: 'value', version: "${ballerinaLangVersion}" + + implementation group: 'org.apache.kafka', name: 'kafka-clients', version: "${kafkaClientVersion}" + implementation group: 'org.apache.avro', name: 'avro', version: "${avroVersion}" + implementation group: 'io.confluent', name: 'kafka-avro-serializer', version: "${kafkaAvroVersion}" + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${jacksonVersion}" + implementation group: 'io.confluent', name: 'kafka-schema-registry-client', version: "${kafkaSchemaRegistryVersion}" + implementation group: 'io.confluent', name: 'common-config', version: "${commonConfigVersion}" + implementation group: 'io.confluent', name: 'common-utils', version: "${commonUtilsVersion}" +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +test { + testLogging { + showStackTraces = true + showStandardStreams = true + events "failed" + exceptionFormat "full" + } + jacoco { + enabled = true + destinationFile = file("$buildDir/coverage-reports/jacoco.exec") + includeNoLocationClasses = true + } +} + +spotbugsMain { + ignoreFailures = true + effort = "max" + reportLevel = "low" + reportsDir = file("$project.buildDir/reports/spotbugs") + def excludeFile = file("${rootDir}/build-config/spotbugs-exclude.xml") + if (excludeFile.exists()) { + it.excludeFilter = excludeFile + } + reports { + text.enabled = true + } +} + +spotbugsTest { + enabled = false +} + +tasks.withType(Checkstyle) { + exclude '**/module-info.java' +} + +checkstyle { + toolVersion "${checkstylePluginVersion}" + configFile file("${rootDir}/build-config/checkstyle/build/checkstyle.xml") + configProperties = ["suppressionFile": file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] +} + +checkstyleMain.dependsOn ':checkstyle:downloadCheckstyleRuleFiles' + +jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + dependsOn configurations.dist + from { configurations.dist.collect { it.isDirectory() ? it : zipTree(it) } } { + exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' + } +} + +compileJava { + doFirst { + options.compilerArgs = [ + '--module-path', classpath.asPath, + ] + classpath = files() + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..819974b --- /dev/null +++ b/settings.gradle @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you 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 + * + * http://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. + */ + +pluginManagement { + plugins { + id "com.github.spotbugs-base" version "${spotbugsPluginVersion}" + id "com.github.johnrengelman.shadow" version "${shadowJarPluginVersion}" + id "de.undercouch.download" version "${downloadPluginVersion}" + id "net.researchgate.release" version "${releasePluginVersion}" + id "io.ballerina.plugin" version "${ballerinaGradlePluginVersion}" + } + + repositories { + gradlePluginPortal() + maven { + url = 'https://maven.pkg.github.com/ballerina-platform/*' + credentials { + username System.getenv("packageUser") + password System.getenv("packagePAT") + } + } + } +} + +plugins { + id "com.gradle.enterprise" version "3.13.2" +} + +def projectName = 'confluent.cavroserdes' +rootProject.name = "ballerinax-${projectName}" + +include ":checkstyle" +include ":${projectName}-native" +include ":${projectName}-ballerina" + +project(':checkstyle').projectDir = file("build-config${File.separator}checkstyle") +project(":${projectName}-native").projectDir = file('native') +project(":${projectName}-ballerina").projectDir = file('ballerina') + +gradleEnterprise { + buildScan { + termsOfServiceUrl = 'https://gradle.com/terms-of-service' + termsOfServiceAgree = 'yes' + } +} From 44967101abcdb13013dc1440fce408b6b982a961 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 13:40:44 +0530 Subject: [PATCH 04/21] Add necessary Ballerina files to the project --- ballerina/Ballerina.toml | 22 ++++++++++++++++++++++ ballerina/error.bal | 21 +++++++++++++++++++++ ballerina/init.bal | 25 +++++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 ballerina/Ballerina.toml create mode 100644 ballerina/error.bal create mode 100644 ballerina/init.bal diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml new file mode 100644 index 0000000..ac614f3 --- /dev/null +++ b/ballerina/Ballerina.toml @@ -0,0 +1,22 @@ +[package] +org = "ballerinax" +name = "confluent.cavroserdes" +version = "0.1.0" +authors = ["Ballerina"] +export=["confluent.cavroserdes"] +keywords = ["confluent", "schema_registry", "avro", "serdes"] +repository = "https://github.com/ballerina-platform/module-ballerinax-confluent.cregistry" +license = ["Apache-2.0"] +distribution = "2201.8.6" + +[build-options] +observabilityIncluded = true + +[platform.java17] +graalvmCompatible = true + +[[platform.java17.dependency]] +groupId = "io.ballerina.lib" +artifactId = "confluent.cavroserdes-native" +version = "0.1.0" +path = "../native/build/libs/confluent.cavroserdes-native-0.1.0-SNAPSHOT.jar" diff --git a/ballerina/error.bal b/ballerina/error.bal new file mode 100644 index 0000000..f8301f7 --- /dev/null +++ b/ballerina/error.bal @@ -0,0 +1,21 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you 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 +// +// http://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. + +# Represents any error related to the module. +public type Error distinct error; + +const SERIALIZATION_ERROR = "Data serialization error"; +const DESERIALIZATION_ERROR = "Data deserialization error"; diff --git a/ballerina/init.bal b/ballerina/init.bal new file mode 100644 index 0000000..836076b --- /dev/null +++ b/ballerina/init.bal @@ -0,0 +1,25 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you 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 +// +// http://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. + +import ballerina/jballerina.java; + +function init() { + setModule(); +} + +function setModule() = @java:Method { + 'class: "io.ballerina.lib.confluent.avro.serdes.ModuleUtils" +} external; From ad4f182c66d86c2fc5f71d6fa4554c7452ecc382 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 13:40:54 +0530 Subject: [PATCH 05/21] Add client APIs to the project --- ballerina/Dependencies.toml | 369 ++++++++++++++++++++++++++++++++++++ ballerina/client.bal | 69 +++++++ 2 files changed, 438 insertions(+) create mode 100644 ballerina/Dependencies.toml create mode 100644 ballerina/client.bal diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml new file mode 100644 index 0000000..e844b6f --- /dev/null +++ b/ballerina/Dependencies.toml @@ -0,0 +1,369 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.8.6" + +[[package]] +org = "ballerina" +name = "auth" +version = "2.10.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "avro" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "avro", moduleName = "avro"} +] + +[[package]] +org = "ballerina" +name = "cache" +version = "3.7.1" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "task"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "constraint" +version = "1.4.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "crypto" +version = "2.5.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "file" +version = "1.9.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "os"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "http" +version = "2.10.12" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "file"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "jwt"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.decimal"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.regexp"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "observe"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "http", moduleName = "http"}, + {org = "ballerina", packageName = "http", moduleName = "http.httpscerr"} +] + +[[package]] +org = "ballerina" +name = "io" +version = "1.6.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" +modules = [ + {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "jwt" +version = "2.10.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "lang.__internal" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.array" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"} +] + +[[package]] +org = "ballerina" +name = "lang.decimal" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.error" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.int" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.object" +version = "0.0.0" +scope = "testOnly" + +[[package]] +org = "ballerina" +name = "lang.regexp" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.string" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.regexp"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "log" +version = "2.9.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "observe"} +] + +[[package]] +org = "ballerina" +name = "mime" +version = "2.9.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"} +] + +[[package]] +org = "ballerina" +name = "oauth2" +version = "2.10.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] + +[[package]] +org = "ballerina" +name = "observe" +version = "1.2.2" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "os" +version = "1.8.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "task" +version = "2.5.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "test" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.error"} +] +modules = [ + {org = "ballerina", packageName = "test", moduleName = "test"} +] + +[[package]] +org = "ballerina" +name = "time" +version = "2.4.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "url" +version = "2.4.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerinai" +name = "observe" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "observe"} +] +modules = [ + {org = "ballerinai", packageName = "observe", moduleName = "observe"} +] + +[[package]] +org = "ballerinax" +name = "confluent.cavroserdes" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "avro"}, + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "test"}, + {org = "ballerinai", name = "observe"}, + {org = "ballerinax", name = "confluent.cregistry"} +] +modules = [ + {org = "ballerinax", packageName = "confluent.cavroserdes", moduleName = "confluent.cavroserdes"} +] + +[[package]] +org = "ballerinax" +name = "confluent.cregistry" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerinax", packageName = "confluent.cregistry", moduleName = "confluent.cregistry"} +] + diff --git a/ballerina/client.bal b/ballerina/client.bal new file mode 100644 index 0000000..b0f114e --- /dev/null +++ b/ballerina/client.bal @@ -0,0 +1,69 @@ +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you 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 +// +// http://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. + +import ballerina/avro; +import ballerina/jballerina.java; +import ballerinax/confluent.cregistry; + +# Consists of APIs to integrate with Avro Serializer/Deserializer for Confluent Schema Registry. +public isolated client class Client { + private final cregistry:Client schemaClient; + + public isolated function init(*cregistry:ConnectionConfig config) returns Error? { + cregistry:Client|error schemaClient = new (config); + if schemaClient is error { + return error Error("Client invocation error", schemaClient); + } + self.schemaClient = schemaClient; + } + + remote isolated function serialize(string schema, anydata data, string topic) returns byte[]|Error { + do { + int id = check self.schemaClient->register(topic, schema); + byte[] encodedId = check self.toBytes(id); + avro:Schema avroClient = check new (schema); + byte[] serializedData = check avroClient.toAvro(data); + return [...encodedId, ...serializedData]; + } on fail error e { + return error Error(SERIALIZATION_ERROR, e); + } + } + + remote isolated function deserialize(byte[] data, typedesc T = <>) + returns T|Error = @java:Method { + 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" + } external; + + private isolated function toBytes(int id) returns byte[]|error = @java:Method { + 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" + } external; + + private isolated function getId(byte[] bytes) returns int = @java:Method { + 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" + } external; + + public isolated function deserializeData(byte[] data) returns anydata|Error { + do { + int schemaId = self.getId(data.slice(0, 4)); + string retrievedSchema = check self.schemaClient->getSchemaById(schemaId); + avro:Schema avroClient = check new (retrievedSchema); + anydata deserializedData = check avroClient.fromAvro(data.slice(4, data.length())); + return deserializedData; + } on fail error e { + return error Error(DESERIALIZATION_ERROR, e); + } + } +} From bf8972ac22399c3c5d4de27b978db6c2b318d97e Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 13:41:16 +0530 Subject: [PATCH 06/21] Add .gitignore file --- .gitignore | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 524f096..e1163f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,68 @@ # Compiled class file *.class +*.balx # Log file *.log +#Config file +Config.toml +**/Config.toml + # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ +# .DS_Store files +*.DS_Store + # Package Files # *.jar +!gradle/wrapper/gradle-wrapper.jar *.war -*.nar *.ear *.zip *.tar.gz *.rar +*.deb # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* -replay_pid* + +# Ignore everything in this directory +target +.idea +.classpath +.settings +.project +*.iml +*.iws +*.ipr +.idea +.m2 +.vscode/ +# Ignore ballerina files +accessToken.bal +temp.bal.ballerina/ +target/ +.DS_Store +*Ballerina.lock +.ballerina + +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore Gradle build output directory +build +generated + +# Ignore Docker env file +docker.env + +# Ignore environment files +*.env + +# Ignore all Dependencies.toml files in the examples directory +examples/**/Dependencies.toml From 7b6e6ac032696374ca9a9eba0ce302929ea6de8c Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 13:41:36 +0530 Subject: [PATCH 07/21] Add native APIs to the project --- .../confluent/avro/serdes/AvroSerializer.java | 66 +++++++++++++++++++ .../avro/serdes/ExecutionCallback.java | 47 +++++++++++++ .../confluent/avro/serdes/ModuleUtils.java | 36 ++++++++++ native/src/main/java/module-info.java | 23 +++++++ 4 files changed, 172 insertions(+) create mode 100644 native/src/main/java/io/ballerina/lib/confluent/avro/serdes/AvroSerializer.java create mode 100644 native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java create mode 100644 native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ModuleUtils.java create mode 100644 native/src/main/java/module-info.java diff --git a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/AvroSerializer.java b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/AvroSerializer.java new file mode 100644 index 0000000..2c7b5d5 --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/AvroSerializer.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you 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 + * + * http://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. + */ + +package io.ballerina.lib.confluent.avro.serdes; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.Future; +import io.ballerina.runtime.api.PredefinedTypes; +import io.ballerina.runtime.api.async.StrandMetadata; +import io.ballerina.runtime.api.creators.TypeCreator; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.types.UnionType; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BTypedesc; + +import java.nio.ByteBuffer; + +import static io.ballerina.lib.confluent.avro.serdes.ModuleUtils.getModule; + +public class AvroSerializer { + private static final String FUNCTION = "deserializeData"; + + public static final StrandMetadata EXECUTION_STRAND = new StrandMetadata( + getModule().getOrg(), + getModule().getName(), + getModule().getMajorVersion(), + FUNCTION); + + public static Object deserialize(Environment env, BObject kafkaSerDes, BArray data, + BTypedesc typeDesc) { + Future future = env.markAsync(); + ExecutionCallback executionCallback = new ExecutionCallback(future, typeDesc); + Object[] arguments = new Object[]{data, true}; + UnionType typeUnion = TypeCreator.createUnionType(PredefinedTypes.TYPE_ANYDATA_ARRAY, + PredefinedTypes.TYPE_ERROR); + env.getRuntime() + .invokeMethodAsyncConcurrently(kafkaSerDes, FUNCTION, null, EXECUTION_STRAND, + executionCallback, null, typeUnion, arguments); + return null; + } + + public static BArray toBytes(int id) { + byte[] value = ByteBuffer.allocate(4).putInt(id).array(); + return ValueCreator.createArrayValue(value); + } + + public static int getId(BArray bytes) { + return ByteBuffer.wrap(bytes.getByteArray()).getInt(); + } +} diff --git a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java new file mode 100644 index 0000000..642999b --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you 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 + * + * http://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. + */ + +package io.ballerina.lib.confluent.avro.serdes; + +import io.ballerina.runtime.api.Future; +import io.ballerina.runtime.api.async.Callback; +import io.ballerina.runtime.api.utils.JsonUtils; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.utils.ValueUtils; +import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BTypedesc; + +public class ExecutionCallback implements Callback { + private final Future future; + private final BTypedesc typeDesc; + + ExecutionCallback(Future future, BTypedesc typeDesc) { + this.future = future; + this.typeDesc = typeDesc; + } + @Override + public void notifySuccess(Object o) { + Object jsonObject = JsonUtils.parse(StringUtils.getJsonString(o)); + this.future.complete(ValueUtils.convert(jsonObject, typeDesc.getDescribingType())); + } + + @Override + public void notifyFailure(BError bError) { + this.future.complete(bError); + } +} diff --git a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ModuleUtils.java b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ModuleUtils.java new file mode 100644 index 0000000..97da007 --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ModuleUtils.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you 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 + * + * http://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. + */ + +package io.ballerina.lib.confluent.avro.serdes; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.Module; + +public class ModuleUtils { + private static Module module; + + private ModuleUtils() {} + + public static void setModule(Environment environment) { + module = environment.getCurrentModule(); + } + + public static Module getModule() { + return module; + } +} diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java new file mode 100644 index 0000000..c87014f --- /dev/null +++ b/native/src/main/java/module-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you 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 + * + * http://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. + */ + +module io.ballerina.stdlib.serdes { + requires io.ballerina.runtime; + requires io.ballerina.lang; + requires org.apache.avro; +} From 7263a7f80011980ed8bf4daa487f6d7509c266cb Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 13:41:57 +0530 Subject: [PATCH 08/21] Add test cases to validate APIs --- ballerina/tests/mock_service.bal | 40 +++++++++ ballerina/tests/tests.bal | 134 +++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 ballerina/tests/mock_service.bal create mode 100644 ballerina/tests/tests.bal diff --git a/ballerina/tests/mock_service.bal b/ballerina/tests/mock_service.bal new file mode 100644 index 0000000..51d60fe --- /dev/null +++ b/ballerina/tests/mock_service.bal @@ -0,0 +1,40 @@ +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you 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 +// +// http://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. + +import ballerina/http; + +type Schema readonly & record {| + int id; + string schema; +|}; + +service /registry on new http:Listener(9092) { + resource function post subjects/[string subject]/versions() returns http:Response|error { + http:Response res = new; + _ = check res.setContentType("application/vnd.schemaregistry.v1+json"); + res.setPayload({"id": 1002}); + return res; + } + + resource function get schemas/ids/[int id]() returns http:Response|error { + http:Response res = new; + res.statusCode = 404; + _ = check res.setContentType("application/vnd.schemaregistry.v1+json"); + string message = string `Schema ${id} not found`; + res.setPayload({"error_code":40403,"message": message}); + return res; + } +} diff --git a/ballerina/tests/tests.bal b/ballerina/tests/tests.bal new file mode 100644 index 0000000..28cacdb --- /dev/null +++ b/ballerina/tests/tests.bal @@ -0,0 +1,134 @@ +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you 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 +// +// http://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. + +import ballerina/test; + +configurable string baseUrl = ?; +configurable int identityMapCapacity = ?; +configurable map originals = ?; +configurable map headers = ?; + +Client avroSerDesClient = check new ({ + baseUrl, + identityMapCapacity, + originals, + headers +}); + +@test:Config {} +public function testSerDes() returns error? { + string schema = string ` + { + "namespace": "example.avro", + "type": "record", + "name": "Student", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "colors", "type": {"type": "array", "items": "string"}} + ] + }`; + + Color colors = { + name: "Red", + colors: ["maroon", "dark red", "light red"] + }; + + byte[] bytes = check avroSerDesClient->serialize(schema, colors, "subject-0"); + Color getColors = check avroSerDesClient->deserialize(bytes); + test:assertEquals(getColors, colors); +} + +@test:Config {} +public function testWithRecords() returns error? { + string schema = string ` + { + "namespace": "example.avro", + "type": "record", + "name": "Student", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "subject", "type": "string"} + ] + }`; + + Student student = { + name: "John", + subject: "Math" + }; + + byte[] bytes = check avroSerDesClient->serialize(schema, student, "subject-1"); + Student getStudent = check avroSerDesClient->deserialize(bytes); + test:assertEquals(getStudent, student); +} + +@test:Config {} +public function testSerDesWithInteger() returns error? { + string schema = string ` + { + "type": "int", + "name" : "intValue", + "namespace": "data" + }`; + + int value = 1; + + byte[] bytes = check avroSerDesClient->serialize(schema, value, "subject-5"); + int getValue = check avroSerDesClient->deserialize(bytes); + test:assertEquals(getValue, value); +} + +@test:Config {} +public function testSerDesWithCourse() returns error? { + string schema = string ` + { + "namespace": "example.avro", + "type": "record", + "name": "Course", + "fields": [ + {"name": "name", "type": ["null", "string"]}, + {"name": "credits", "type": ["null", "int"]} + ] + }`; + + Course course = { + name: "Physics", + credits: 3 + }; + + byte[] bytes = check avroSerDesClient->serialize(schema, course, "subject-3"); + Course getCourse = check avroSerDesClient->deserialize(bytes); + test:assertEquals(getCourse, course); +} + +public type Student record { + string name; + string subject; +}; + +public type Person record { + string name; + int age; +}; + +public type Course record { + string? name; + int? credits; +}; + +public type Color record { + string? name; + string[] colors; +}; From 1c0176a7bb598db91edeb4111710e56e2ab6a17d Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 13:42:04 +0530 Subject: [PATCH 09/21] Add code owners --- .github/CODEOWNERS | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..2b11434 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# Lines starting with '#' are comments. +# Each line is a file pattern followed by one or more owners. + +# See: https://help.github.com/articles/about-codeowners/ + +# These owners will be the default owners for everything in the repo. +* @Nuvindu @ThisaruGuruge @shafreenAnfar From 3cc2880acb8080ba509f934cddfab3e75114e243 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 13:42:17 +0530 Subject: [PATCH 10/21] Add documentation files to the project --- README.md | 161 ++++++++++++++++++++++++++++++++++++++++++- ballerina/Module.md | 52 ++++++++++++++ ballerina/Package.md | 52 ++++++++++++++ 3 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 ballerina/Module.md create mode 100644 ballerina/Package.md diff --git a/README.md b/README.md index f848084..7dab306 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,159 @@ -# module-ballerinax-confluent.cavroserdes -The Ballerina Avro Ser/Des package for Confluent provides APIs which will hide the complexities in serializing and deserializing data using Avro schemas along with the registration and retrieval of these schemas from the schema registry +# Ballerina Confluent Avro Serialization/Deserialization Connector + +[![Build](https://github.com/ballerina-platform/module-ballerinax-confluent.cavroserdes/actions/workflows/ci.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-confluent.cavroserdes/actions/workflows/ci.yml) +[![Trivy](https://github.com/ballerina-platform/module-ballerinax-confluent.cavroserdes/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-confluent.cavroserdes/actions/workflows/trivy-scan.yml) +[![GraalVM Check](https://github.com/ballerina-platform/module-ballerinax-confluent.cavroserdes/actions/workflows/build-with-bal-test-graalvm.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerinax-confluent.cavroserdes/actions/workflows/build-with-bal-test-graalvm.yml) +[![GitHub Last Commit](https://img.shields.io/github/last-commit/ballerina-platform/module-ballerinax-confluent.cavroserdes.svg)](https://github.com/ballerina-platform/module-ballerinax-confluent.cavroserdes/commits/main) +[![GitHub Issues](https://img.shields.io/github/issues/ballerina-platform/ballerina-library/module/confluent.cavroserdes.svg?label=Open%20Issues)](https://github.com/ballerina-platform/ballerina-library/labels/module%2Fconfluent.cavroserdes) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +[Avro Serializer/Deserializer for Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/serdes-avro.html) is an Avro serializer/deserializer designed to work with the Confluent Schema Registry. It is designed to not include the message schema in the message payload but instead includes the schema ID. + +The Ballerina Avro Serializer/Deserializer for Confluent Schema Registry connector integrates with the Confluent Schema Registry for Avro serialization and deserialization. + +## Quickstart + +To use the Confluent schema registry connector in your Ballerina project, modify the `.bal` file as follows. + +### Step 1: Import the module + +Import the `ballerinax/confluent.cavroserdes` module into your Ballerina project. + +```ballerina +import ballerinax/confluent.cavroserdes; +``` + +### Step 2: Instantiate a new connector + +```ballerina +configurable string baseUrl = ?; +configurable int identityMapCapacity = ?; +configurable map originals = ?; +configurable map headers = ?; + +cavroserdes:Client avroSerDes = check new ({ + baseUrl, + identityMapCapacity, + originals, + headers +}); +``` + +### Step 3: Invoke the connector operation + +You can now utilize the operations available within the connector. + +```ballerina +public function main() returns error? { + string schema = string ` + { + "type": "int", + "name" : "value", + "namespace": "data" + }`; + + int value = 1; + byte[] bytes = check avroSerDes.serialize(schema, value, "subject"); + int number = check avroSerDesClient.deserialize(bytes); +} +``` + +### Step 4: Run the Ballerina application + +Use the following command to compile and run the Ballerina program. + +```bash +bal run +``` + +## Issues and projects + +The **Issues** and **Projects** tabs are disabled for this repository as this is part of the Ballerina library. To report bugs, request new features, start new discussions, view project boards, etc., visit the Ballerina library [parent repository](https://github.com/ballerina-platform/ballerina-library). + +This repository only contains the source code for the package. + +## Building from the source + +### Prerequisites + +1. Download and install Java SE Development Kit (JDK) version 17. You can download it from either of the following sources: + + - [Oracle JDK](https://www.oracle.com/java/technologies/downloads/) + - [OpenJDK](https://adoptium.net/) + + > **Note:** After installation, remember to set the `JAVA_HOME` environment variable to the directory where JDK was installed. + +2. Download and install [Ballerina Swan Lake](https://ballerina.io/). + +3. Download and install [Docker](https://www.docker.com/get-started). + + > **Note**: Ensure that the Docker daemon is running before executing any tests. + +4. Generate a Github access token with read package permissions, then set the following `env` variables: + + ```bash + export packageUser= + export packagePAT= + ``` + +### Build options + +Execute the commands below to build from the source. + +1. To build the package: + + ```bash + ./gradlew clean build + ``` + +2. To run the tests: + + ```bash + ./gradlew clean test + ``` + +3. To build the without the tests: + + ```bash + ./gradlew clean build -x test + ``` + +4. To debug package with a remote debugger: + + ```bash + ./gradlew clean build -Pdebug= + ``` + +5. To debug with Ballerina language: + + ```bash + ./gradlew clean build -PbalJavaDebug= + ``` + +6. Publish the generated artifacts to the local Ballerina central repository: + + ```bash + ./gradlew clean build -PpublishToLocalCentral=true + ``` + +7. Publish the generated artifacts to the Ballerina central repository: + + ```bash + ./gradlew clean build -PpublishToCentral=true + ``` + +## Contributing to Ballerina + +As an open source project, Ballerina welcomes contributions from the community. + +For more information, go to the [contribution guidelines](https://github.com/ballerina-platform/ballerina-lang/blob/master/CONTRIBUTING.md). + +## Code of conduct + +All contributors are encouraged to read the [Ballerina Code of Conduct](https://ballerina.io/code-of-conduct). + +## Useful links + +- Discuss code changes of the Ballerina project in [ballerina-dev@googlegroups.com](mailto:ballerina-dev@googlegroups.com). +- Chat live with us via our [Discord server](https://discord.gg/ballerinalang). +- Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag. diff --git a/ballerina/Module.md b/ballerina/Module.md new file mode 100644 index 0000000..e6f5f20 --- /dev/null +++ b/ballerina/Module.md @@ -0,0 +1,52 @@ +## Overview + +[Avro Serializer/Deserializer for Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/serdes-avro.html) is an Avro serializer/deserializer designed to work with the Confluent Schema Registry. It is designed to not include the message schema in the message payload but instead includes the schema ID. + +The Ballerina Avro Serializer/Deserializer for Confluent Schema Registry connector integrates with the Confluent Schema Registry for Avro serialization and deserialization. + +## Quickstart + +To use the Confluent schema registry connector in your Ballerina project, modify the `.bal` file as follows. + +### Step 1: Import the module + +Import the `ballerinax/confluent.cavroserdes` module into your Ballerina project. + +```ballerina +import ballerinax/confluent.cavroserdes; +``` + +### Step 2: Instantiate a new connector + +```ballerina +configurable string baseUrl = ?; +configurable int identityMapCapacity = ?; +configurable map originals = ?; +configurable map headers = ?; + +cavroserdes:Client avroSerDes = check new ({ + baseUrl, + identityMapCapacity, + originals, + headers +}); +``` + +### Step 3: Invoke the connector operation + +You can now utilize the operations available within the connector. + +```ballerina +public function main() returns error? { + string schema = string ` + { + "type": "int", + "name" : "value", + "namespace": "data" + }`; + + int value = 1; + byte[] bytes = check avroSerDes.serialize(schema, value, "subject"); + int number = check avroSerDesClient.deserialize(bytes); +} +``` diff --git a/ballerina/Package.md b/ballerina/Package.md new file mode 100644 index 0000000..b110a7b --- /dev/null +++ b/ballerina/Package.md @@ -0,0 +1,52 @@ +## Package overview + +[Avro Serializer/Deserializer for Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/serdes-avro.html) is an Avro serializer/deserializer designed to work with the Confluent Schema Registry. It is designed to not include the message schema in the message payload but instead includes the schema ID. + +The Ballerina Avro Serializer/Deserializer for Confluent Schema Registry connector integrates with the Confluent Schema Registry for Avro serialization and deserialization. + +## Quickstart + +To use the Confluent schema registry connector in your Ballerina project, modify the `.bal` file as follows. + +### Step 1: Import the module + +Import the `ballerinax/confluent.cavroserdes` module into your Ballerina project. + +```ballerina +import ballerinax/confluent.cavroserdes; +``` + +### Step 2: Instantiate a new connector + +```ballerina +configurable string baseUrl = ?; +configurable int identityMapCapacity = ?; +configurable map originals = ?; +configurable map headers = ?; + +cavroserdes:Client avroSerDes = check new ({ + baseUrl, + identityMapCapacity, + originals, + headers +}); +``` + +### Step 3: Invoke the connector operation + +You can now utilize the operations available within the connector. + +```ballerina +public function main() returns error? { + string schema = string ` + { + "type": "int", + "name" : "value", + "namespace": "data" + }`; + + int value = 1; + byte[] bytes = check avroSerDes.serialize(schema, value, "subject"); + int number = check avroSerDesClient.deserialize(bytes); +} +``` From f747ed3cb384ce1e5e5cf78cbb4258d74195af70 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 14:14:24 +0530 Subject: [PATCH 11/21] Add Config.toml for test cases --- ballerina/tests/Config.toml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 ballerina/tests/Config.toml diff --git a/ballerina/tests/Config.toml b/ballerina/tests/Config.toml new file mode 100644 index 0000000..f739ab6 --- /dev/null +++ b/ballerina/tests/Config.toml @@ -0,0 +1,6 @@ +baseUrl = "http://localhost:9092/registry" +identityMapCapacity = 1000 + +[originals] + +[headers] From fb58a13cee1e7a70ca004281966ef252c91529b9 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 14:15:25 +0530 Subject: [PATCH 12/21] Add magic byte to the serialized data --- ballerina/client.bal | 4 ++-- .../ballerina/lib/confluent/avro/serdes/AvroSerializer.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ballerina/client.bal b/ballerina/client.bal index b0f114e..c8f4563 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -57,10 +57,10 @@ public isolated client class Client { public isolated function deserializeData(byte[] data) returns anydata|Error { do { - int schemaId = self.getId(data.slice(0, 4)); + int schemaId = self.getId(data.slice(1, 5)); string retrievedSchema = check self.schemaClient->getSchemaById(schemaId); avro:Schema avroClient = check new (retrievedSchema); - anydata deserializedData = check avroClient.fromAvro(data.slice(4, data.length())); + anydata deserializedData = check avroClient.fromAvro(data.slice(5, data.length())); return deserializedData; } on fail error e { return error Error(DESERIALIZATION_ERROR, e); diff --git a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/AvroSerializer.java b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/AvroSerializer.java index 2c7b5d5..1c48800 100644 --- a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/AvroSerializer.java +++ b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/AvroSerializer.java @@ -56,7 +56,7 @@ public static Object deserialize(Environment env, BObject kafkaSerDes, BArray da } public static BArray toBytes(int id) { - byte[] value = ByteBuffer.allocate(4).putInt(id).array(); + byte[] value = ByteBuffer.allocate(5).put((byte) 0).putInt(id).array(); return ValueCreator.createArrayValue(value); } From 3a91b8df11886db2dbc82af144f77138f47edf47 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 14:17:06 +0530 Subject: [PATCH 13/21] Add API docs to client APIs --- ballerina/client.bal | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/ballerina/client.bal b/ballerina/client.bal index c8f4563..7856e50 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -30,9 +30,15 @@ public isolated client class Client { self.schemaClient = schemaClient; } - remote isolated function serialize(string schema, anydata data, string topic) returns byte[]|Error { + # Serializes the given data according to the Avro format and registers the schema into the schema registry. + # + # + schema - The Avro schema + # + data - The data to be serialized according to the schema + # + subject - The subject under which the schema should be registered + # + return - A `byte` array of the serialized data or else an `cavroserdes:Error` + remote isolated function serialize(string schema, anydata data, string subject) returns byte[]|Error { do { - int id = check self.schemaClient->register(topic, schema); + int id = check self.schemaClient->register(subject, schema); byte[] encodedId = check self.toBytes(id); avro:Schema avroClient = check new (schema); byte[] serializedData = check avroClient.toAvro(data); @@ -42,8 +48,14 @@ public isolated client class Client { } } - remote isolated function deserialize(byte[] data, typedesc T = <>) - returns T|Error = @java:Method { + # Deserializes the given Avro serialized message to the given data type by retrieving the schema + # from the schema registry. + # + # + data - Avro serialized data which includes the schema id + # + targetType - Default parameter use to infer the user specified type + # + return - A deserialized data with the given type or else an `cavroserdes:Error` + remote isolated function deserialize(byte[] data, typedesc targetType = <>) + returns targetType|Error = @java:Method { 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" } external; @@ -55,7 +67,7 @@ public isolated client class Client { 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" } external; - public isolated function deserializeData(byte[] data) returns anydata|Error { + private isolated function deserializeData(byte[] data) returns anydata|Error { do { int schemaId = self.getId(data.slice(1, 5)); string retrievedSchema = check self.schemaClient->getSchemaById(schemaId); From c12a012ae0a51941ab85c78371bead535f316d13 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 14:26:38 +0530 Subject: [PATCH 14/21] Add specification for the package --- docs/spec/spec.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 docs/spec/spec.md diff --git a/docs/spec/spec.md b/docs/spec/spec.md new file mode 100644 index 0000000..13b85f8 --- /dev/null +++ b/docs/spec/spec.md @@ -0,0 +1,93 @@ +# Specification: Ballerina Avro Serializer/Deserializer for Confluent Schema Registry Library + +_Authors_: @Nuvindu \ +_Reviewers_: @ThisaruGuruge \ +_Created_: 2024/04/10 \ +_Updated_: 2024/04/10 \ +_Edition_: Swan Lake + +## Introduction + +The Ballerina Avro Serializer/Deserializer for Confluent Schema Registry module facilitates the integration with Confluent's Schema Registry and Avro. It offers capabilities to serialize and deserialize Avro data using the schemas stored in the registry. + +The Avro Serializer/Deserializer library specification has evolved and may continue to evolve in the future. The released versions of the specification can be found under the relevant GitHub tag. + +For feedback or suggestions about the library, please initiate a discussion via a [GitHub issue](https://github.com/ballerina-platform/ballerina-library/issues) or in the [Discord server](https://discord.gg/ballerinalang). The specification and implementation can be updated based on the discussion's outcome. Community feedback is highly appreciated. Accepted proposals that affect the specification are stored under `/docs/proposals`. Proposals under discussion can be found with the label `type/proposal` in GitHub. + +The conforming implementation of the specification is released and included in the distribution. Any deviation from the specification is considered a bug. + +## Contents + +1. [Overview](#1-overview) +2. [Initialize the Avro Serializer/Deserializer client](#2-initialize-the-avro-serializerdeserializer-client) + * 2.1 [The `init` method](#21-the-init-method) +3. [Serialize data](#3-serialize-data) + * 3.1 [The `serialize` API](#31-the-serialize-api) +4. [Deserialize data](#4-deserialize-data) + * 4.1 [The `deserialize` API](#41-the-deserialize-api) +5. [The `cavroserdes:Error` Type](#5-the-cavroserdeserror-type) + +## 1. Overview + +This specification provides a detailed explanation of the functionalities offered by the Ballerina Avro Serializer/Deserializer for Confluent Schema Registry module. The module provides the following capabilities. + +1. Serialize data +2. Deserialize data + +## 2. Initialize the Avro Serializer/Deserializer client + +The Avro Serializer/Deserializer client needs to be initialized before performing the functionalities. + +### 2.1 The `init` method + +The `init` method initializes the Avro Serializer/Deserializer client. It takes a `config` parameter, which contains the necessary configuration to connect to the Schema Registry. The method returns an error if the initialization fails. + +```ballerina +configurable string baseUrl = ?; +configurable int identityMapCapacity = ?; +configurable map originals = ?; +configurable map headers = ?; + +cavroserdes:Client avroSerDes = check new ({ + baseUrl, + identityMapCapacity, + originals, + headers +}); +``` + +## 3. Serialize data + +The Avro Serializer/Deserializer module allows serializing data to Avro format using the schemas from the registry. + +### 3.1 The `serialize` API + +The `serialize` method serializes a given value to Avro format using the provided schema and subject. Here the new schemas added on a particular `subject` must have backward compatibility with previous versions of schemas on that `subject`. + +```ballerina +string schema = string ` +{ + "type": "int", + "name" : "value", + "namespace": "data" +}`; + +int value = 1; +byte[] bytes = check avroSerDes.serialize(schema, value, "subject"); +``` + +## 4. Deserialize data + +This section details the process of deserializing Avro data using the schemas from the registry. + +### 4.1 The `deserialize` API + +The `deserialize` method deserializes a given Avro data to its original form using the schema from the registry. + +```ballerina +int number = check avroSerDes.deserialize(bytes); +``` + +## 5. The `cavroserdes:Error` Type + +The `cavroserdes:Error` type represents all the errors related to the Avro Serializer/Deserializer for Confluent Schema Registry module. This is a subtype of the Ballerina `error` type. From 0fca32b45cc1e6ccca077069fa1d9b01255ed71b Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 14:40:57 +0530 Subject: [PATCH 15/21] Apply suggestions from the review --- README.md | 5 +++-- ballerina/Module.md | 12 ++++++++++-- ballerina/Package.md | 12 ++++++++++-- ballerina/client.bal | 6 +++--- ballerina/tests/tests.bal | 18 +++++++++--------- 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 7dab306..be345ab 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ public function main() returns error? { }`; int value = 1; - byte[] bytes = check avroSerDes.serialize(schema, value, "subject"); - int number = check avroSerDesClient.deserialize(bytes); + byte[] bytes = check avroSerDes->serialize(schema, value, "subject"); + int number = check avroSerDes->deserialize(bytes); } ``` @@ -154,6 +154,7 @@ All contributors are encouraged to read the [Ballerina Code of Conduct](https:// ## Useful links +- For more information go to the [confluent.cavroderdes](https://central.ballerina.io/ballerinax/confluent.cavroserdes/latest) library. - Discuss code changes of the Ballerina project in [ballerina-dev@googlegroups.com](mailto:ballerina-dev@googlegroups.com). - Chat live with us via our [Discord server](https://discord.gg/ballerinalang). - Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag. diff --git a/ballerina/Module.md b/ballerina/Module.md index e6f5f20..3b2ce46 100644 --- a/ballerina/Module.md +++ b/ballerina/Module.md @@ -46,7 +46,15 @@ public function main() returns error? { }`; int value = 1; - byte[] bytes = check avroSerDes.serialize(schema, value, "subject"); - int number = check avroSerDesClient.deserialize(bytes); + byte[] bytes = check avroSerDes->serialize(schema, value, "subject"); + int number = check avroSerDes->deserialize(bytes); } ``` + +### Step 4: Run the Ballerina application + +Use the following command to compile and run the Ballerina program. + +```bash +bal run +``` diff --git a/ballerina/Package.md b/ballerina/Package.md index b110a7b..e592d4b 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -46,7 +46,15 @@ public function main() returns error? { }`; int value = 1; - byte[] bytes = check avroSerDes.serialize(schema, value, "subject"); - int number = check avroSerDesClient.deserialize(bytes); + byte[] bytes = check avroSerDes->serialize(schema, value, "subject"); + int number = check avroSerDes->deserialize(bytes); } ``` + +### Step 4: Run the Ballerina application + +Use the following command to compile and run the Ballerina program. + +```bash +bal run +``` diff --git a/ballerina/client.bal b/ballerina/client.bal index 7856e50..d504b7e 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -59,15 +59,15 @@ public isolated client class Client { 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" } external; - private isolated function toBytes(int id) returns byte[]|error = @java:Method { + isolated function toBytes(int id) returns byte[]|error = @java:Method { 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" } external; - private isolated function getId(byte[] bytes) returns int = @java:Method { + isolated function getId(byte[] bytes) returns int = @java:Method { 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" } external; - private isolated function deserializeData(byte[] data) returns anydata|Error { + isolated function deserializeData(byte[] data) returns anydata|Error { do { int schemaId = self.getId(data.slice(1, 5)); string retrievedSchema = check self.schemaClient->getSchemaById(schemaId); diff --git a/ballerina/tests/tests.bal b/ballerina/tests/tests.bal index 28cacdb..f382d21 100644 --- a/ballerina/tests/tests.bal +++ b/ballerina/tests/tests.bal @@ -21,7 +21,7 @@ configurable int identityMapCapacity = ?; configurable map originals = ?; configurable map headers = ?; -Client avroSerDesClient = check new ({ +Client avroSerDes = check new ({ baseUrl, identityMapCapacity, originals, @@ -46,8 +46,8 @@ public function testSerDes() returns error? { colors: ["maroon", "dark red", "light red"] }; - byte[] bytes = check avroSerDesClient->serialize(schema, colors, "subject-0"); - Color getColors = check avroSerDesClient->deserialize(bytes); + byte[] bytes = check avroSerDes->serialize(schema, colors, "subject-0"); + Color getColors = check avroSerDes->deserialize(bytes); test:assertEquals(getColors, colors); } @@ -69,8 +69,8 @@ public function testWithRecords() returns error? { subject: "Math" }; - byte[] bytes = check avroSerDesClient->serialize(schema, student, "subject-1"); - Student getStudent = check avroSerDesClient->deserialize(bytes); + byte[] bytes = check avroSerDes->serialize(schema, student, "subject-1"); + Student getStudent = check avroSerDes->deserialize(bytes); test:assertEquals(getStudent, student); } @@ -85,8 +85,8 @@ public function testSerDesWithInteger() returns error? { int value = 1; - byte[] bytes = check avroSerDesClient->serialize(schema, value, "subject-5"); - int getValue = check avroSerDesClient->deserialize(bytes); + byte[] bytes = check avroSerDes->serialize(schema, value, "subject-5"); + int getValue = check avroSerDes->deserialize(bytes); test:assertEquals(getValue, value); } @@ -108,8 +108,8 @@ public function testSerDesWithCourse() returns error? { credits: 3 }; - byte[] bytes = check avroSerDesClient->serialize(schema, course, "subject-3"); - Course getCourse = check avroSerDesClient->deserialize(bytes); + byte[] bytes = check avroSerDes->serialize(schema, course, "subject-3"); + Course getCourse = check avroSerDes->deserialize(bytes); test:assertEquals(getCourse, course); } From faf663e86e34a71d3c787edf1676b1a9f50ed144 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 14:59:07 +0530 Subject: [PATCH 16/21] [Automated] Update the toml files --- ballerina/Ballerina.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index ac614f3..2d35876 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -5,7 +5,7 @@ version = "0.1.0" authors = ["Ballerina"] export=["confluent.cavroserdes"] keywords = ["confluent", "schema_registry", "avro", "serdes"] -repository = "https://github.com/ballerina-platform/module-ballerinax-confluent.cregistry" +repository = "https://github.com/ballerina-platform/module-ballerinax-confluent.cavroserdes" license = ["Apache-2.0"] distribution = "2201.8.6" From 0e9820ed6a959776fdec91f8df2d5f6d2dfa96c2 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 15:00:04 +0530 Subject: [PATCH 17/21] Fix review comments --- build-config/resources/Ballerina.toml | 2 +- .../lib/confluent/avro/serdes/AvroSerializer.java | 11 +++++++---- .../lib/confluent/avro/serdes/ExecutionCallback.java | 6 +++++- .../lib/confluent/avro/serdes/ModuleUtils.java | 6 +++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index 4287cea..05c38e0 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -5,7 +5,7 @@ version = "@toml.version@" authors = ["Ballerina"] export=["confluent.cavroserdes"] keywords = ["confluent", "schema_registry", "avro", "serdes"] -repository = "https://github.com/ballerina-platform/module-ballerinax-confluent.cregistry" +repository = "https://github.com/ballerina-platform/module-ballerinax-confluent.cavroserdes" license = ["Apache-2.0"] distribution = "2201.8.6" diff --git a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/AvroSerializer.java b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/AvroSerializer.java index 1c48800..51fc20e 100644 --- a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/AvroSerializer.java +++ b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/AvroSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -33,14 +33,17 @@ import static io.ballerina.lib.confluent.avro.serdes.ModuleUtils.getModule; +/** + * Provide APIs related to Avro Serialization/Deserialization with the Schema Registry. + */ public class AvroSerializer { - private static final String FUNCTION = "deserializeData"; + private static final String DESERIALIZE_FUNCTION = "deserializeData"; public static final StrandMetadata EXECUTION_STRAND = new StrandMetadata( getModule().getOrg(), getModule().getName(), getModule().getMajorVersion(), - FUNCTION); + DESERIALIZE_FUNCTION); public static Object deserialize(Environment env, BObject kafkaSerDes, BArray data, BTypedesc typeDesc) { @@ -50,7 +53,7 @@ public static Object deserialize(Environment env, BObject kafkaSerDes, BArray da UnionType typeUnion = TypeCreator.createUnionType(PredefinedTypes.TYPE_ANYDATA_ARRAY, PredefinedTypes.TYPE_ERROR); env.getRuntime() - .invokeMethodAsyncConcurrently(kafkaSerDes, FUNCTION, null, EXECUTION_STRAND, + .invokeMethodAsyncConcurrently(kafkaSerDes, DESERIALIZE_FUNCTION, null, EXECUTION_STRAND, executionCallback, null, typeUnion, arguments); return null; } diff --git a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java index 642999b..93a8bb7 100644 --- a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java +++ b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -26,6 +26,9 @@ import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BTypedesc; +/** + * Callback class for executing Ballerina Avro Deserialization methods. + */ public class ExecutionCallback implements Callback { private final Future future; private final BTypedesc typeDesc; @@ -34,6 +37,7 @@ public class ExecutionCallback implements Callback { this.future = future; this.typeDesc = typeDesc; } + @Override public void notifySuccess(Object o) { Object jsonObject = JsonUtils.parse(StringUtils.getJsonString(o)); diff --git a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ModuleUtils.java b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ModuleUtils.java index 97da007..1353ba4 100644 --- a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ModuleUtils.java +++ b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ModuleUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except @@ -21,6 +21,10 @@ import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.Module; +/** + * This class includes the utility functions related to Ballerina Avro Serializer for Schema Registry module. + * + */ public class ModuleUtils { private static Module module; From 4e558f74bfb85a936982d8dbae37a31e6462fe1e Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 15:14:35 +0530 Subject: [PATCH 18/21] Fix error handling in the callback class --- .../lib/confluent/avro/serdes/ExecutionCallback.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java index 93a8bb7..c875549 100644 --- a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java +++ b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java @@ -40,8 +40,12 @@ public class ExecutionCallback implements Callback { @Override public void notifySuccess(Object o) { - Object jsonObject = JsonUtils.parse(StringUtils.getJsonString(o)); - this.future.complete(ValueUtils.convert(jsonObject, typeDesc.getDescribingType())); + try { + Object jsonObject = JsonUtils.parse(StringUtils.getJsonString(o)); + this.future.complete(ValueUtils.convert(jsonObject, typeDesc.getDescribingType())); + } catch (BError e) { + this.future.complete(e); + } } @Override From c88f6516500d621e31ed52a61a1efb584ef68aaf Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 15:15:32 +0530 Subject: [PATCH 19/21] Add unrelated APIs outside the Client class --- ballerina/client.bal | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ballerina/client.bal b/ballerina/client.bal index d504b7e..7cf51e7 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -39,7 +39,7 @@ public isolated client class Client { remote isolated function serialize(string schema, anydata data, string subject) returns byte[]|Error { do { int id = check self.schemaClient->register(subject, schema); - byte[] encodedId = check self.toBytes(id); + byte[] encodedId = check toBytes(id); avro:Schema avroClient = check new (schema); byte[] serializedData = check avroClient.toAvro(data); return [...encodedId, ...serializedData]; @@ -59,17 +59,9 @@ public isolated client class Client { 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" } external; - isolated function toBytes(int id) returns byte[]|error = @java:Method { - 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" - } external; - - isolated function getId(byte[] bytes) returns int = @java:Method { - 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" - } external; - isolated function deserializeData(byte[] data) returns anydata|Error { do { - int schemaId = self.getId(data.slice(1, 5)); + int schemaId = getId(data.slice(1, 5)); string retrievedSchema = check self.schemaClient->getSchemaById(schemaId); avro:Schema avroClient = check new (retrievedSchema); anydata deserializedData = check avroClient.fromAvro(data.slice(5, data.length())); @@ -79,3 +71,11 @@ public isolated client class Client { } } } + +isolated function toBytes(int id) returns byte[]|error = @java:Method { + 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" +} external; + +isolated function getId(byte[] bytes) returns int = @java:Method { + 'class: "io.ballerina.lib.confluent.avro.serdes.AvroSerializer" +} external; From 023da0f3d92fd374551734c3dea859127ba7ee9f Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 15:28:41 +0530 Subject: [PATCH 20/21] Change EOF format to LF in files --- ballerina/Module.md | 120 ++++++++-------- ballerina/Package.md | 120 ++++++++-------- ballerina/tests/Config.toml | 12 +- ballerina/tests/tests.bal | 268 ++++++++++++++++++------------------ docs/spec/spec.md | 186 ++++++++++++------------- 5 files changed, 353 insertions(+), 353 deletions(-) diff --git a/ballerina/Module.md b/ballerina/Module.md index 3b2ce46..300c5db 100644 --- a/ballerina/Module.md +++ b/ballerina/Module.md @@ -1,60 +1,60 @@ -## Overview - -[Avro Serializer/Deserializer for Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/serdes-avro.html) is an Avro serializer/deserializer designed to work with the Confluent Schema Registry. It is designed to not include the message schema in the message payload but instead includes the schema ID. - -The Ballerina Avro Serializer/Deserializer for Confluent Schema Registry connector integrates with the Confluent Schema Registry for Avro serialization and deserialization. - -## Quickstart - -To use the Confluent schema registry connector in your Ballerina project, modify the `.bal` file as follows. - -### Step 1: Import the module - -Import the `ballerinax/confluent.cavroserdes` module into your Ballerina project. - -```ballerina -import ballerinax/confluent.cavroserdes; -``` - -### Step 2: Instantiate a new connector - -```ballerina -configurable string baseUrl = ?; -configurable int identityMapCapacity = ?; -configurable map originals = ?; -configurable map headers = ?; - -cavroserdes:Client avroSerDes = check new ({ - baseUrl, - identityMapCapacity, - originals, - headers -}); -``` - -### Step 3: Invoke the connector operation - -You can now utilize the operations available within the connector. - -```ballerina -public function main() returns error? { - string schema = string ` - { - "type": "int", - "name" : "value", - "namespace": "data" - }`; - - int value = 1; - byte[] bytes = check avroSerDes->serialize(schema, value, "subject"); - int number = check avroSerDes->deserialize(bytes); -} -``` - -### Step 4: Run the Ballerina application - -Use the following command to compile and run the Ballerina program. - -```bash -bal run -``` +## Overview + +[Avro Serializer/Deserializer for Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/serdes-avro.html) is an Avro serializer/deserializer designed to work with the Confluent Schema Registry. It is designed to not include the message schema in the message payload but instead includes the schema ID. + +The Ballerina Avro Serializer/Deserializer for Confluent Schema Registry connector integrates with the Confluent Schema Registry for Avro serialization and deserialization. + +## Quickstart + +To use the Confluent schema registry connector in your Ballerina project, modify the `.bal` file as follows. + +### Step 1: Import the module + +Import the `ballerinax/confluent.cavroserdes` module into your Ballerina project. + +```ballerina +import ballerinax/confluent.cavroserdes; +``` + +### Step 2: Instantiate a new connector + +```ballerina +configurable string baseUrl = ?; +configurable int identityMapCapacity = ?; +configurable map originals = ?; +configurable map headers = ?; + +cavroserdes:Client avroSerDes = check new ({ + baseUrl, + identityMapCapacity, + originals, + headers +}); +``` + +### Step 3: Invoke the connector operation + +You can now utilize the operations available within the connector. + +```ballerina +public function main() returns error? { + string schema = string ` + { + "type": "int", + "name" : "value", + "namespace": "data" + }`; + + int value = 1; + byte[] bytes = check avroSerDes->serialize(schema, value, "subject"); + int number = check avroSerDes->deserialize(bytes); +} +``` + +### Step 4: Run the Ballerina application + +Use the following command to compile and run the Ballerina program. + +```bash +bal run +``` diff --git a/ballerina/Package.md b/ballerina/Package.md index e592d4b..1ca9fad 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -1,60 +1,60 @@ -## Package overview - -[Avro Serializer/Deserializer for Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/serdes-avro.html) is an Avro serializer/deserializer designed to work with the Confluent Schema Registry. It is designed to not include the message schema in the message payload but instead includes the schema ID. - -The Ballerina Avro Serializer/Deserializer for Confluent Schema Registry connector integrates with the Confluent Schema Registry for Avro serialization and deserialization. - -## Quickstart - -To use the Confluent schema registry connector in your Ballerina project, modify the `.bal` file as follows. - -### Step 1: Import the module - -Import the `ballerinax/confluent.cavroserdes` module into your Ballerina project. - -```ballerina -import ballerinax/confluent.cavroserdes; -``` - -### Step 2: Instantiate a new connector - -```ballerina -configurable string baseUrl = ?; -configurable int identityMapCapacity = ?; -configurable map originals = ?; -configurable map headers = ?; - -cavroserdes:Client avroSerDes = check new ({ - baseUrl, - identityMapCapacity, - originals, - headers -}); -``` - -### Step 3: Invoke the connector operation - -You can now utilize the operations available within the connector. - -```ballerina -public function main() returns error? { - string schema = string ` - { - "type": "int", - "name" : "value", - "namespace": "data" - }`; - - int value = 1; - byte[] bytes = check avroSerDes->serialize(schema, value, "subject"); - int number = check avroSerDes->deserialize(bytes); -} -``` - -### Step 4: Run the Ballerina application - -Use the following command to compile and run the Ballerina program. - -```bash -bal run -``` +## Package overview + +[Avro Serializer/Deserializer for Confluent Schema Registry](https://docs.confluent.io/platform/current/schema-registry/fundamentals/serdes-develop/serdes-avro.html) is an Avro serializer/deserializer designed to work with the Confluent Schema Registry. It is designed to not include the message schema in the message payload but instead includes the schema ID. + +The Ballerina Avro Serializer/Deserializer for Confluent Schema Registry connector integrates with the Confluent Schema Registry for Avro serialization and deserialization. + +## Quickstart + +To use the Confluent schema registry connector in your Ballerina project, modify the `.bal` file as follows. + +### Step 1: Import the module + +Import the `ballerinax/confluent.cavroserdes` module into your Ballerina project. + +```ballerina +import ballerinax/confluent.cavroserdes; +``` + +### Step 2: Instantiate a new connector + +```ballerina +configurable string baseUrl = ?; +configurable int identityMapCapacity = ?; +configurable map originals = ?; +configurable map headers = ?; + +cavroserdes:Client avroSerDes = check new ({ + baseUrl, + identityMapCapacity, + originals, + headers +}); +``` + +### Step 3: Invoke the connector operation + +You can now utilize the operations available within the connector. + +```ballerina +public function main() returns error? { + string schema = string ` + { + "type": "int", + "name" : "value", + "namespace": "data" + }`; + + int value = 1; + byte[] bytes = check avroSerDes->serialize(schema, value, "subject"); + int number = check avroSerDes->deserialize(bytes); +} +``` + +### Step 4: Run the Ballerina application + +Use the following command to compile and run the Ballerina program. + +```bash +bal run +``` diff --git a/ballerina/tests/Config.toml b/ballerina/tests/Config.toml index f739ab6..179f52d 100644 --- a/ballerina/tests/Config.toml +++ b/ballerina/tests/Config.toml @@ -1,6 +1,6 @@ -baseUrl = "http://localhost:9092/registry" -identityMapCapacity = 1000 - -[originals] - -[headers] +baseUrl = "http://localhost:9092/registry" +identityMapCapacity = 1000 + +[originals] + +[headers] diff --git a/ballerina/tests/tests.bal b/ballerina/tests/tests.bal index f382d21..aa520a7 100644 --- a/ballerina/tests/tests.bal +++ b/ballerina/tests/tests.bal @@ -1,134 +1,134 @@ -// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) -// -// WSO2 LLC. licenses this file to you 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 -// -// http://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. - -import ballerina/test; - -configurable string baseUrl = ?; -configurable int identityMapCapacity = ?; -configurable map originals = ?; -configurable map headers = ?; - -Client avroSerDes = check new ({ - baseUrl, - identityMapCapacity, - originals, - headers -}); - -@test:Config {} -public function testSerDes() returns error? { - string schema = string ` - { - "namespace": "example.avro", - "type": "record", - "name": "Student", - "fields": [ - {"name": "name", "type": "string"}, - {"name": "colors", "type": {"type": "array", "items": "string"}} - ] - }`; - - Color colors = { - name: "Red", - colors: ["maroon", "dark red", "light red"] - }; - - byte[] bytes = check avroSerDes->serialize(schema, colors, "subject-0"); - Color getColors = check avroSerDes->deserialize(bytes); - test:assertEquals(getColors, colors); -} - -@test:Config {} -public function testWithRecords() returns error? { - string schema = string ` - { - "namespace": "example.avro", - "type": "record", - "name": "Student", - "fields": [ - {"name": "name", "type": "string"}, - {"name": "subject", "type": "string"} - ] - }`; - - Student student = { - name: "John", - subject: "Math" - }; - - byte[] bytes = check avroSerDes->serialize(schema, student, "subject-1"); - Student getStudent = check avroSerDes->deserialize(bytes); - test:assertEquals(getStudent, student); -} - -@test:Config {} -public function testSerDesWithInteger() returns error? { - string schema = string ` - { - "type": "int", - "name" : "intValue", - "namespace": "data" - }`; - - int value = 1; - - byte[] bytes = check avroSerDes->serialize(schema, value, "subject-5"); - int getValue = check avroSerDes->deserialize(bytes); - test:assertEquals(getValue, value); -} - -@test:Config {} -public function testSerDesWithCourse() returns error? { - string schema = string ` - { - "namespace": "example.avro", - "type": "record", - "name": "Course", - "fields": [ - {"name": "name", "type": ["null", "string"]}, - {"name": "credits", "type": ["null", "int"]} - ] - }`; - - Course course = { - name: "Physics", - credits: 3 - }; - - byte[] bytes = check avroSerDes->serialize(schema, course, "subject-3"); - Course getCourse = check avroSerDes->deserialize(bytes); - test:assertEquals(getCourse, course); -} - -public type Student record { - string name; - string subject; -}; - -public type Person record { - string name; - int age; -}; - -public type Course record { - string? name; - int? credits; -}; - -public type Color record { - string? name; - string[] colors; -}; +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you 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 +// +// http://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. + +import ballerina/test; + +configurable string baseUrl = ?; +configurable int identityMapCapacity = ?; +configurable map originals = ?; +configurable map headers = ?; + +Client avroSerDes = check new ({ + baseUrl, + identityMapCapacity, + originals, + headers +}); + +@test:Config {} +public function testSerDes() returns error? { + string schema = string ` + { + "namespace": "example.avro", + "type": "record", + "name": "Student", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "colors", "type": {"type": "array", "items": "string"}} + ] + }`; + + Color colors = { + name: "Red", + colors: ["maroon", "dark red", "light red"] + }; + + byte[] bytes = check avroSerDes->serialize(schema, colors, "subject-0"); + Color getColors = check avroSerDes->deserialize(bytes); + test:assertEquals(getColors, colors); +} + +@test:Config {} +public function testWithRecords() returns error? { + string schema = string ` + { + "namespace": "example.avro", + "type": "record", + "name": "Student", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "subject", "type": "string"} + ] + }`; + + Student student = { + name: "John", + subject: "Math" + }; + + byte[] bytes = check avroSerDes->serialize(schema, student, "subject-1"); + Student getStudent = check avroSerDes->deserialize(bytes); + test:assertEquals(getStudent, student); +} + +@test:Config {} +public function testSerDesWithInteger() returns error? { + string schema = string ` + { + "type": "int", + "name" : "intValue", + "namespace": "data" + }`; + + int value = 1; + + byte[] bytes = check avroSerDes->serialize(schema, value, "subject-5"); + int getValue = check avroSerDes->deserialize(bytes); + test:assertEquals(getValue, value); +} + +@test:Config {} +public function testSerDesWithCourse() returns error? { + string schema = string ` + { + "namespace": "example.avro", + "type": "record", + "name": "Course", + "fields": [ + {"name": "name", "type": ["null", "string"]}, + {"name": "credits", "type": ["null", "int"]} + ] + }`; + + Course course = { + name: "Physics", + credits: 3 + }; + + byte[] bytes = check avroSerDes->serialize(schema, course, "subject-3"); + Course getCourse = check avroSerDes->deserialize(bytes); + test:assertEquals(getCourse, course); +} + +public type Student record { + string name; + string subject; +}; + +public type Person record { + string name; + int age; +}; + +public type Course record { + string? name; + int? credits; +}; + +public type Color record { + string? name; + string[] colors; +}; diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 13b85f8..7ebb8d3 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -1,93 +1,93 @@ -# Specification: Ballerina Avro Serializer/Deserializer for Confluent Schema Registry Library - -_Authors_: @Nuvindu \ -_Reviewers_: @ThisaruGuruge \ -_Created_: 2024/04/10 \ -_Updated_: 2024/04/10 \ -_Edition_: Swan Lake - -## Introduction - -The Ballerina Avro Serializer/Deserializer for Confluent Schema Registry module facilitates the integration with Confluent's Schema Registry and Avro. It offers capabilities to serialize and deserialize Avro data using the schemas stored in the registry. - -The Avro Serializer/Deserializer library specification has evolved and may continue to evolve in the future. The released versions of the specification can be found under the relevant GitHub tag. - -For feedback or suggestions about the library, please initiate a discussion via a [GitHub issue](https://github.com/ballerina-platform/ballerina-library/issues) or in the [Discord server](https://discord.gg/ballerinalang). The specification and implementation can be updated based on the discussion's outcome. Community feedback is highly appreciated. Accepted proposals that affect the specification are stored under `/docs/proposals`. Proposals under discussion can be found with the label `type/proposal` in GitHub. - -The conforming implementation of the specification is released and included in the distribution. Any deviation from the specification is considered a bug. - -## Contents - -1. [Overview](#1-overview) -2. [Initialize the Avro Serializer/Deserializer client](#2-initialize-the-avro-serializerdeserializer-client) - * 2.1 [The `init` method](#21-the-init-method) -3. [Serialize data](#3-serialize-data) - * 3.1 [The `serialize` API](#31-the-serialize-api) -4. [Deserialize data](#4-deserialize-data) - * 4.1 [The `deserialize` API](#41-the-deserialize-api) -5. [The `cavroserdes:Error` Type](#5-the-cavroserdeserror-type) - -## 1. Overview - -This specification provides a detailed explanation of the functionalities offered by the Ballerina Avro Serializer/Deserializer for Confluent Schema Registry module. The module provides the following capabilities. - -1. Serialize data -2. Deserialize data - -## 2. Initialize the Avro Serializer/Deserializer client - -The Avro Serializer/Deserializer client needs to be initialized before performing the functionalities. - -### 2.1 The `init` method - -The `init` method initializes the Avro Serializer/Deserializer client. It takes a `config` parameter, which contains the necessary configuration to connect to the Schema Registry. The method returns an error if the initialization fails. - -```ballerina -configurable string baseUrl = ?; -configurable int identityMapCapacity = ?; -configurable map originals = ?; -configurable map headers = ?; - -cavroserdes:Client avroSerDes = check new ({ - baseUrl, - identityMapCapacity, - originals, - headers -}); -``` - -## 3. Serialize data - -The Avro Serializer/Deserializer module allows serializing data to Avro format using the schemas from the registry. - -### 3.1 The `serialize` API - -The `serialize` method serializes a given value to Avro format using the provided schema and subject. Here the new schemas added on a particular `subject` must have backward compatibility with previous versions of schemas on that `subject`. - -```ballerina -string schema = string ` -{ - "type": "int", - "name" : "value", - "namespace": "data" -}`; - -int value = 1; -byte[] bytes = check avroSerDes.serialize(schema, value, "subject"); -``` - -## 4. Deserialize data - -This section details the process of deserializing Avro data using the schemas from the registry. - -### 4.1 The `deserialize` API - -The `deserialize` method deserializes a given Avro data to its original form using the schema from the registry. - -```ballerina -int number = check avroSerDes.deserialize(bytes); -``` - -## 5. The `cavroserdes:Error` Type - -The `cavroserdes:Error` type represents all the errors related to the Avro Serializer/Deserializer for Confluent Schema Registry module. This is a subtype of the Ballerina `error` type. +# Specification: Ballerina Avro Serializer/Deserializer for Confluent Schema Registry Library + +_Authors_: @Nuvindu \ +_Reviewers_: @ThisaruGuruge \ +_Created_: 2024/04/10 \ +_Updated_: 2024/04/10 \ +_Edition_: Swan Lake + +## Introduction + +The Ballerina Avro Serializer/Deserializer for Confluent Schema Registry module facilitates the integration with Confluent's Schema Registry and Avro. It offers capabilities to serialize and deserialize Avro data using the schemas stored in the registry. + +The Avro Serializer/Deserializer library specification has evolved and may continue to evolve in the future. The released versions of the specification can be found under the relevant GitHub tag. + +For feedback or suggestions about the library, please initiate a discussion via a [GitHub issue](https://github.com/ballerina-platform/ballerina-library/issues) or in the [Discord server](https://discord.gg/ballerinalang). The specification and implementation can be updated based on the discussion's outcome. Community feedback is highly appreciated. Accepted proposals that affect the specification are stored under `/docs/proposals`. Proposals under discussion can be found with the label `type/proposal` in GitHub. + +The conforming implementation of the specification is released and included in the distribution. Any deviation from the specification is considered a bug. + +## Contents + +1. [Overview](#1-overview) +2. [Initialize the Avro Serializer/Deserializer client](#2-initialize-the-avro-serializerdeserializer-client) + * 2.1 [The `init` method](#21-the-init-method) +3. [Serialize data](#3-serialize-data) + * 3.1 [The `serialize` API](#31-the-serialize-api) +4. [Deserialize data](#4-deserialize-data) + * 4.1 [The `deserialize` API](#41-the-deserialize-api) +5. [The `cavroserdes:Error` Type](#5-the-cavroserdeserror-type) + +## 1. Overview + +This specification provides a detailed explanation of the functionalities offered by the Ballerina Avro Serializer/Deserializer for Confluent Schema Registry module. The module provides the following capabilities. + +1. Serialize data +2. Deserialize data + +## 2. Initialize the Avro Serializer/Deserializer client + +The Avro Serializer/Deserializer client needs to be initialized before performing the functionalities. + +### 2.1 The `init` method + +The `init` method initializes the Avro Serializer/Deserializer client. It takes a `config` parameter, which contains the necessary configuration to connect to the Schema Registry. The method returns an error if the initialization fails. + +```ballerina +configurable string baseUrl = ?; +configurable int identityMapCapacity = ?; +configurable map originals = ?; +configurable map headers = ?; + +cavroserdes:Client avroSerDes = check new ({ + baseUrl, + identityMapCapacity, + originals, + headers +}); +``` + +## 3. Serialize data + +The Avro Serializer/Deserializer module allows serializing data to Avro format using the schemas from the registry. + +### 3.1 The `serialize` API + +The `serialize` method serializes a given value to Avro format using the provided schema and subject. Here the new schemas added on a particular `subject` must have backward compatibility with previous versions of schemas on that `subject`. + +```ballerina +string schema = string ` +{ + "type": "int", + "name" : "value", + "namespace": "data" +}`; + +int value = 1; +byte[] bytes = check avroSerDes.serialize(schema, value, "subject"); +``` + +## 4. Deserialize data + +This section details the process of deserializing Avro data using the schemas from the registry. + +### 4.1 The `deserialize` API + +The `deserialize` method deserializes a given Avro data to its original form using the schema from the registry. + +```ballerina +int number = check avroSerDes.deserialize(bytes); +``` + +## 5. The `cavroserdes:Error` Type + +The `cavroserdes:Error` type represents all the errors related to the Avro Serializer/Deserializer for Confluent Schema Registry module. This is a subtype of the Ballerina `error` type. From 42da72afda096b1cd6b586439da852356fe7dcf6 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 10 Apr 2024 16:38:24 +0530 Subject: [PATCH 21/21] Fix error handling in callback API --- .../lib/confluent/avro/serdes/ExecutionCallback.java | 10 +++++----- native/src/main/java/module-info.java | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java index c875549..d45b558 100644 --- a/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java +++ b/native/src/main/java/io/ballerina/lib/confluent/avro/serdes/ExecutionCallback.java @@ -39,12 +39,12 @@ public class ExecutionCallback implements Callback { } @Override - public void notifySuccess(Object o) { - try { - Object jsonObject = JsonUtils.parse(StringUtils.getJsonString(o)); + public void notifySuccess(Object result) { + if (result instanceof BError) { + this.future.complete(result); + } else { + Object jsonObject = JsonUtils.parse(StringUtils.getJsonString(result)); this.future.complete(ValueUtils.convert(jsonObject, typeDesc.getDescribingType())); - } catch (BError e) { - this.future.complete(e); } } diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java index c87014f..6b8d2d7 100644 --- a/native/src/main/java/module-info.java +++ b/native/src/main/java/module-info.java @@ -16,7 +16,7 @@ * under the License. */ -module io.ballerina.stdlib.serdes { +module io.ballerina.lib.serdes { requires io.ballerina.runtime; requires io.ballerina.lang; requires org.apache.avro;