diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c2b3d9a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,82 @@
+# EditorConfig is awesome:http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Don't use tabs for indentation.
+[*]
+indent_style = space
+# (Please don't specify an indent_size here; that has too many unintended consequences.)
+
+# Code files
+[*.{cs,csx}]
+indent_size = 4
+
+# Xml project files
+[*.{csproj}]
+indent_size = 2
+
+# Xml config files
+[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
+indent_size = 2
+
+# JSON files
+[*.json]
+indent_size = 2
+
+# proto
+[*.proto]
+indent_size = 2
+
+# Dotnet code style settings:
+[*.{cs}]
+# Sort using and Import directives with System.* appearing first
+dotnet_sort_system_directives_first = true
+# Avoid "this." and "Me." if not necessary
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+
+# Use language keywords instead of framework type names for type references
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+
+# Suggest more modern language features when available
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+
+# CSharp code style settings:
+[*.cs]
+# Prefer "var" everywhere
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
+
+# Prefer method-like constructs to have a block body
+csharp_style_expression_bodied_methods = false:none
+csharp_style_expression_bodied_constructors = false:none
+csharp_style_expression_bodied_operators = false:none
+
+# Prefer property-like constructs to have an expression-body
+csharp_style_expression_bodied_properties = true:none
+csharp_style_expression_bodied_indexers = true:none
+csharp_style_expression_bodied_accessors = true:none
+
+# Suggest more modern language features when available
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Newline settings
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml
new file mode 100644
index 0000000..ce91b3e
--- /dev/null
+++ b/.github/workflows/Build.yml
@@ -0,0 +1,85 @@
+# ------------------------------------------------------------------------------
+#
+#
+# This code was generated.
+#
+# - To turn off auto-generation set:
+#
+# [CustomGitHubActions (AutoGenerate = false)]
+#
+# - To trigger manual generation invoke:
+#
+# nuke --generate-configuration GitHubActions_Build --host GitHubActions
+#
+#
+# ------------------------------------------------------------------------------
+
+name: Build
+
+on:
+ push:
+ branches:
+ - main
+ - dev
+ - alpha
+ - beta
+ paths:
+ - '!docs/**/*'
+ - '!package.json'
+ - '!README.md'
+ pull_request:
+ branches:
+ - main
+ - dev
+ - alpha
+ - beta
+
+jobs:
+ windows-latest:
+ name: windows-latest
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 8.0.*
+ - uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 7.0.*
+ - name: 'Cache: .nuke/temp, ~/.nuget/packages'
+ uses: actions/cache@v3
+ with:
+ path: |
+ .nuke/temp
+ ~/.nuget/packages
+ key: ${{ runner.os }}-${{ hashFiles('global.json', 'SchemaGenerator/**/*.csproj') }}
+ - name: 'Run: Compile'
+ run: ./build.cmd Compile
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ubuntu-latest:
+ name: ubuntu-latest
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 8.0.*
+ - uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 7.0.*
+ - name: 'Cache: .nuke/temp, ~/.nuget/packages'
+ uses: actions/cache@v3
+ with:
+ path: |
+ .nuke/temp
+ ~/.nuget/packages
+ key: ${{ runner.os }}-${{ hashFiles('global.json', 'SchemaGenerator/**/*.csproj') }}
+ - name: 'Run: Compile'
+ run: ./build.cmd Compile
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/Cancel.yml b/.github/workflows/Cancel.yml
new file mode 100644
index 0000000..ea9b36b
--- /dev/null
+++ b/.github/workflows/Cancel.yml
@@ -0,0 +1,12 @@
+name: Cancel
+on: [push]
+jobs:
+ cancel:
+ name: 'Cancel Previous Runs'
+ runs-on: ubuntu-latest
+ timeout-minutes: 3
+ steps:
+ - uses: styfle/cancel-workflow-action@0.7.0
+ with:
+ workflow_id: 5724590,5725340
+ access_token: ${{ github.token }}
\ No newline at end of file
diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml
new file mode 100644
index 0000000..1da9142
--- /dev/null
+++ b/.github/workflows/Release.yml
@@ -0,0 +1,49 @@
+# ------------------------------------------------------------------------------
+#
+#
+# This code was generated.
+#
+# - To turn off auto-generation set:
+#
+# [CustomGitHubActions (AutoGenerate = false)]
+#
+# - To trigger manual generation invoke:
+#
+# nuke --generate-configuration GitHubActions_Release --host GitHubActions
+#
+#
+# ------------------------------------------------------------------------------
+
+name: Release
+
+on:
+ push:
+ tags:
+ - '*'
+
+jobs:
+ windows-latest:
+ name: windows-latest
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 8.0.*
+ - uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 7.0.*
+ - name: 'Cache: .nuke/temp, ~/.nuget/packages'
+ uses: actions/cache@v3
+ with:
+ path: |
+ .nuke/temp
+ ~/.nuget/packages
+ key: ${{ runner.os }}-${{ hashFiles('global.json', 'SchemaGenerator/**/*.csproj') }}
+ - name: 'Run: Release'
+ run: ./build.cmd Release
+ env:
+ NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml
new file mode 100644
index 0000000..a6e38c5
--- /dev/null
+++ b/.github/workflows/Tests.yml
@@ -0,0 +1,77 @@
+# ------------------------------------------------------------------------------
+#
+#
+# This code was generated.
+#
+# - To turn off auto-generation set:
+#
+# [CustomGitHubActions (AutoGenerate = false)]
+#
+# - To trigger manual generation invoke:
+#
+# nuke --generate-configuration GitHubActions_Tests --host GitHubActions
+#
+#
+# ------------------------------------------------------------------------------
+
+name: Tests
+
+on:
+ push:
+ branches:
+ - main
+ - dev
+ pull_request:
+ branches:
+ - main
+ - dev
+
+jobs:
+ windows-latest:
+ name: windows-latest
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 8.0.*
+ - uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 7.0.*
+ - name: 'Cache: .nuke/temp, ~/.nuget/packages'
+ uses: actions/cache@v3
+ with:
+ path: |
+ .nuke/temp
+ ~/.nuget/packages
+ key: ${{ runner.os }}-${{ hashFiles('global.json', 'SchemaGenerator/**/*.csproj') }}
+ - name: 'Run: Test'
+ run: ./build.cmd Test
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ubuntu-latest:
+ name: ubuntu-latest
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 8.0.*
+ - uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: 7.0.*
+ - name: 'Cache: .nuke/temp, ~/.nuget/packages'
+ uses: actions/cache@v3
+ with:
+ path: |
+ .nuke/temp
+ ~/.nuget/packages
+ key: ${{ runner.os }}-${{ hashFiles('global.json', 'SchemaGenerator/**/*.csproj') }}
+ - name: 'Run: Test'
+ run: ./build.cmd Test
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4ce6fdd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,340 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- Backup*.rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
\ No newline at end of file
diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
new file mode 100644
index 0000000..d810b91
--- /dev/null
+++ b/.nuke/build.schema.json
@@ -0,0 +1,129 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "$ref": "#/definitions/build",
+ "title": "Build Schema",
+ "definitions": {
+ "build": {
+ "type": "object",
+ "properties": {
+ "Configuration": {
+ "type": "string",
+ "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
+ "enum": [
+ "Debug",
+ "Release"
+ ]
+ },
+ "Continue": {
+ "type": "boolean",
+ "description": "Indicates to continue a previously failed build attempt"
+ },
+ "Help": {
+ "type": "boolean",
+ "description": "Shows the help text for this build assembly"
+ },
+ "Host": {
+ "type": "string",
+ "description": "Host for execution. Default is 'automatic'",
+ "enum": [
+ "AppVeyor",
+ "AzurePipelines",
+ "Bamboo",
+ "Bitbucket",
+ "Bitrise",
+ "GitHubActions",
+ "GitLab",
+ "Jenkins",
+ "Rider",
+ "SpaceAutomation",
+ "TeamCity",
+ "Terminal",
+ "TravisCI",
+ "VisualStudio",
+ "VSCode"
+ ]
+ },
+ "NoLogo": {
+ "type": "boolean",
+ "description": "Disables displaying the NUKE logo"
+ },
+ "NuGetApiKey": {
+ "type": "string",
+ "default": "Secrets must be entered via 'nuke :secrets [profile]'"
+ },
+ "NugetApiUrl": {
+ "type": "string"
+ },
+ "Partition": {
+ "type": "string",
+ "description": "Partition to use on CI"
+ },
+ "Plan": {
+ "type": "boolean",
+ "description": "Shows the execution plan (HTML)"
+ },
+ "Profile": {
+ "type": "array",
+ "description": "Defines the profiles to load",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Root": {
+ "type": "string",
+ "description": "Root directory during build execution"
+ },
+ "Skip": {
+ "type": "array",
+ "description": "List of targets to be skipped. Empty list skips all dependencies",
+ "items": {
+ "type": "string",
+ "enum": [
+ "AuthenticatedGitHubClient",
+ "Clean",
+ "Compile",
+ "GitHubRelease",
+ "Pack",
+ "Release",
+ "Restore",
+ "RunChangelog",
+ "Test"
+ ]
+ }
+ },
+ "Solution": {
+ "type": "string",
+ "description": "Path to a solution file that is automatically loaded"
+ },
+ "Target": {
+ "type": "array",
+ "description": "List of targets to be invoked. Default is '{default_target}'",
+ "items": {
+ "type": "string",
+ "enum": [
+ "AuthenticatedGitHubClient",
+ "Clean",
+ "Compile",
+ "GitHubRelease",
+ "Pack",
+ "Release",
+ "Restore",
+ "RunChangelog",
+ "Test"
+ ]
+ }
+ },
+ "Verbosity": {
+ "type": "string",
+ "description": "Logging verbosity during build execution. Default is 'Normal'",
+ "enum": [
+ "Minimal",
+ "Normal",
+ "Quiet",
+ "Verbose"
+ ]
+ }
+ }
+ }
+ }
+}
diff --git a/.nuke/parameters.json b/.nuke/parameters.json
new file mode 100644
index 0000000..67f5352
--- /dev/null
+++ b/.nuke/parameters.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "./build.schema.json",
+ "Solution": "SchemaGenerator.sln"
+}
\ No newline at end of file
diff --git a/AssemblyInfo.cs b/AssemblyInfo.cs
new file mode 100644
index 0000000..e98a683
--- /dev/null
+++ b/AssemblyInfo.cs
@@ -0,0 +1,9 @@
+//
+ using System.Reflection;
+ [assembly: AssemblyMetadataAttribute("githash","31fd699076fb0c88f0bee74ed639fc85fdf2c633")]
+ namespace System {
+ internal static class AssemblyVersionInformation {
+ internal const System.String AssemblyMetadata_githash = "31fd699076fb0c88f0bee74ed639fc85fdf2c633";
+ }
+ }
+
\ No newline at end of file
diff --git a/AvroSchemaGenerator.Tests/AvroSchemaGenerator.Tests.csproj b/AvroSchemaGenerator.Tests/AvroSchemaGenerator.Tests.csproj
new file mode 100644
index 0000000..3723203
--- /dev/null
+++ b/AvroSchemaGenerator.Tests/AvroSchemaGenerator.Tests.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net8.0
+
+ false
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+ $(DefineConstants);NETCORE
+
+
diff --git a/AvroSchemaGenerator.Tests/FullSchemaTest.cs b/AvroSchemaGenerator.Tests/FullSchemaTest.cs
new file mode 100644
index 0000000..db3b5bc
--- /dev/null
+++ b/AvroSchemaGenerator.Tests/FullSchemaTest.cs
@@ -0,0 +1,306 @@
+using Avro;
+using Avro.IO;
+using Avro.Reflect;
+using AvroSchemaGenerator.Attributes;
+using System;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.IO;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace AvroSchemaGenerator.Tests
+{
+ public class FullSchemaTest
+ {
+
+ private readonly ITestOutputHelper _output;
+
+ public FullSchemaTest(ITestOutputHelper output)
+ {
+ this._output = output;
+
+ }
+
+ class IntTest
+ {
+ public int Age { get; set; }
+ [Ignore]
+ public bool IsTrue => Age > 40;
+ }
+
+ [Fact]
+ public void TestInt()
+ {
+ var actualSchema = typeof(IntTest).GetSchema();
+ _output.WriteLine(actualSchema);
+ var expectedSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"IntTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":\"int\"}]}";
+ Assert.Equal(expectedSchema, actualSchema);
+ }
+
+ class IntNullableTest
+ {
+ public int? Age { get; set; }
+ }
+
+ [Fact]
+ public void TestIntNullable()
+ {
+ var actualSchema = typeof(IntNullableTest).GetSchema();
+ _output.WriteLine(actualSchema);
+ var expectedSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"IntNullableTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":[\"null\",\"int\"],\"default\":null}]}";
+ Assert.Equal(expectedSchema, actualSchema);
+ }
+
+ class IntDefaultTest
+ {
+ [DefaultValue(100)]
+ public int Age { get; set; }
+ }
+
+ [Fact]
+ public void TestIntDefault()
+ {
+ var actualSchema = typeof(IntDefaultTest).GetSchema();
+ _output.WriteLine(actualSchema);
+ var expectedSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"IntDefaultTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":\"int\",\"default\":100}]}";
+ Assert.Equal(expectedSchema, actualSchema);
+ }
+
+ class StringTest
+ {
+ public string Age { get; set; }
+ }
+
+ [Fact]
+ public void TestString()
+ {
+ var actualSchema = typeof(StringTest).GetSchema();
+ _output.WriteLine(actualSchema);
+ var expectedSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"StringTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":[\"null\",\"string\"],\"default\":null}]}";
+ Assert.Equal(expectedSchema, actualSchema);
+ }
+
+ class StringRequiredTest
+ {
+ [Required]
+ public string Age { get; set; }
+ [Ignore]
+ public bool IsTrue => Age == "45";
+ }
+
+ [Fact]
+ public void TestStringRequired()
+ {
+ var actualSchema = typeof(StringRequiredTest).GetSchema();
+ _output.WriteLine(actualSchema);
+ var expectedSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"StringRequiredTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":\"string\"}]}";
+ Assert.Equal(expectedSchema, actualSchema);
+ }
+
+ class StringRequiredDefaultTest
+ {
+ [Required]
+ [DefaultValue("100")]
+ public string Age { get; set; }
+
+ [Ignore]
+ public bool IsTrue => Age == "45";
+ }
+
+ [Fact]
+ public void TestStringRequiredDefault()
+ {
+ var actualSchema = typeof(StringRequiredDefaultTest).GetSchema();
+ _output.WriteLine(actualSchema);
+ var expectedSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"StringRequiredDefaultTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":\"string\",\"default\":\"100\"}]}";
+ Assert.Equal(expectedSchema, actualSchema);
+ }
+
+ struct StructTest
+ {
+ public int Age { get; set; }
+ }
+
+ [Fact]
+ public void TestStructTest()
+ {
+ var actualSchema = typeof(StructTest).GetSchema();
+ _output.WriteLine(actualSchema);
+ var expectedSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"StructTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":\"int\"}]}";
+ Assert.Equal(expectedSchema, actualSchema);
+ }
+
+
+ class ClassFieldTest
+ {
+ [DefaultValue(null)]
+ public IntTest Age { get; set; }
+ [Ignore]
+ public IntTest AgeIgnore { get; set; }
+ }
+
+ [Fact]
+ public void TestClassField()
+ {
+ var actualSchema = typeof(ClassFieldTest).GetSchema();
+ _output.WriteLine(actualSchema);
+ var expectedSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"ClassFieldTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":[\"null\",{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"IntTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":\"int\"}]}],\"default\":null}]}";
+ //"{"type":"record","namespace":"AvroSchemaGenerator.Tests","name":"ClassFieldTest","fields":[{"name":"Age","type":["null",{"type":"record","namespace":"AvroSchemaGenerator.Tests","name":"Age","fields":[{"name":"Age","type":"int"}]}],"default":null}]}";
+ Assert.Equal(expectedSchema, actualSchema);
+ }
+
+ class ClassFieldRequiredTest
+ {
+ [Required]
+ public IntTest Age { get; set; }
+ [Ignore]
+ public DateTime TimestampAsDateTime => DateTime.FromBinary(long.MaxValue);
+ }
+
+ [Fact]
+ public void TestClassFieldRequired()
+ {
+ var actualSchema = typeof(ClassFieldRequiredTest).GetSchema();
+ _output.WriteLine(actualSchema);
+ var expectedSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"ClassFieldRequiredTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"IntTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":\"int\"}]}}]}";
+ Assert.Equal(expectedSchema, actualSchema);
+ }
+
+ class StructFieldTest
+ {
+ [Required]
+ public StructTest Age { get; set; }
+ }
+
+ [Fact]
+ public void TestStructField()
+ {
+ var actualSchema = typeof(StructFieldTest).GetSchema();
+ _output.WriteLine(actualSchema);
+ var expectedSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"StructFieldTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"StructTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Age\",\"type\":\"int\"}]}}]}";
+ Assert.Equal(expectedSchema, actualSchema);
+ }
+
+ public class RecType
+ {
+ public string Name { get; set; }
+ public RecType Child { get; set; }
+ }
+ public class RecTypeNestedRecursion
+ {
+ public string Name { get; set; }
+ public RecTypeNestedRecursion Child { get; set; }
+ public RecTypeRecursive Recursive { get; set; }
+ }
+ public class RecTypeRecursive
+ {
+ public string Name { get; set; }
+ public RecTypeRecursive Child { get; set; }
+ }
+
+ public class NestedSchema
+ {
+ public RecType Foo { get; set; }
+ public RecType Bar { get; set; }
+ }
+ public class NestedSchemaWithDifferentSchemaButSamePropertyName
+ {
+ public RecTypeNestedRecursion Foo { get; set; }
+ public RecTypeNestedRecursion Bar { get; set; }
+ }
+ class RecTypeRequired
+ {
+ [Required]
+ public string Name { get; set; }
+ [Required]
+ public RecTypeRequired Child { get; set; }
+ }
+ [Fact]
+ public void NestedTypesProduceValidAvroSchema()
+ {
+ try
+ {
+ var simple = typeof(NestedSchema).GetSchema();
+ _output.WriteLine(simple);
+ var schema = Schema.Parse(simple);
+ var data = new NestedSchema
+ {
+ Foo = new RecType
+ {
+ Name = "Foo-Name",
+ Child = new RecType
+ {
+ Name = "Foo Grand Child"
+ }
+ },
+ Bar = new RecType
+ {
+ Name = "Bar-Name",
+ Child = new RecType
+ {
+ Name = "Bar Grand Child"
+ }
+ }
+ };
+ var reader = new ReflectReader(schema, schema);
+ var writer = new ReflectWriter(schema);
+ var msgBytes = Write(data, writer);
+ using var stream = new MemoryStream((byte[])(object)msgBytes);
+ var msg = Read(stream, reader);
+ Assert.NotNull(msg);
+ Assert.True(msg.Foo.Name == "Foo-Name");
+ }
+ catch(Exception ex)
+ {
+ _output.WriteLine(ex.ToString());
+ }
+ }
+ [Fact]
+ public void TestRecType()
+ {
+ var expected = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"RecType\",\"type\":\"record\",\"fields\":[{\"name\":\"Name\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"Child\",\"type\":[\"null\",\"RecType\"],\"default\":null}]}";
+ var actual = typeof(RecType).GetSchema();
+ _output.WriteLine(actual);
+ Assert.Equal(expected, actual);
+ }
+ [Fact]
+ public void TestRecTypeRequired()
+ {
+ var expected = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"RecTypeRequired\",\"type\":\"record\",\"fields\":[{\"name\":\"Name\",\"type\":\"string\"},{\"name\":\"Child\",\"type\":\"RecTypeRequired\"}]}";
+ var actual = typeof(RecTypeRequired).GetSchema();
+ _output.WriteLine(actual);
+ Assert.Equal(expected, actual);
+ }
+
+ class WithGuidType
+ {
+ [Required]
+ public Guid Id { get; set; }
+ }
+ [Fact]
+ public void TestGuidType()
+ {
+ var expected = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"WithGuidType\",\"type\":\"record\",\"fields\":[{\"name\":\"Id\",\"type\":{\"type\":\"string\",\"logicalType\":\"uuid\"}}]}";
+ var actual = typeof(WithGuidType).GetSchema();
+ _output.WriteLine(actual);
+ Assert.Equal(expected, actual);
+
+ }
+ private sbyte[] Write(T message, ReflectWriter writer)
+ {
+ var ms = new MemoryStream();
+ Avro.IO.Encoder e = new BinaryEncoder(ms);
+ writer.Write(message, e);
+ ms.Flush();
+ ms.Position = 0;
+ var b = ms.ToArray();
+ return (sbyte[])(object)b;
+ }
+ public T Read(Stream stream, ReflectReader reader)
+ {
+ stream.Seek(0, SeekOrigin.Begin);
+ return reader.Read(default, new BinaryDecoder(stream));
+ }
+ }
+}
\ No newline at end of file
diff --git a/AvroSchemaGenerator.Tests/GetSchemaTest.cs b/AvroSchemaGenerator.Tests/GetSchemaTest.cs
new file mode 100644
index 0000000..fffe828
--- /dev/null
+++ b/AvroSchemaGenerator.Tests/GetSchemaTest.cs
@@ -0,0 +1,660 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.IO;
+using System.Numerics;
+using Avro;
+using Avro.IO;
+using Avro.Reflect;
+using AvroSchemaGenerator.Attributes;
+using AvroSchemaGenerator.Tests;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace AvroSchemaGenerator.Tests
+{
+ [Collection("GetSchemaTest")]
+ public class GetSchemaTest
+ {
+ private readonly ITestOutputHelper _output;
+
+ public GetSchemaTest(ITestOutputHelper output)
+ {
+ this._output = output;
+ }
+ [Fact]
+ public void TestSimpleFoo()
+ {
+ var simple = typeof(SimpleFoo).GetSchema();
+ _output.WriteLine(simple);
+ var schema = Schema.Parse(simple);
+ var writer = new ReflectWriter(schema);
+ Assert.Contains("Age", simple);
+ }
+ [Fact]
+ public void TestSimpleRecord()
+ {
+ var simple = typeof(SimpleRecord).GetSchema();
+ _output.WriteLine(simple);
+ var schema = Schema.Parse(simple);
+ var writer = new ReflectWriter(schema);
+ Assert.Contains("Age", simple);
+ }
+
+ [Fact]
+ public void TestFoo()
+ {
+ var simple = typeof(Foo).GetSchema();
+ _output.WriteLine(simple);
+ var schema = Schema.Parse(simple);
+ var writer = new ReflectWriter(schema);
+ Assert.Contains("Age", simple);
+ }
+ [Fact]
+ public void TestFooCustom()
+ {
+ var simplecu = typeof(FooCustom).GetSchema();
+ _output.WriteLine(simplecu);
+ var schema = Schema.Parse(simplecu);
+ var writer = new ReflectWriter(schema);
+ Assert.Contains("EntryYear", simplecu);
+ Assert.Contains("Level", simplecu);
+ }
+ [Fact]
+ public void TestRecursiveArray()
+ {
+ var actual = typeof(Family).GetSchema();
+ _output.WriteLine(actual);
+ var schema = Schema.Parse(actual);
+ var writer = new ReflectWriter(schema);
+
+ Assert.True(true);
+ }
+ [Fact]
+ public void TestDictionaryRecursiveArray()
+ {
+ var actual = typeof(Dictionary).GetSchema();
+ _output.WriteLine(actual);
+ var schema = Schema.Parse(actual);
+ var writer = new ReflectWriter(schema);
+
+ Assert.True(true);
+ }
+ [Fact]
+ public void TestDictionaryRecursive()
+ {
+ try
+ {
+ var actual = typeof(DictionaryRecursive).GetSchema();
+ var schema = Schema.Parse(actual);
+ var writer = new ReflectWriter(schema);
+ _output.WriteLine(actual);
+ }
+ catch (Exception ex)
+ {
+ _output.WriteLine(ex.ToString());
+ Assert.True(false);
+ }
+ }
+ [Fact]
+ public void TestRecursiveSchema()
+ {
+ try
+ {
+ var actual = typeof(Recursive).GetSchema();
+ var schema = Schema.Parse(actual);
+ _output.WriteLine(actual);
+ var recursive = new Recursive
+ {
+ Fo = new SimpleFoo
+ {
+ Age = 67,
+ Attending = true,
+ FactTime = 90909099L,
+ Id = new byte[0] { },
+ Name = "Ebere",
+ Point = 888D,
+ Precision = 787F
+ },
+ Recurse = new Recursive
+ {
+ Fo = new SimpleFoo
+ {
+ Age = 6,
+ Attending = false,
+ FactTime = 90L,
+ Id = new byte[0] { },
+ Name = "Ebere Abanonu",
+ Point = 88D,
+ Precision = 78F
+ },
+ }
+ };
+ var writer = new ReflectWriter(schema);
+ var reader = new ReflectReader(schema, schema);
+ var msgBytes = Write(recursive, writer);
+ using var stream = new MemoryStream((byte[])(object)msgBytes);
+ var msg = Read(stream, reader);
+ Assert.NotNull(msg);
+ }
+ catch (Exception ex)
+ {
+ _output.WriteLine(ex.ToString());
+ Assert.True(false);
+ }
+ }
+ [Fact]
+ public void TestEnums()
+ {
+ var expectSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"MediaStream\",\"type\":\"record\",\"fields\":[{\"name\":\"Id\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"Title\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"Type\",\"type\":{\"namespace\":\"AvroSchemaGenerator.Tests\",\"type\":\"enum\",\"name\":\"MediaType\",\"symbols\":[\"Video\",\"Audio\"]}},{\"name\":\"Container\",\"type\":{\"namespace\":\"AvroSchemaGenerator.Tests\",\"type\":\"enum\",\"name\":\"MediaContainer\",\"symbols\":[\"Flv\",\"Mp3\",\"Avi\",\"Mp4\"]}},{\"name\":\"Media\",\"type\":[\"null\",\"bytes\"],\"default\":null}]}";
+ var actual = typeof(MediaStream).GetSchema();
+ var schema = Schema.Parse(actual);
+ var writer = new ReflectWriter(schema);
+ _output.WriteLine(actual);
+
+ Assert.Equal(expectSchema, actual);
+ }
+
+ [Fact]
+ public void TestMultipleEnums()
+ {
+ var expectSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"MultipleEnums\",\"type\":\"record\",\"fields\":[{\"name\":\"Type\",\"type\":{\"namespace\":\"AvroSchemaGenerator.Tests\",\"type\":\"enum\",\"name\":\"MediaType\",\"symbols\":[\"Video\",\"Audio\"]}},{\"name\":\"TypeTo\",\"type\":\"MediaType\"},{\"name\":\"Month\",\"type\":{\"namespace\":\"AvroSchemaGenerator.Tests\",\"type\":\"enum\",\"name\":\"Month\",\"symbols\":[\"January\",\"February\",\"March\",\"April\",\"June\",\"July\"]}},{\"name\":\"Container\",\"type\":{\"namespace\":\"AvroSchemaGenerator.Tests\",\"type\":\"enum\",\"name\":\"MediaContainer\",\"symbols\":[\"Flv\",\"Mp3\",\"Avi\",\"Mp4\"]}},{\"name\":\"InnerAliases\",\"type\":[\"null\",{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"InnerAliases\",\"type\":\"record\",\"fields\":[{\"name\":\"Container\",\"type\":[\"null\",\"MediaContainer\"]},{\"name\":\"Title\",\"type\":[\"null\",\"string\"],\"default\":null}]}]},{\"name\":\"YearlyMovies\",\"type\":{\"type\":\"map\",\"values\":{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"MovieAliase\",\"type\":\"record\",\"aliases\":[\"Movies_Aliase\"],\"fields\":[{\"name\":\"Dated\",\"aliases\":[\"DateCreated\"],\"type\":\"long\"},{\"name\":\"Year\",\"aliases\":[\"ReleaseYear\"],\"type\":\"int\"},{\"name\":\"Month\",\"aliases\":[\"ReleaseMonth\"],\"type\":\"Month\"}]}},\"default\":{}}]}";
+ var actual = typeof(MultipleEnums).GetSchema();
+ _output.WriteLine(actual);
+ var schema = Schema.Parse(actual);
+
+ Assert.Equal(expectSchema, actual);
+ Assert.NotNull(schema);
+ }
+
+ [Fact]
+ public void TestClassWhichHasFieldsWithSameType()
+ {
+ var expectSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"ClassWhichHasFieldsWithSameType\",\"type\":\"record\",\"fields\":[{\"name\":\"FirstBook\",\"type\":[\"null\",{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"Book\",\"type\":\"record\",\"fields\":[{\"name\":\"Author\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"Title\",\"type\":[\"null\",\"string\"],\"default\":null}]}]},{\"name\":\"SecondBook\",\"type\":[\"null\",\"Book\"]}]}";
+ var actual = typeof(ClassWhichHasFieldsWithSameType).GetSchema();
+ _output.WriteLine(actual);
+ var schema = Schema.Parse(actual);
+
+ Assert.Equal(expectSchema, actual);
+ Assert.NotNull(schema);
+ }
+
+ [Fact]
+ public void TestAliasesList()
+ {
+ var expectSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"ClassWithAliasesWithList\",\"aliases\":[\"InterLives\",\"CountrySide\"],\"type\":\"record\",\"fields\":[{\"name\":\"City\",\"aliases\":[\"TownHall\",\"Province\"],\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"State\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"Movie\",\"aliases\":[\"PopularMovie\"],\"type\":[\"null\",{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"InnerAliases\",\"type\":\"record\",\"fields\":[{\"name\":\"Container\",\"aliases\":[\"Media\"],\"type\":{\"namespace\":\"AvroSchemaGenerator.Tests\",\"type\":\"enum\",\"name\":\"MediaContainer\",\"symbols\":[\"Flv\",\"Mp3\",\"Avi\",\"Mp4\"]}},{\"name\":\"Title\",\"type\":[\"null\",\"string\"],\"default\":null}]}]},{\"name\":\"Popular\",\"aliases\":[\"PopularMediaType\"],\"type\":{\"namespace\":\"AvroSchemaGenerator.Tests\",\"type\":\"enum\",\"name\":\"MediaType\",\"symbols\":[\"Video\",\"Audio\"]}},{\"name\":\"Movies\",\"aliases\":[\"MovieCollection\"],\"type\":[\"null\",{\"type\":\"array\",\"items\":{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"MovieAliase\",\"type\":\"record\",\"aliases\":[\"Movies_Aliase\"],\"fields\":[{\"name\":\"Dated\",\"aliases\":[\"DateCreated\"],\"type\":\"long\"},{\"name\":\"Year\",\"aliases\":[\"ReleaseYear\"],\"type\":\"int\"},{\"name\":\"Month\",\"aliases\":[\"ReleaseMonth\"],\"type\":{\"namespace\":\"AvroSchemaGenerator.Tests\",\"type\":\"enum\",\"name\":\"Month\",\"symbols\":[\"January\",\"February\",\"March\",\"April\",\"June\",\"July\"]}}]}}]}]}";
+ var actual = typeof(ClassWithAliasesWithList).GetSchema();
+ _output.WriteLine(actual);
+ var schema = Schema.Parse(actual);
+ var writer = new ReflectWriter(schema);
+
+ Assert.Equal(expectSchema, actual);
+ }
+ [Fact]
+ public void TestAliasesDictionary()
+ {
+ var actual = typeof(ClassWithAliasesWithDictionary).GetSchema();
+ _output.WriteLine(actual);
+ var schema = Schema.Parse(actual);
+ var writer = new ReflectWriter(schema);
+
+ Assert.True(true);
+ }
+ [Fact]
+ public void TestStaticFieldsAreIgnored()
+ {
+ var expectSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"SimpleWithStaticFieldsAndNestedClass\",\"type\":\"record\",\"fields\":[{\"name\":\"FactTime\",\"type\":\"long\"},{\"name\":\"SimpleStaticInner\",\"type\":[\"null\",{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"SimpleWithStaticFields\",\"type\":\"record\",\"fields\":[{\"name\":\"Name\",\"type\":[\"null\",\"string\"],\"default\":null}]}]}]}";
+ var actual = typeof(SimpleWithStaticFieldsAndNestedClass).GetSchema();
+ _output.WriteLine(actual);
+ var schema = Schema.Parse(actual);
+ var writer = new ReflectWriter(schema);
+
+ Assert.Equal(expectSchema, actual);
+ }
+ [Fact]
+ public void TestEnumType()
+ {
+ var expectSchema = "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"Month\",\"type\":\"enum\",\"symbols\":[\"January\",\"February\",\"March\",\"April\",\"June\",\"July\"]}";
+ var actual = typeof(Month).GetSchema();
+ _output.WriteLine(actual);
+ var schema = Schema.Parse(actual);
+ var writer = new ReflectWriter(schema);
+
+ Assert.Equal(expectSchema, actual);
+ }
+ [Fact]
+ public void TestNoNamespaceType()
+ {
+ var expectSchema = "{\"name\":\"ClassWithoutNamespace\",\"type\":\"record\",\"fields\":[{\"name\":\"ForReal\",\"type\":\"boolean\"},{\"name\":\"Comment\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"Book\",\"type\":[\"null\",{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"Book\",\"type\":\"record\",\"fields\":[{\"name\":\"Author\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"Title\",\"type\":[\"null\",\"string\"],\"default\":null}]}]}]}";
+
+ var actual = typeof(ClassWithoutNamespace).GetSchema();
+ _output.WriteLine(actual);
+
+ Assert.Equal(expectSchema, actual);
+ var schema = Schema.Parse(actual);
+ var data = new ClassWithoutNamespace
+ {
+ ForReal = true,
+ Comment = "Harry Pull Requests",
+ Book = new Book
+ {
+ Author = "Ebere Abanonu",
+ Title = "How to skin a PR!!!"
+ }
+ };
+ var writer = new ReflectWriter(schema);
+ var reader = new ReflectReader(schema, schema);
+ var msgBytes = Write(data, writer);
+ using var stream = new MemoryStream((byte[])(object)msgBytes);
+ var msg = Read(stream, reader);
+ Assert.NotNull(msg);
+ //Assert.True(msg[0] == 10);
+ //Assert.True(msg[1] == 100);
+ }
+ [Theory]
+ [InlineData(typeof(List))]
+ [InlineData(typeof(IEnumerable))]
+ public void TestListType(Type type)
+ {
+ var expectSchema = "{\"namespace\":\"System.Collections.Generic\",\"type\":\"array\",\"items\":\"long\",\"default\":[]}";
+ var actual = type.GetSchema();
+ _output.WriteLine(actual);
+
+ Assert.Equal(expectSchema, actual);
+ var schema = Schema.Parse(actual);
+ var data = new List()
+ {
+ 10, 100
+ };
+ var writer = new ReflectWriter>(schema);
+ var reader = new ReflectReader>(schema, schema);
+ var msgBytes = Write(data, writer);
+ using var stream = new MemoryStream((byte[])(object)msgBytes);
+ var msg = Read(stream, reader);
+ Assert.NotNull(msg);
+ Assert.True(msg[0] == 10);
+ Assert.True(msg[1] == 100);
+ }
+ [Fact]
+ public void TestUserListType()
+ {
+ var actual = typeof(List).GetSchema();
+ _output.WriteLine(actual);
+
+ var schema = Schema.Parse(actual);
+ var data = new List()
+ {
+ new Book{ Author = "Ebere Abanonu", Title="Avro 101" },
+ new Book{ Author = "Apache Avro", Title = "Avro Schema" }
+ };
+ var writer = new ReflectWriter>(schema);
+ var reader = new ReflectReader>(schema, schema);
+ var msgBytes = Write(data, writer);
+ using var stream = new MemoryStream((byte[])(object)msgBytes);
+ var msg = Read(stream, reader);
+ Assert.NotNull(msg);
+ Assert.True(msg[0].Author == "Ebere Abanonu");
+ }
+ [Fact]
+ public void TestDictionaryType()
+ {
+ var expectSchema = "{\"namespace\":\"System.Collections.Generic\",\"type\":\"map\",\"values\":\"long\",\"default\":{}}";
+ var actual = typeof(Dictionary).GetSchema();
+ _output.WriteLine(actual);
+
+ Assert.Equal(expectSchema, actual);
+ var schema = Schema.Parse(actual);
+ var data = new Dictionary()
+ {
+ {"Index", 10 },
+ {"Pos", 100 }
+ };
+ var writer = new ReflectWriter>(schema);
+ var reader = new ReflectReader>(schema, schema);
+ var msgBytes = Write(data, writer);
+ using var stream = new MemoryStream((byte[])(object)msgBytes);
+ var msg = Read(stream, reader);
+ Assert.NotNull(msg);
+ Assert.True(msg["Index"] == 10);
+ Assert.True(msg["Pos"] == 100);
+ }
+ [Fact]
+ public void TestUserDictionaryType()
+ {
+ var actual = typeof(Dictionary).GetSchema();
+ _output.WriteLine(actual);
+
+ var schema = Schema.Parse(actual);
+ var data = new Dictionary()
+ {
+ {"First", new Book{ Author = "Ebere Abanonu", Title="Avro 101" } },
+ {"Second", new Book{ Author = "Apache Avro", Title = "Avro Schema" } }
+ };
+ var writer = new ReflectWriter>(schema);
+ var reader = new ReflectReader>(schema, schema);
+ var msgBytes = Write(data, writer);
+ using var stream = new MemoryStream((byte[])(object)msgBytes);
+ var msg = Read(stream, reader);
+ Assert.NotNull(msg);
+ }
+
+ public class CustomDefinitionTest
+ {
+ [AvroSchema("{\n" +
+ " \"type\": \"bytes\",\n" +
+ " \"logicalType\": \"decimal\",\n" +
+ " \"precision\": 10,\n" +
+ " \"scale\": 6\n" +
+ "}")]
+ public AvroDecimal DecimalAvro { get; set; }
+ }
+
+ public class NestedCustomDefinitionTest
+ {
+ public InnerClass Inner { get; set; }
+
+ public class InnerClass
+ {
+ [AvroSchema("{\n" +
+ " \"type\": \"bytes\",\n" +
+ " \"logicalType\": \"decimal\",\n" +
+ " \"precision\": 10,\n" +
+ " \"scale\": 6\n" +
+ "}")]
+ public BigInteger BI { get; set; }
+ }
+ }
+
+ public class FailedCustomDefinitionTest
+ {
+ [AvroSchema("\n" +
+ " \"type\" \"bytes\",\n" +
+ " \"logicalType\": \"decimal\",\n" +
+ " \"precision\": 10,\n" +
+ " \"scale\": 6\n" +
+ "}")]
+ public AvroDecimal DecimalAvro { get; set; }
+ }
+
+ [Fact]
+ public void TestCustomDefinition()
+ {
+ var expectSchema =
+ "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"CustomDefinitionTest\",\"type\":\"record\",\"fields\":[{\"name\":\"DecimalAvro\",\"type\":[\"null\",{\"type\":\"bytes\",\"logicalType\":\"decimal\",\"precision\":10,\"scale\":6}],\"default\":null}]}";
+ var actual = typeof(CustomDefinitionTest).GetSchema();
+ _output.WriteLine(actual);
+ var schema = Schema.Parse(actual);
+ var writer = new ReflectWriter(schema);
+
+ Assert.Equal(expectSchema, actual);
+ }
+
+ [Fact]
+ public void TestNestedCustomDefinition()
+ {
+ var expectSchema =
+ "{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"NestedCustomDefinitionTest\",\"type\":\"record\",\"fields\":[{\"name\":\"Inner\",\"type\":[\"null\",{\"namespace\":\"AvroSchemaGenerator.Tests\",\"name\":\"InnerClass\",\"type\":\"record\",\"fields\":[{\"name\":\"BI\",\"type\":[\"null\",{\"type\":\"bytes\",\"logicalType\":\"decimal\",\"precision\":10,\"scale\":6}],\"default\":null}]}]}]}";
+ var actual = typeof(NestedCustomDefinitionTest).GetSchema();
+ _output.WriteLine(actual);
+ var schema = Schema.Parse(actual);
+ var writer = new ReflectWriter(schema);
+
+ Assert.Equal(expectSchema, actual);
+ }
+
+ [Fact]
+ public void TestFailedCustomDefinition()
+ {
+ Assert.Throws(() => typeof(FailedCustomDefinitionTest).GetSchema());
+ }
+
+ private sbyte[] Write(T message, ReflectWriter writer)
+ {
+ var ms = new MemoryStream();
+ Encoder e = new BinaryEncoder(ms);
+ writer.Write(message, e);
+ ms.Flush();
+ ms.Position = 0;
+ var b = ms.ToArray();
+ return (sbyte[])(object)b;
+ }
+ public T Read(Stream stream, ReflectReader reader)
+ {
+ stream.Seek(0, SeekOrigin.Begin);
+ return reader.Read(default, new BinaryDecoder(stream));
+ }
+ }
+ public class SimpleFoo
+ {
+ [Required]
+ public int Age { get; set; }
+ [DefaultValue(null)]
+ public string Name { get; set; }
+ public long FactTime { get; set; }
+ public double Point { get; set; }
+ public float Precision { get; set; }
+ public bool Attending { get; set; }
+ public byte[] Id { get; set; }
+ }
+ public class Foo
+ {
+ public SimpleFoo Fo { get; set; }
+ [Required]
+ public List Courses { get; set; }
+ [Required]
+ public IEnumerable Lessons { get; set; }
+ [Required]
+ public Dictionary Normal { get; set; }
+
+ }
+ public class Dictionary
+ {
+ public SimpleFoo Fo { get; set; }
+ [Required]
+ public List Courses { get; set; }
+ [Required]
+ public Dictionary Families { get; set; }
+
+ }
+ public class DictionaryRecursive
+ {
+ public SimpleFoo Fo { get; set; }
+ [Required]
+ public List Courses { get; set; }
+ [Required]
+ public Dictionary Diction { get; set; }
+
+ }
+ public class FooCustom
+ {
+ public SimpleFoo Fo { get; set; }
+ public List Courses { get; set; }
+ public Dictionary Departments { get; set; }
+ }
+ public class Recursive
+ {
+ public SimpleFoo Fo { get; set; }
+ public Recursive Recurse { get; set; }
+ }
+
+ public class Course
+ {
+ [DefaultValue("200")]
+ [Required]
+ public string Level { get; set; }
+
+ [Required]
+ public int Year { get; set; }
+
+ [DefaultValue("Closed")]
+ public string State { get; set; }
+
+ public string Gender { get; set; }
+ }
+
+ public class School
+ {
+ public string State { get; set; }
+ public string Year { get; set; }
+ public List Schools { get; set; }
+ }
+ public class Lecturers
+ {
+ public int EntryYear { get; set; }
+ public string Name { get; set; }
+ }
+
+ public class Book
+ {
+ public string Author { get; set; }
+ public string Title { get; set; }
+ }
+ public class Family
+ {
+ [Required]
+ public List Members { get; set; }
+ }
+
+ public class Person
+ {
+ [Required]
+ public string FirstName { get; set; }
+ [Required]
+ public string LastName { get; set; }
+ public School Schools { get; set; }
+ public List Books { get; set; }
+ [Required]
+ public List Children { get; set; }
+ }
+
+ public class ClassWhichHasFieldsWithSameType
+ {
+ public Book FirstBook { get; set; }
+ public Book SecondBook { get; set; }
+ }
+
+ [Aliases("InterLives", "CountrySide")]
+ public sealed class ClassWithAliasesWithList
+ {
+ [Aliases("TownHall", "Province")]
+ public string City { get; set; }
+ public string State { get; set; }
+ [Aliases("PopularMovie")]
+ public InnerAliases Movie { get; set; }
+
+ [Aliases("PopularMediaType")]
+ public MediaType Popular { get; set; }
+
+ [Aliases("MovieCollection")]
+ public List Movies { get; set; }
+ }
+
+ [Aliases("InterLives", "CountrySide")]
+ public sealed class ClassWithAliasesWithDictionary
+ {
+ [Aliases("TownHall", "Province")]
+ public string City { get; set; }
+ public string State { get; set; }
+ [Aliases("PopularMovie")]
+ public InnerAliases Movie { get; set; }
+
+ [Aliases("PopularMediaType")]
+ public MediaType Popular { get; set; }
+
+ [Aliases("MoviesByYear")]
+ public Dictionary YearlyMovies { get; set; }
+ }
+
+ public sealed class InnerAliases
+ {
+ [Aliases("Media")]
+ public MediaContainer Container { get; set; }
+ public string Title { get; set; }
+ }
+
+ [Aliases("Movies_Aliase")]
+ public sealed class MovieAliase
+ {
+ [Aliases("DateCreated")]
+ public long Dated { get; set; }
+ [Aliases("ReleaseYear")]
+ public int Year { get; set; }
+ [Aliases("ReleaseMonth")]
+ public Month Month { get; set; }
+ }
+ public class MediaStream
+ {
+ public string Id { get; set; }
+
+ public string Title { get; set; }
+ public MediaType Type { get; set; }
+ public MediaContainer Container { get; set; }
+ public byte[] Media { get; set; }
+ }
+
+ public class MultipleEnums
+ {
+ public MediaType Type { get; set; }
+ public MediaType TypeTo { get; set; }
+ public Month Month { get; set; }
+ public MediaContainer Container { get; set; }
+ public InnerAliases InnerAliases { get; set; }
+ public Dictionary YearlyMovies { get; set; }
+ }
+
+ public enum Month
+ {
+ January,
+ February,
+ March,
+ April,
+ June,
+ July
+ }
+
+ public enum MediaType
+ {
+ Video,
+ Audio
+ }
+
+ public enum MediaContainer
+ {
+ Flv,
+ Mp3,
+ Avi,
+ Mp4
+ }
+
+ public class SimpleWithStaticFields
+ {
+ private static readonly string _staticFieldOnInner = "a field";
+ public static string StaticFieldOnInner => _staticFieldOnInner;
+
+ public string Name { get; set; }
+ }
+
+ public class SimpleWithStaticFieldsAndNestedClass
+ {
+ private static readonly string _staticField = "a field";
+ public static string StaticField => _staticField;
+
+ public long FactTime { get; set; }
+
+ public SimpleWithStaticFields SimpleStaticInner { get; set; }
+ }
+
+ public class SimpleRecord
+ {
+ public string Name { get; set; }
+ public int Age { get; set; }
+ }
+}
+
+public class ClassWithoutNamespace
+{
+ public bool ForReal { get; set; }
+ public string Comment { get; set; }
+ public Book Book { get; set; }
+}
\ No newline at end of file
diff --git a/AvroSchemaGenerator.Tests/LogicalTypeTests.cs b/AvroSchemaGenerator.Tests/LogicalTypeTests.cs
new file mode 100644
index 0000000..221f05d
--- /dev/null
+++ b/AvroSchemaGenerator.Tests/LogicalTypeTests.cs
@@ -0,0 +1,198 @@
+using System;
+using System.IO;
+using Avro;
+using Avro.IO;
+using Avro.Specific;
+using Avro.Util;
+using AvroSchemaGenerator.Attributes;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace AvroSchemaGenerator.Tests
+{
+ [Collection("LogicalTypeTests")]
+ public class LogicalTypeTests
+ {
+ private readonly ITestOutputHelper _output;
+
+ public LogicalTypeTests(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ [Fact]
+ public void LogicalDate()
+ {
+ SpecificDatumReader reader;
+ SpecificDatumWriter writer;
+ var simple = typeof(MessageDateKind).GetSchema();
+ _output.WriteLine(simple);
+ var schema = (RecordSchema)Schema.Parse(simple);
+ reader = new SpecificDatumReader(schema, schema);
+ writer = new SpecificDatumWriter(schema);
+ var msgBytes =
+ Write(
+ new MessageDateKind
+ {
+ Schema = schema,
+ CreatedTime = DateTime.Now,
+ DayOfWeek = "Saturday",
+ Size = new AvroDecimal(102.65M),
+ CustomDecimal = new AvroDecimal(102.6578M)
+ }, writer);
+ using var stream = new MemoryStream((byte[]) (object) msgBytes);
+ var msg = Read(stream, reader);
+ Assert.NotNull(msg);
+ Assert.True(msg.Size == 102.65M);
+ Assert.True(msg.CustomDecimal == 102.6578M);
+ }
+ [Fact]
+ public void LogicalTime()
+ {
+ SpecificDatumReader reader;
+ SpecificDatumWriter writer;
+ var simple = typeof(MessageTimeKind).GetSchema();
+ _output.WriteLine(simple);
+ var schema = (RecordSchema)Schema.Parse(simple);
+ reader = new SpecificDatumReader(schema, schema);
+ writer = new SpecificDatumWriter(schema);
+ var msgBytes = Write(new MessageTimeKind { Schema = schema, TimeMicros = TimeSpan.FromSeconds(60), TimeMillis = TimeSpan.FromSeconds(300) }, writer);
+ using var stream = new MemoryStream((byte[])(object)msgBytes);
+ var msg = Read(stream, reader);
+ Assert.NotNull(msg);
+ }
+ [Fact]
+ public void LogicalTimeStamp()
+ {
+ SpecificDatumReader reader;
+ SpecificDatumWriter writer;
+ var simple = typeof(MessageTimestampKind).GetSchema();
+ _output.WriteLine(simple);
+ var schema = (RecordSchema)Schema.Parse(simple);
+ reader = new SpecificDatumReader(schema, schema);
+ writer = new SpecificDatumWriter(schema);
+ var msgBytes = Write(new MessageTimestampKind { Schema = schema, StampMicros = DateTime.Now, StampMillis = DateTime.Now.AddDays(20)}, writer);
+ using var stream = new MemoryStream((byte[])(object)msgBytes);
+ var msg = Read(stream, reader);
+ Assert.NotNull(msg);
+ }
+ private sbyte[] Write(T message, SpecificDatumWriter writer)
+ {
+ var ms = new MemoryStream();
+ Encoder e = new BinaryEncoder(ms);
+ writer.Write(message, e);
+ ms.Flush();
+ ms.Position = 0;
+ var b = ms.ToArray();
+ return (sbyte[])(object)b;
+ }
+ public T Read(Stream stream, SpecificDatumReader reader)
+ {
+ stream.Seek(0, SeekOrigin.Begin);
+ return reader.Read(default, new BinaryDecoder(stream));
+ }
+ }
+ public class MessageTimeKind: ISpecificRecord
+ {
+ [LogicalType(LogicalTypeKind.TimeMicrosecond)]
+ public TimeSpan TimeMicros { get; set; }
+
+ [LogicalType(LogicalTypeKind.TimeMillisecond)]
+ public TimeSpan TimeMillis { get; set; }
+
+ [Ignore]
+ public Schema Schema { get; set; }
+
+ public object Get(int fieldPos)
+ {
+ switch (fieldPos)
+ {
+ case 0: return TimeMicros;
+ case 1: return TimeMillis;
+ default: throw new AvroRuntimeException("Bad index " + fieldPos + " in Get()");
+ };
+ }
+
+ public void Put(int fieldPos, object fieldValue)
+ {
+ switch (fieldPos)
+ {
+ case 0: TimeMicros = (TimeSpan)fieldValue; break;
+ case 1: TimeMillis = (TimeSpan)fieldValue; break;
+ default: throw new AvroRuntimeException("Bad index " + fieldPos + " in Put()");
+ };
+ }
+ }
+ public class MessageTimestampKind: ISpecificRecord
+ {
+ [LogicalType(LogicalTypeKind.TimestampMicrosecond)]
+ public DateTime StampMicros { get; set; }
+
+ [LogicalType(LogicalTypeKind.TimestampMillisecond)]
+ public DateTime StampMillis { get; set; }
+
+ [Ignore]
+ public Schema Schema { get; set; }
+
+ public object Get(int fieldPos)
+ {
+ switch (fieldPos)
+ {
+ case 0: return StampMicros;
+ case 1: return StampMillis;
+ default: throw new AvroRuntimeException("Bad index " + fieldPos + " in Get()");
+ };
+ }
+
+ public void Put(int fieldPos, object fieldValue)
+ {
+ switch (fieldPos)
+ {
+ case 0: StampMicros = (DateTime)fieldValue; break;
+ case 1: StampMillis = (DateTime)fieldValue; break;
+ default: throw new AvroRuntimeException("Bad index " + fieldPos + " in Put()");
+ };
+ }
+ }
+ public class MessageDateKind: ISpecificRecord
+ {
+ [LogicalType(LogicalTypeKind.Date)]
+ public DateTime CreatedTime { get; set; }
+ public AvroDecimal Size { get; set; }
+ public string DayOfWeek { get; set; }
+ [AvroSchema("{\n" +
+ " \"type\": \"bytes\",\n" +
+ " \"logicalType\": \"decimal\",\n" +
+ " \"precision\": 8,\n" +
+ " \"scale\": 4\n" +
+ "}")]
+ public AvroDecimal CustomDecimal { get; set; }
+
+ [Ignore]
+ public Schema Schema { get; set; }
+
+ public object Get(int fieldPos)
+ {
+ switch (fieldPos)
+ {
+ case 0: return CreatedTime;
+ case 1: return Size;
+ case 2: return DayOfWeek;
+ case 3: return CustomDecimal;
+ default: throw new AvroRuntimeException("Bad index " + fieldPos + " in Get()");
+ };
+ }
+
+ public void Put(int fieldPos, object fieldValue)
+ {
+ switch (fieldPos)
+ {
+ case 0: CreatedTime = (DateTime)fieldValue; break;
+ case 1: Size = (AvroDecimal)fieldValue; break;
+ case 2: DayOfWeek = (System.String)fieldValue; break;
+ case 3: CustomDecimal = (AvroDecimal) fieldValue; break;
+ default: throw new AvroRuntimeException("Bad index " + fieldPos + " in Put()");
+ };
+ }
+ }
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..1142534
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,44 @@
+## [vNext]
+
+## [2.10.0] / 2023-12-16
+- [Add] `net8.0`
+- [update] NUKE 7.0.6
+- [Added] `CODE OF CONDUCT`
+
+## [2.9.2] / 2023-07-10
+- Add `netstandard2.0`
+- [update] NUKE 7.0.2
+
+## [2.9.1] / 2023-03-19
+- Package License
+
+## [2.9.0] / 2023-02-04
+- The Guid type in .NET logically maps the the AVRO UUID type @addisonj [https://github.com/eaba/AvroSchemaGenerator/commit/a4cd2255306531fbf24064947377789dc730f2c6]
+
+## [2.8.0] / 2022-12-24
+- [update] .NET 7
+- [fix] Resolves #64 Fixed that the same enum can be used multiple times in the schema @danny-krueger [https://github.com/eaba/AvroSchemaGenerator/pull/74/commits/ba622efc263f89ed8d5bc826493cf8a33637b580]
+
+## [2.7.0] / 2022-07-20
+- Add Custom AVRO definition support @RobertIndie [https://github.com/eaba/AvroSchemaGenerator/commit/81272d5e24e63e152369c14b6c564333fc3aa9ac]
+
+## [2.6.0] / 2022-02-20
+- Add back support for NET standard 2.0
+- Add `IEnumerabl<>` support - @ThomasHarzer [https://github.com/eaba/AvroSchemaGenerator/commit/38c38308d183ecda416ce5b8b946ebe681c2f171]
+
+## [2.5.2-Alpha.1] / 2022-01-16
+- Improved `CI`
+
+## [2.5.1] / 2022-01-14
+- Add `.NET 6` support
+
+[vNext]: https://github.com/eaba/AvroSchemaGenerator/compare/2.10.0...HEAD
+[2.10.0]: https://github.com/eaba/AvroSchemaGenerator/compare/2.9.2...2.10.0
+[2.9.2]: https://github.com/eaba/AvroSchemaGenerator/compare/2.9.1...2.9.2
+[2.9.1]: https://github.com/eaba/AvroSchemaGenerator/compare/2.9.0...2.9.1
+[2.9.0]: https://github.com/eaba/AvroSchemaGenerator/compare/2.8.0...2.9.0
+[2.8.0]: https://github.com/eaba/AvroSchemaGenerator/compare/2.7.0...2.8.0
+[2.7.0]: https://github.com/eaba/AvroSchemaGenerator/compare/2.6.0...2.7.0
+[2.6.0]: https://github.com/eaba/AvroSchemaGenerator/compare/2.5.2-Alpha.1...2.6.0
+[2.5.2-Alpha.1]: https://github.com/eaba/AvroSchemaGenerator/compare/2.5.1...2.5.2-Alpha.1
+[2.5.1]: https://github.com/eaba/AvroSchemaGenerator/tree/2.5.1
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..68add25
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,5 @@
+# Code of Conduct
+
+This project has adopted the code of conduct defined by the Contributor Covenant
+to clarify expected behavior in our community.
+For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..e147d63
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/GitVersion.yml b/GitVersion.yml
new file mode 100644
index 0000000..b55263c
--- /dev/null
+++ b/GitVersion.yml
@@ -0,0 +1,50 @@
+assembly-versioning-scheme: MajorMinorPatch
+assembly-file-versioning-scheme: MajorMinorPatch
+mode: ContinuousDelivery
+next-version: 2.9.1
+branches: # https://gitversion.net/docs/learn/branching-strategies/gitflow/examples
+ main:
+ regex: ^master$|^main$
+ mode: ContinuousDelivery
+ tag: ''
+ prevent-increment-of-merged-branch-version: true
+ track-merge-target: false
+ tracks-release-branches: false
+ is-release-branch: false
+ is-mainline: true
+ pre-release-weight: 55000
+ develop:
+ regex: ^dev(elop)?(ment)?$
+ mode: ContinuousDeployment
+ tag: rc
+ increment: Minor
+ prevent-increment-of-merged-branch-version: false
+ track-merge-target: true
+ tracks-release-branches: true
+ is-release-branch: false
+ is-mainline: false
+ pre-release-weight: 0
+ release:
+ regex: release?$
+ mode: ContinuousDelivery
+ tag: beta
+ increment: Patch
+ prevent-increment-of-merged-branch-version: true
+ track-merge-target: false
+ tracks-release-branches: false
+ is-release-branch: true
+ is-mainline: false
+ pre-release-weight: 30000
+ feature:
+ regex: ^features?[/-]
+ mode: ContinuousDelivery
+ tag: useBranchName
+ increment: Inherit
+ prevent-increment-of-merged-branch-version: false
+ track-merge-target: false
+ tracks-release-branches: false
+ is-release-branch: false
+ is-mainline: false
+ pre-release-weight: 30000
+ignore:
+ sha: []
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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
+
+ 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2951a49
--- /dev/null
+++ b/README.md
@@ -0,0 +1,127 @@
+# AvroSchemaGenerator
+Use to generate Avro Schema with support for **RECURSIVE SCHEMA**
+
+## Getting Started
+Install the NuGet package [AvroSchemaGenerator](https://www.nuget.org/packages/AvroSchemaGenerator/) and copy/paste the code below
+
+```csharp
+using AvroSchemaGenerator;
+public class Course
+{
+ public string Level { get; set; }
+
+ public int Year { get; set; }
+
+ public string State { get; set; }
+
+ public string Gender { get; set; }
+}
+var avroSchema = typeof(Course).GetSchema();
+```
+
+By default, `AvroSchemaGenerator` generates schema with optional fields. The code below is an example of how to mark fields as required
+
+```csharp
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using AvroSchemaGenerator;
+public class Course
+{
+ [Required]
+ public string Level { get; set; }
+
+ [Required]
+ public int Year { get; set; }
+
+ [Required]
+ public string State { get; set; }
+
+ [Required]
+ public string Gender { get; set; }
+}
+var avroSchema = typeof(Course).GetSchema();
+```
+
+You can assign a default value as well
+```csharp
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using AvroSchemaGenerator;
+public class Course
+{
+ [DefaultValue("200")]
+ [Required]
+ public string Level { get; set; }
+
+ [Required]
+ public int Year { get; set; }
+
+ [DefaultValue("Closed")]
+ public string State { get; set; }
+
+ public string Gender { get; set; }
+}
+var avroSchema = typeof(Course).GetSchema();
+```
+## Aliases
+```csharp
+[Aliases("OldCourse")]
+public class Course
+{
+ [Aliases("Level")]
+ public string NewLevel { get; set; }
+}
+```
+## Logical Types
+```csharp
+public class MessageTimeKind
+{
+ [LogicalType(LogicalTypeKind.TimeMicrosecond)]
+ public TimeSpan TimeMicros { get; set; }
+
+ [LogicalType(LogicalTypeKind.TimeMillisecond)]
+ public TimeSpan TimeMillis { get; set; }
+}
+
+public class MessageTimestampKind
+{
+ [LogicalType(LogicalTypeKind.TimestampMicrosecond)]
+ public DateTime StampMicros { get; set; }
+
+ [LogicalType(LogicalTypeKind.TimestampMillisecond)]
+ public DateTime StampMillis { get; set; }
+}
+
+public class MessageDateKind
+{
+ [LogicalType(LogicalTypeKind.Date)]
+ public DateTime CreatedTime { get; set; }
+
+ public AvroDecimal Size { get; set; }
+
+ public string DayOfWeek { get; set; }
+}
+```
+
+## Custom Avro Definition
+```csharp
+public class CustomDefinition
+{
+ [AvroSchema("{\n" +
+ " \"type\": \"bytes\",\n" +
+ " \"logicalType\": \"decimal\",\n" +
+ " \"precision\": 10,\n" +
+ " \"scale\": 6\n" +
+ "}")]
+ public AvroDecimal DecimalAvro { get; set; }
+}
+```
+
+## NOTE
+- Don't use same declaring type as dictionary value
+- Don't use same declaring type as list argument
+- Dictionary key must be a string type
+
+## License
+
+This project is licensed under the Apache License Version 2.0 - see the [LICENSE](LICENSE) file for details.
\ No newline at end of file
diff --git a/SchemaGenerator.sln b/SchemaGenerator.sln
new file mode 100644
index 0000000..98d8949
--- /dev/null
+++ b/SchemaGenerator.sln
@@ -0,0 +1,42 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.32014.148
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvroSchemaGenerator", "SchemaGenerator\AvroSchemaGenerator.csproj", "{F13C8E45-F5C8-48EB-B879-63DD464FEFF0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AvroSchemaGenerator.Tests", "AvroSchemaGenerator.Tests\AvroSchemaGenerator.Tests.csproj", "{30808D11-0F77-4CCD-82C0-10946CA397D4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{DF485363-E6D3-47E3-9A82-EC2D0E50C491}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FC014F32-B268-4452-8C08-4FE19A2BDBF3}"
+ ProjectSection(SolutionItems) = preProject
+ CHANGELOG.md = CHANGELOG.md
+ GitVersion.yml = GitVersion.yml
+ README.md = README.md
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F13C8E45-F5C8-48EB-B879-63DD464FEFF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F13C8E45-F5C8-48EB-B879-63DD464FEFF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F13C8E45-F5C8-48EB-B879-63DD464FEFF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F13C8E45-F5C8-48EB-B879-63DD464FEFF0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {30808D11-0F77-4CCD-82C0-10946CA397D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {30808D11-0F77-4CCD-82C0-10946CA397D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {30808D11-0F77-4CCD-82C0-10946CA397D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {30808D11-0F77-4CCD-82C0-10946CA397D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DF485363-E6D3-47E3-9A82-EC2D0E50C491}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DF485363-E6D3-47E3-9A82-EC2D0E50C491}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3437C2B5-7977-4B47-94B6-31328FD6847D}
+ EndGlobalSection
+EndGlobal
diff --git a/SchemaGenerator/Attributes/Aliases.cs b/SchemaGenerator/Attributes/Aliases.cs
new file mode 100644
index 0000000..f45ea63
--- /dev/null
+++ b/SchemaGenerator/Attributes/Aliases.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AvroSchemaGenerator.Attributes
+{
+ [AttributeUsage(AttributeTargets.All)]
+ public class AliasesAttribute : Attribute
+ {
+ private readonly List _values;
+
+ public AliasesAttribute(params string[] value)
+ {
+ _values = value.ToList();
+ }
+ public virtual List Values => _values;
+ }
+}
diff --git a/SchemaGenerator/Attributes/AvroSchema.cs b/SchemaGenerator/Attributes/AvroSchema.cs
new file mode 100644
index 0000000..18da0d4
--- /dev/null
+++ b/SchemaGenerator/Attributes/AvroSchema.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace AvroSchemaGenerator.Attributes
+{
+ [AttributeUsage(AttributeTargets.Property)]
+ public class AvroSchemaAttribute : Attribute
+ {
+ public string Value { get; }
+
+ public AvroSchemaAttribute(string value)
+ {
+ Value = value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/SchemaGenerator/Attributes/Ignore.cs b/SchemaGenerator/Attributes/Ignore.cs
new file mode 100644
index 0000000..a238b89
--- /dev/null
+++ b/SchemaGenerator/Attributes/Ignore.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace AvroSchemaGenerator.Attributes
+{
+ [AttributeUsage(AttributeTargets.Property)]
+ public class IgnoreAttribute : Attribute
+ {
+ }
+}
diff --git a/SchemaGenerator/Attributes/LogicalType.cs b/SchemaGenerator/Attributes/LogicalType.cs
new file mode 100644
index 0000000..c4897c0
--- /dev/null
+++ b/SchemaGenerator/Attributes/LogicalType.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace AvroSchemaGenerator.Attributes
+{
+ [AttributeUsage(AttributeTargets.Property)]
+ public class LogicalTypeAttribute : Attribute
+ {
+ public LogicalTypeKind Kind { get; }
+ public LogicalTypeAttribute(LogicalTypeKind kind)
+ {
+ Kind = kind;
+ }
+ }
+ public enum LogicalTypeKind
+ {
+ Date,
+ TimeMicrosecond,
+ TimeMillisecond,
+ TimestampMicrosecond,
+ TimestampMillisecond
+ }
+}
diff --git a/SchemaGenerator/AvroSchemaGenerator.csproj b/SchemaGenerator/AvroSchemaGenerator.csproj
new file mode 100644
index 0000000..72c43b4
--- /dev/null
+++ b/SchemaGenerator/AvroSchemaGenerator.csproj
@@ -0,0 +1,33 @@
+
+
+
+ netstandard2.0;net8.0
+ https://github.com/eaba/SchemaGenerator
+ https://github.com/eaba/SchemaGenerator
+ README.md
+ AvroSchemaGenerator
+ en-US
+ Apache Avro, Avro Schema
+ avro-schema.png
+ Apache-2.0
+
+ false
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ True
+
+
+
+
+
diff --git a/SchemaGenerator/DefaultValue.cs b/SchemaGenerator/DefaultValue.cs
new file mode 100644
index 0000000..c4cd39e
--- /dev/null
+++ b/SchemaGenerator/DefaultValue.cs
@@ -0,0 +1,31 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Reflection;
+using AvroSchemaGenerator.Attributes;
+
+namespace AvroSchemaGenerator
+{
+ public static class DefaultValue
+ {
+ public static (bool required, bool hasDefault, object defaultValue) GetSchemaCustomAttributes(this PropertyInfo property)
+ {
+ var required = IsRequiredProperty(property);
+ var dv = GetDefaultValueForProperty(property);
+ return (required, dv.hasDefault, dv.defaultValue);
+ }
+ private static (bool hasDefault, object defaultValue) GetDefaultValueForProperty(PropertyInfo property)
+ {
+ var defaultAttr = (DefaultValueAttribute)property.GetCustomAttribute(typeof(DefaultValueAttribute));
+ return defaultAttr != null ? (true, defaultAttr.Value) : (false, null);
+ }
+ private static bool IsRequiredProperty(PropertyInfo property)
+ {
+ return property.GetCustomAttribute(typeof(RequiredAttribute)) != null;
+ }
+
+ public static string GetAvroCustomDefinition(this PropertyInfo property)
+ {
+ return ((AvroSchemaAttribute) property.GetCustomAttribute(typeof(AvroSchemaAttribute)))?.Value;
+ }
+ }
+}
diff --git a/SchemaGenerator/GenerateSchema.cs b/SchemaGenerator/GenerateSchema.cs
new file mode 100644
index 0000000..24c9468
--- /dev/null
+++ b/SchemaGenerator/GenerateSchema.cs
@@ -0,0 +1,1607 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text.Json;
+using AvroSchemaGenerator.Attributes;
+
+namespace AvroSchemaGenerator
+{
+ //https://docs.oracle.com/database/nosql-12.1.3.0/GettingStartedGuide/avroschemas.html
+ //https://www.jsonschemavalidator.net/
+ //https://json-schema-validator.herokuapp.com/avro.jsp
+ //https://stackoverflow.com/questions/13522132/how-to-exclude-static-property-when-using-getproperties-method
+ // ReSharper disable once UnusedType.Global
+ public static class GenerateSchema
+ {
+ // ReSharper disable once UnusedMember.Global
+ public static Dictionary GetSchemaObject(this Type type)
+ {
+ if (IsDictionary(type))
+ {
+ var sch = new Dictionary();
+ if (!string.IsNullOrEmpty(type.Namespace))
+ {
+ sch.Add("namespace", type.Namespace);
+ }
+ sch["type"] = "map";
+ var arg = type.GetGenericArguments()[1];
+ if (IsUserDefined(arg))
+ {
+ sch["values"] = GetGenericUserDefinedProperties(arg, true, new List());
+ }
+ else
+ {
+ sch["values"] = ToAvroDataType(arg.Name, null);
+ }
+ sch["default"] = new Dictionary();
+ return sch;
+ }
+ if (IsList(type))
+ {
+ var sch = new Dictionary();
+ if (!string.IsNullOrEmpty(type.Namespace))
+ {
+ sch.Add("namespace", type.Namespace);
+ }
+ sch["type"] = "array";
+ var arg = type.GetGenericArguments()[0];
+ if (IsUserDefined(arg))
+ {
+ sch["items"] = GetGenericUserDefinedProperties(arg, true, new List());
+ }
+ else
+ {
+ sch["items"] = ToAvroDataType(arg.Name, null);
+ }
+ sch["default"] = new List();
+ return sch;
+ }
+ var schema = new Dictionary();
+ if (!string.IsNullOrEmpty(type.Namespace))
+ {
+ schema.Add("namespace", type.Namespace);
+ }
+ schema["name"] = type.Name;
+ var aliases = GetAliases(type);
+ if (aliases != null)
+ {
+ schema["aliases"] = aliases;
+ }
+
+ if (type.IsEnum)
+ {
+ schema["type"] = "enum";
+ schema["symbols"] = GetEnumValues(type);
+ return schema;
+ }
+
+ // record type
+ schema["type"] = "record";
+ schema["fields"] = new List>();
+ var existingTypes = new List();
+ var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
+ foreach (var p in properties)
+ {
+
+ if (!ShouldIgnore(p))
+ PropertyInfo(p, schema, existingTypes);
+ }
+
+ return schema;
+ }
+
+ // ReSharper disable once UnusedMember.Global
+ public static string GetSchema(this Type type)
+ {
+ return JsonSerializer.Serialize(type.GetSchemaObject());
+ }
+
+ private static void PropertyInfo(PropertyInfo property, Dictionary finalSchema,
+ List existingTypes)
+ {
+ var p = property;
+
+ var customDefinition = CheckAndHandleCustomSchema(p);
+ if (customDefinition != null)
+ {
+ var field = (List>) finalSchema["fields"];
+ field.Add(customDefinition);
+ finalSchema["fields"] = field;
+ return;
+ }
+
+ if (p.PropertyType.FullName != null && p.PropertyType.FullName.StartsWith("Avro."))
+ {
+ AddFields(p, finalSchema);
+ }
+ else
+ {
+ if (IsUserDefined(p))
+ {
+ if (p.PropertyType.GetSubTypeIfNullable().IsEnum)
+ {
+ var aliases = GetAliases(p);
+ var row = GetEnumField(p, existingTypes);
+ if (aliases != null)
+ {
+ var rows = row.ToList();
+ rows.Insert(1, new KeyValuePair("aliases", aliases));
+ row = rows.ToDictionary(x => x.Key, x => x.Value);
+ }
+
+ var fd = (List>) finalSchema["fields"];
+ fd.Add(row);
+ finalSchema["fields"] = fd;
+ return;
+ }
+
+ var t = p.PropertyType.Name;
+ var dt = p.DeclaringType?.Name;
+ var recursive = t.Equals(dt);
+
+ if (existingTypes.Contains(t))
+ {
+ List