diff --git a/README.md b/README.md
index 08f5974a8..d3125289b 100644
--- a/README.md
+++ b/README.md
@@ -400,6 +400,10 @@ Generates Feature Files in `Corvus.Json.Specs` for the JSON Patch tests.
Benchmark suites for various components.
+### Corvus.Json.SourceGenerator
+
+The Source Generator which generates types from Json Schema.
+
## V4.0 Updates
There are a number of significant changes in this release
@@ -427,6 +431,72 @@ If so, you can now use the `--useImplicitOperatorString` command line switch to
Note: this means you will never use the built-in `Corvus.Json` types for your string-like types. This could increase the amount of code generated for your schema.
+### New Source Generator
+
+We now have a source generator that can generate types at compile time, rather than using the `generatejsonschematypes` tool.
+
+### Using the source generator
+
+Add a reference to the `Corvus.Json.SourceGenerator` nuget package in addition to `Corvus.Json.ExtendedTypes`. [Note, you may need to restart Visual Studio once you have done this.]
+Add your JSON schema file(s) as _AdditionalFiles_.C
+
+```xml
+
+
+
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Now, create a `readonly partial struct` as a placeholder for your root generated type, and attribute it with
+`[JsonSchemaTypeGenerator]`. The path to the schema file is relative to the file containing the attribute. You can
+provide a pointer fragment in the usual way, if you need to e.g. `"./somefile.json#/components/schema/mySchema"`
+
+```csharp
+namespace SourceGenTest2.Model;
+
+using Corvus.Json;
+
+[JsonSchemaTypeGenerator("../test.json")]
+public readonly partial struct FlimFlam
+{
+}
+```
+
+The source generator will now automatically emit code for your schema, and you can use the generated types in your code.
+
+```
+using Corvus.Json;
+using SourceGenTest2.Model;
+
+FlimFlam flimFlam = JsonAny.ParseValue("[1,2,3]"u8);
+Console.WriteLine(flimFlam);
+JsonArray array = flimFlam.As();
+Console.WriteLine(array);
+```
+
+You can find an example project here: [Sandbox.SourceGenerator](./Solutions/Sandbox.SourceGenerator)
+
+We'd like to credit our Google Summer of Code 2024 contributor, [Pranay Joshi](https://github.com/pranayjoshi) and mentor [Greg Dennis](https://github.com/gregsdennis) for their work on this tool.
+
### New dynamic schema validation
There is a new `Corvus.Json.Validator` assembly, containing a `JsonSchema` type.
diff --git a/Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/PublicCodeGeneratorExtensions.cs b/Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/PublicCodeGeneratorExtensions.cs
index f28679957..369a1785d 100644
--- a/Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/PublicCodeGeneratorExtensions.cs
+++ b/Solutions/Corvus.Json.CodeGeneration.CSharp/Corvus.Json.CodeGeneration/CSharp/PublicCodeGeneratorExtensions.cs
@@ -243,9 +243,9 @@ public static CodeGenerator AppendSeparatorLine(this CodeGenerator generator)
return generator;
}
- if ((generator.ScopeType == ScopeType.Type && generator.EndsWith($";{Environment.NewLine}")) ||
- generator.EndsWith($"}}{Environment.NewLine}") ||
- generator.EndsWith($"#endif{Environment.NewLine}"))
+ if ((generator.ScopeType == ScopeType.Type && generator.EndsWith($";\n")) ||
+ generator.EndsWith($"}}\n") ||
+ generator.EndsWith($"#endif\n"))
{
// Append a blank line
generator.AppendLine();
diff --git a/Solutions/Corvus.Json.SourceGenerator/Corvus.HighPerformance/ValueStringBuilder.AppendSpanFormattable.cs b/Solutions/Corvus.Json.SourceGenerator/Corvus.HighPerformance/ValueStringBuilder.AppendSpanFormattable.cs
new file mode 100644
index 000000000..14446f634
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/Corvus.HighPerformance/ValueStringBuilder.AppendSpanFormattable.cs
@@ -0,0 +1,31 @@
+//
+// Copyright (c) Endjin Limited. All rights reserved.
+//
+//
+// Derived from code Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See https://github.com/dotnet/runtime/blob/c1049390d5b33483203f058b0e1457d2a1f62bf4/src/libraries/Common/src/System/Text/ValueStringBuilder.cs
+//
+
+#pragma warning disable // Currently this is a straight copy of the original code, so we disable warnings to avoid diagnostic problems.
+
+#if NET8_0_OR_GREATER
+
+namespace Corvus.HighPerformance;
+
+public ref partial struct ValueStringBuilder
+{
+ public void AppendSpanFormattable(T value, string? format = null, IFormatProvider? provider = null) where T : ISpanFormattable
+ {
+ if (value.TryFormat(_chars.Slice(_pos), out int charsWritten, format, provider))
+ {
+ _pos += charsWritten;
+ }
+ else
+ {
+ Append(value.ToString(format, provider));
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/Solutions/Corvus.Json.SourceGenerator/Corvus.HighPerformance/ValueStringBuilder.Replace.cs b/Solutions/Corvus.Json.SourceGenerator/Corvus.HighPerformance/ValueStringBuilder.Replace.cs
new file mode 100644
index 000000000..414513b4c
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/Corvus.HighPerformance/ValueStringBuilder.Replace.cs
@@ -0,0 +1,165 @@
+//
+// Copyright (c) Endjin Limited. All rights reserved.
+//
+//
+// Derived from code Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See https://github.com/dotnet/runtime/blob/c1049390d5b33483203f058b0e1457d2a1f62bf4/src/libraries/Common/src/System/Text/ValueStringBuilder.cs
+//
+
+#pragma warning disable // Currently this is a straight copy of the original code, so we disable warnings to avoid diagnostic problems.
+
+#if !NET8_0_OR_GREATER
+using System.Runtime.CompilerServices;
+#endif
+
+namespace Corvus.HighPerformance;
+
+public ref partial struct ValueStringBuilder
+{
+#if !NET8_0_OR_GREATER
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Replace(
+ string oldValue,
+ string newValue,
+ int startIndex,
+ int count)
+ => Replace(oldValue.AsSpan(), newValue.AsSpan(), startIndex, count);
+#endif
+
+ public void Replace(
+ ReadOnlySpan oldValue,
+ ReadOnlySpan newValue,
+ int startIndex,
+ int count)
+ {
+ if (startIndex < 0 || (startIndex + count) > _pos)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex));
+ }
+
+ if (count == 0)
+ {
+ return;
+ }
+
+ Span rangeBuffer = _chars.Slice(startIndex, count);
+
+ int diff = newValue.Length - oldValue.Length;
+ if (diff == 0)
+ {
+ int matchIndex = rangeBuffer.IndexOf(oldValue);
+ if (matchIndex == -1)
+ {
+ return;
+ }
+
+ Span remainingBuffer = rangeBuffer;
+ do
+ {
+ remainingBuffer = remainingBuffer[matchIndex..];
+ newValue.CopyTo(remainingBuffer);
+ remainingBuffer = remainingBuffer[oldValue.Length..];
+
+ matchIndex = remainingBuffer.IndexOf(oldValue);
+ } while (matchIndex != -1);
+
+ return;
+ }
+
+ if (diff < 0)
+ {
+ int matchIndex = rangeBuffer.IndexOf(oldValue);
+ if (matchIndex == -1)
+ {
+ return;
+ }
+
+ // We will never need to grow the buffer, but we might need to shift characters
+ // down.
+ Span remainingTargetBuffer = _chars[(startIndex + matchIndex)..this._pos];
+ Span remainingSourceBuffer = remainingTargetBuffer;
+ int endOfSearchRangeRelativeToRemainingSourceBuffer = count - matchIndex;
+ do
+ {
+ this._pos += diff;
+
+ newValue.CopyTo(remainingTargetBuffer);
+
+ remainingSourceBuffer = remainingSourceBuffer[oldValue.Length..];
+ endOfSearchRangeRelativeToRemainingSourceBuffer -= oldValue.Length;
+ remainingTargetBuffer = remainingTargetBuffer[newValue.Length..];
+
+ matchIndex = remainingSourceBuffer[..endOfSearchRangeRelativeToRemainingSourceBuffer]
+ .IndexOf(oldValue);
+
+ int lengthOfChunkToRelocate = matchIndex == -1
+ ? remainingSourceBuffer.Length
+ : matchIndex;
+ remainingSourceBuffer[..lengthOfChunkToRelocate].CopyTo(remainingTargetBuffer);
+
+ remainingSourceBuffer = remainingSourceBuffer[lengthOfChunkToRelocate..];
+ endOfSearchRangeRelativeToRemainingSourceBuffer -= lengthOfChunkToRelocate;
+ remainingTargetBuffer = remainingTargetBuffer[lengthOfChunkToRelocate..];
+ } while (matchIndex != -1);
+
+ return;
+ }
+ else
+ {
+ int matchIndex = rangeBuffer.IndexOf(oldValue);
+ if (matchIndex == -1)
+ {
+ return;
+ }
+
+ Span matchIndexes = stackalloc int[(rangeBuffer.Length + oldValue.Length - 1) / oldValue.Length];
+
+ int matchCount = 0;
+ int currentRelocationDistance = 0;
+ while (matchIndex != -1)
+ {
+ matchIndexes[matchCount++] = matchIndex;
+ currentRelocationDistance += diff;
+
+ int nextIndex = rangeBuffer[(matchIndex + oldValue.Length)..].IndexOf(oldValue);
+ matchIndex = nextIndex == -1 ? -1 : matchIndex + nextIndex + oldValue.Length;
+ }
+
+ int relocationRangeEndIndex = this._pos;
+
+ int growBy = (this._pos + currentRelocationDistance) - _chars.Length;
+ if (growBy > 0)
+ {
+ Grow(growBy);
+ }
+ this._pos += currentRelocationDistance;
+
+
+ // We work from the back of the string when growing to avoid having to
+ // shift anything more than once.
+ do
+ {
+ matchIndex = matchIndexes[matchCount - 1];
+
+ int relocationTargetStart = startIndex + matchIndex + oldValue.Length + currentRelocationDistance;
+ int relocationSourceStart = startIndex + matchIndex + oldValue.Length;
+ int endOfSearchRangeRelativeToRemainingSourceBuffer = count - matchIndex;
+
+ Span relocationTargetBuffer = _chars[relocationTargetStart..];
+ Span sourceBuffer = _chars[relocationSourceStart..relocationRangeEndIndex];
+
+ sourceBuffer.CopyTo(relocationTargetBuffer);
+
+ currentRelocationDistance -= diff;
+ Span replaceTargetBuffer = this._chars.Slice(startIndex + matchIndex + currentRelocationDistance);
+ newValue.CopyTo(replaceTargetBuffer);
+
+ relocationRangeEndIndex = matchIndex + startIndex;
+ matchIndex = rangeBuffer[..matchIndex].LastIndexOf(oldValue);
+
+ matchCount -= 1;
+ } while (matchCount > 0);
+ }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/Corvus.HighPerformance/ValueStringBuilder.cs b/Solutions/Corvus.Json.SourceGenerator/Corvus.HighPerformance/ValueStringBuilder.cs
new file mode 100644
index 000000000..2472185a0
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/Corvus.HighPerformance/ValueStringBuilder.cs
@@ -0,0 +1,413 @@
+//
+// Copyright (c) Endjin Limited. All rights reserved.
+//
+//
+// Derived from code Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See https://github.com/dotnet/runtime/blob/c1049390d5b33483203f058b0e1457d2a1f62bf4/src/libraries/Common/src/System/Text/ValueStringBuilder.cs
+//
+
+#pragma warning disable // Currently this is a straight copy of the original code, so we disable warnings to avoid diagnostic problems.
+
+using System.Buffers;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+#nullable enable
+
+namespace Corvus.HighPerformance;
+
+public ref partial struct ValueStringBuilder
+{
+ private char[]? _arrayToReturnToPool;
+ private Span _chars;
+ private int _pos;
+
+ public ValueStringBuilder(Span initialBuffer)
+ {
+ _arrayToReturnToPool = null;
+ _chars = initialBuffer;
+ _pos = 0;
+ }
+
+ public ValueStringBuilder(int initialCapacity)
+ {
+ _arrayToReturnToPool = ArrayPool.Shared.Rent(initialCapacity);
+ _chars = _arrayToReturnToPool;
+ _pos = 0;
+ }
+
+ public int Length
+ {
+ get
+ {
+ if (_pos < 0) { throw new ObjectDisposedException("Either Dispose or GetRentedBuffer or GetRentedBufferAndLength has already been called"); }
+ return _pos;
+ }
+
+ set
+ {
+ Debug.Assert(value >= 0);
+ Debug.Assert(value <= _chars.Length);
+ _pos = value;
+ }
+ }
+
+ public int Capacity => _chars.Length;
+
+ ///
+ /// Return the underlying rented buffer, if any, and the length of the string. This also
+ /// disposes the instance.
+ ///
+ ///
+ ///
+ /// Once you have retrieved this, you must not make any further use of
+ /// . You should call
+ /// once you no longer require the buffer.
+ ///
+ ///
+ public (char[]? Buffer, int Length) GetRentedBufferAndLengthAndDispose()
+ {
+ char[]? result = _arrayToReturnToPool;
+ int length = Length;
+ SetToDisposed();
+ return (result, length);
+ }
+
+ ///
+ /// Returns the buffer retrieved from .
+ ///
+ /// The buffer to return.
+ public static void ReturnRentedBuffer(char[]? buffer)
+ {
+ if (buffer is char[] b)
+ {
+ ArrayPool.Shared.Return(b);
+ }
+ }
+
+ public void EnsureCapacity(int capacity)
+ {
+ // This is not expected to be called this with negative capacity
+ Debug.Assert(capacity >= 0);
+
+ // If the caller has a bug and calls this with negative capacity, make sure to call Grow to throw an exception.
+ if ((uint)capacity > (uint)_chars.Length)
+ Grow(capacity - _pos);
+ }
+
+ ///
+ /// Get a pinnable reference to the builder.
+ /// Does not ensure there is a null char after
+ /// This overload is pattern matched in the C# 7.3+ compiler so you can omit
+ /// the explicit method call, and write eg "fixed (char* c = builder)"
+ ///
+ public ref char GetPinnableReference()
+ {
+ return ref MemoryMarshal.GetReference(_chars);
+ }
+
+ ///
+ /// Get a pinnable reference to the builder.
+ ///
+ /// Ensures that the builder has a null char after
+ public ref char GetPinnableReference(bool terminate)
+ {
+ if (terminate)
+ {
+ EnsureCapacity(Length + 1);
+ _chars[Length] = '\0';
+ }
+ return ref MemoryMarshal.GetReference(_chars);
+ }
+
+ public ref char this[int index]
+ {
+ get
+ {
+ Debug.Assert(index < _pos);
+ return ref _chars[index];
+ }
+ }
+
+ public string CreateStringAndDispose()
+ {
+ string s = _chars.Slice(0, _pos).ToString();
+ Dispose();
+ return s;
+ }
+
+ /// Returns the underlying storage of the builder.
+ public Span RawChars => _chars;
+
+ ///
+ /// Returns a span around the contents of the builder.
+ ///
+ /// Ensures that the builder has a null char after
+ public ReadOnlySpan AsSpan(bool terminate)
+ {
+ if (terminate)
+ {
+ EnsureCapacity(Length + 1);
+ _chars[Length] = '\0';
+ }
+ return _chars.Slice(0, _pos);
+ }
+
+ public ReadOnlySpan AsSpan() => _chars.Slice(0, _pos);
+ public ReadOnlySpan AsSpan(int start) => _chars.Slice(start, _pos - start);
+ public ReadOnlySpan AsSpan(int start, int length) => _chars.Slice(start, length);
+
+ public bool TryCopyTo(Span destination, out int charsWritten)
+ {
+ if (_chars.Slice(0, _pos).TryCopyTo(destination))
+ {
+ charsWritten = _pos;
+ Dispose();
+ return true;
+ }
+ else
+ {
+ charsWritten = 0;
+ Dispose();
+ return false;
+ }
+ }
+
+ public void Insert(int index, char value, int count)
+ {
+ if (_pos > _chars.Length - count)
+ {
+ Grow(count);
+ }
+
+ int remaining = _pos - index;
+ _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
+ _chars.Slice(index, count).Fill(value);
+ _pos += count;
+ }
+
+ public void Insert(int index, string? s)
+ {
+ if (s == null)
+ {
+ return;
+ }
+
+ int count = s.Length;
+
+ if (_pos > _chars.Length - count)
+ {
+ Grow(count);
+ }
+
+ int remaining = _pos - index;
+ _chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
+ s
+#if !NETCOREAPP
+ .AsSpan()
+#endif
+ .CopyTo(_chars.Slice(index));
+ _pos += count;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(char c)
+ {
+ int pos = _pos;
+ Span chars = _chars;
+ if ((uint)pos < (uint)chars.Length)
+ {
+ chars[pos] = c;
+ _pos = pos + 1;
+ }
+ else
+ {
+ GrowAndAppend(c);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(string? s)
+ {
+ if (s == null)
+ {
+ return;
+ }
+
+ int pos = _pos;
+ if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
+ {
+ _chars[pos] = s[0];
+ _pos = pos + 1;
+ }
+ else
+ {
+ AppendSlow(
+ s
+#if !NETCOREAPP
+ .AsSpan()
+#endif
+ );
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Append(int value, string? format = null, IFormatProvider? provider = null)
+ {
+#if NET8_0_OR_GREATER
+ this.AppendSpanFormattable(value, format, provider);
+#else
+ if (format is not null || provider is not null)
+ {
+ Append(value.ToString(format, provider));
+ return;
+ }
+
+ bool isNegative = value < 0;
+ int length = isNegative ? 1 : 0;
+ value = Math.Abs(value);
+
+ // Calculate the length of the integer
+ length += value == 0 ? 1 : (int)Math.Floor(Math.Log10(value) + 1);
+ if (_pos > _chars.Length - length)
+ {
+ Grow(length);
+ }
+
+ if (isNegative)
+ {
+ _chars[_pos] = '-';
+ }
+
+ int index = _pos + length - 1;
+ do
+ {
+ _chars[index--] = (char)('0' + (value % 10));
+ value /= 10;
+ }
+ while (value > 0);
+
+ _pos += length;
+#endif
+ }
+
+ private void AppendSlow(ReadOnlySpan s)
+ {
+ int pos = _pos;
+ if (pos > _chars.Length - s.Length)
+ {
+ Grow(s.Length);
+ }
+
+ s.CopyTo(_chars.Slice(pos));
+ _pos += s.Length;
+ }
+
+ public void Append(char c, int count)
+ {
+ if (_pos > _chars.Length - count)
+ {
+ Grow(count);
+ }
+
+ Span dst = _chars.Slice(_pos, count);
+ for (int i = 0; i < dst.Length; i++)
+ {
+ dst[i] = c;
+ }
+ _pos += count;
+ }
+
+ public void Append(scoped ReadOnlySpan value)
+ {
+ int pos = _pos;
+ if (pos > _chars.Length - value.Length)
+ {
+ Grow(value.Length);
+ }
+
+ value.CopyTo(_chars.Slice(_pos));
+ _pos += value.Length;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span AppendSpan(int length)
+ {
+ int origPos = _pos;
+ if (origPos > _chars.Length - length)
+ {
+ Grow(length);
+ }
+
+ _pos = origPos + length;
+ return _chars.Slice(origPos, length);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void GrowAndAppend(char c)
+ {
+ Grow(1);
+ Append(c);
+ }
+
+ ///
+ /// Resize the internal buffer either by doubling current buffer size or
+ /// by adding to
+ /// whichever is greater.
+ ///
+ ///
+ /// Number of chars requested beyond current position.
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void Grow(int additionalCapacityBeyondPos)
+ {
+ Debug.Assert(additionalCapacityBeyondPos > 0);
+ Debug.Assert(_pos > _chars.Length - additionalCapacityBeyondPos, "Grow called incorrectly, no resize is needed.");
+
+ const uint ArrayMaxLength = 0x7FFFFFC7; // same as Array.MaxLength
+
+ // Increase to at least the required size (_pos + additionalCapacityBeyondPos), but try
+ // to double the size if possible, bounding the doubling to not go beyond the max array length.
+ int newCapacity = (int)Math.Max(
+ (uint)(_pos + additionalCapacityBeyondPos),
+ Math.Min((uint)_chars.Length * 2, ArrayMaxLength));
+
+ // Make sure to let Rent throw an exception if the caller has a bug and the desired capacity is negative.
+ // This could also go negative if the actual required length wraps around.
+ char[] poolArray = ArrayPool.Shared.Rent(newCapacity);
+
+ _chars.Slice(0, _pos).CopyTo(poolArray);
+
+ char[]? toReturn = _arrayToReturnToPool;
+ _chars = _arrayToReturnToPool = poolArray;
+ if (toReturn != null)
+ {
+ ArrayPool.Shared.Return(toReturn);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dispose()
+ {
+ char[]? toReturn = _arrayToReturnToPool;
+ SetToDisposed();
+ if (toReturn != null)
+ {
+ ArrayPool.Shared.Return(toReturn);
+ }
+ }
+
+ ///
+ /// Puts the object into a state preventing accidental continued use an a pool already returned
+ /// to an array, or attempting to retrieve information from the object after it has either
+ /// been disposed, or had its buffer returned to the pool as a result of calling
+ /// .
+ ///
+ private void SetToDisposed()
+ {
+ this = default(ValueStringBuilder) with { _pos = -1 };
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj b/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj
new file mode 100644
index 000000000..edc5936e5
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj
@@ -0,0 +1,159 @@
+
+
+
+
+ netstandard2.0
+ 12.0
+ enable
+ true
+ true
+
+
+
+ true
+ $(NoWarn);nullable;NU5128;RS2008;SA0001
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Corvus.Json.JsonReference\%(RecursiveDir)\%(Filename)%(Extension)
+
+
+ Corvus.Json.CodeGeneration\%(RecursiveDir)\%(Filename)%(Extension)
+
+
+ Corvus.Json.CodeGeneration.CSharp\%(RecursiveDir)\%(Filename)%(Extension)
+
+
+ Corvus.Json.CodeGeneration.201909\%(RecursiveDir)\%(Filename)%(Extension)
+
+
+ Corvus.Json.CodeGeneration.202012\%(RecursiveDir)\%(Filename)%(Extension)
+
+
+ Corvus.Json.CodeGeneration.4\%(RecursiveDir)\%(Filename)%(Extension)
+
+
+ Corvus.Json.CodeGeneration.6\%(RecursiveDir)\%(Filename)%(Extension)
+
+
+ Corvus.Json.CodeGeneration.7\%(RecursiveDir)\%(Filename)%(Extension)
+
+
+ Corvus.Json.CodeGeneration.CorvusVocabulary\%(RecursiveDir)\%(Filename)%(Extension)
+
+
+ Corvus.Json.CodeGeneration.OpenApi30\%(RecursiveDir)\%(Filename)%(Extension)
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(GetTargetPathDependsOn);GetDependencyTargetPaths
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Apache-2.0
+ A source generator that will emit dotnet types based on JSON Schema.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.props b/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.props
new file mode 100644
index 000000000..5afe47256
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.props
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Solutions/Corvus.Json.SourceGenerator/IncrementalSourceGenerator.cs b/Solutions/Corvus.Json.SourceGenerator/IncrementalSourceGenerator.cs
new file mode 100644
index 000000000..7e5093a97
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/IncrementalSourceGenerator.cs
@@ -0,0 +1,374 @@
+//
+// Copyright (c) Endjin Limited. All rights reserved.
+//
+
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Text;
+using Corvus.Json.CodeGeneration;
+using Corvus.Json.CodeGeneration.CSharp;
+using Corvus.Json.CodeGeneration.DocumentResolvers;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Text;
+
+namespace Corvus.Json.SourceGenerator;
+
+///
+/// Base for a source generator.
+///
+[Generator(LanguageNames.CSharp)]
+public class IncrementalSourceGenerator : IIncrementalGenerator
+{
+ private static readonly ImmutableArray DefaultDisabledNamingHeuristics = ["DocumentationNameHeuristic"];
+ private static readonly PrepopulatedDocumentResolver MetaSchemaResolver = CreateMetaSchemaResolver();
+ private static readonly VocabularyRegistry VocabularyRegistry = RegisterVocabularies(MetaSchemaResolver);
+ private static readonly DiagnosticDescriptor Crv1001ErrorGeneratingCSharpCode =
+ new(
+ id: "CRV1001",
+ title: "JSON Schema Type Generator Error",
+ messageFormat: $"Error generating C# code: {{0}}",
+ category: "JsonSchemaCodeGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ private static readonly DiagnosticDescriptor Crv1000ErrorAddingTypeDeclarations =
+ new(
+ id: "CRV1000",
+ title: "JSON Schema Type Generator Error",
+ messageFormat: $"Error adding type declarations for path '{{0}}': {{1}}",
+ category: "JsonSchemaCodeGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ ///
+ public void Initialize(IncrementalGeneratorInitializationContext initializationContext)
+ {
+ EmitGeneratorAttribute(initializationContext);
+
+ IncrementalValueProvider globalOptions = initializationContext.AnalyzerConfigOptionsProvider.Select(GetGlobalOptions);
+
+ IncrementalValuesProvider jsonSourceFiles = initializationContext.AdditionalTextsProvider.Where(p => p.Path.EndsWith(".json"));
+
+ IncrementalValueProvider documentResolver = jsonSourceFiles.Collect().Select(BuildDocumentResolver);
+
+ IncrementalValueProvider generationContext = documentResolver.Combine(globalOptions).Select((r, c) => new GenerationContext(r.Left, r.Right));
+
+ IncrementalValuesProvider generationSpecifications =
+ initializationContext.SyntaxProvider.ForAttributeWithMetadataName(
+ "Corvus.Json.JsonSchemaTypeGeneratorAttribute",
+ IsValidAttributeTarget,
+ BuildGenerationSpecifications);
+
+ IncrementalValueProvider typesToGenerate = generationSpecifications.Collect().Combine(generationContext).Select((c, t) => new TypesToGenerate(c.Left, c.Right));
+
+ initializationContext.RegisterSourceOutput(typesToGenerate, GenerateCode);
+ }
+
+ private static void GenerateCode(SourceProductionContext context, TypesToGenerate generationSource)
+ {
+ if (generationSource.GenerationSpecifications.Length == 0)
+ {
+ // Nothing to generate
+ return;
+ }
+
+ List typesToGenerate = [];
+ List namedTypes = [];
+ JsonSchemaTypeBuilder typeBuilder = new(generationSource.DocumentResolver, VocabularyRegistry);
+
+ foreach (GenerationSpecification spec in generationSource.GenerationSpecifications)
+ {
+ if (context.CancellationToken.IsCancellationRequested)
+ {
+ return;
+ }
+
+ string schemaFile = spec.Location;
+ JsonReference reference = new(schemaFile);
+ TypeDeclaration rootType;
+ try
+ {
+ rootType = typeBuilder.AddTypeDeclarations(reference, generationSource.FallbackVocabulary, spec.RebaseToRootPath, context.CancellationToken);
+ }
+ catch (Exception ex)
+ {
+ context.ReportDiagnostic(
+ Diagnostic.Create(
+ Crv1000ErrorAddingTypeDeclarations,
+ Location.None,
+ reference,
+ ex.Message));
+
+ return;
+ }
+
+ typesToGenerate.Add(rootType);
+
+ namedTypes.Add(
+ new CSharpLanguageProvider.NamedType(
+ rootType.ReducedTypeDeclaration().ReducedType.LocatedSchema.Location,
+ spec.TypeName,
+ spec.Namespace));
+ }
+
+ CSharpLanguageProvider.Options options = new(
+ "GeneratedTypes",
+ namedTypes.ToArray(),
+ disabledNamingHeuristics: generationSource.DisabledNamingHeuristics.ToArray(),
+ optionalAsNullable: generationSource.OptionalAsNullable,
+ useOptionalNameHeuristics: generationSource.UseOptionalNameHeuristics,
+ alwaysAssertFormat: generationSource.AlwaysAssertFormat,
+ fileExtension: ".g.cs");
+
+ var languageProvider = CSharpLanguageProvider.DefaultWithOptions(options);
+
+ IReadOnlyCollection generatedCode;
+
+ try
+ {
+ generatedCode =
+ typeBuilder.GenerateCodeUsing(
+ languageProvider,
+ context.CancellationToken,
+ typesToGenerate);
+ }
+ catch (Exception ex)
+ {
+ context.ReportDiagnostic(
+ Diagnostic.Create(
+ Crv1001ErrorGeneratingCSharpCode,
+ Location.None,
+ ex.Message));
+
+ return;
+ }
+
+ foreach (GeneratedCodeFile codeFile in generatedCode)
+ {
+ if (!context.CancellationToken.IsCancellationRequested)
+ {
+ context.AddSource(codeFile.FileName, SourceText.From(codeFile.FileContent, Encoding.UTF8));
+ }
+ }
+ }
+
+ private static GenerationSpecification BuildGenerationSpecifications(GeneratorAttributeSyntaxContext context, CancellationToken token)
+ {
+ AttributeData attribute = context.Attributes[0];
+ string location = attribute.ConstructorArguments[0].Value as string ?? throw new InvalidOperationException("Location is required");
+
+ if (SchemaReferenceNormalization.TryNormalizeSchemaReference(location, Path.GetDirectoryName(context.TargetNode.SyntaxTree.FilePath), out string? normalizedLocation))
+ {
+ location = normalizedLocation;
+ }
+
+ bool rebaseToRootPath = attribute.ConstructorArguments[0].Value as bool? ?? false;
+
+ return new(context.TargetSymbol.Name, context.TargetSymbol.ContainingNamespace.ToDisplayString(), location, rebaseToRootPath);
+ }
+
+ private static GlobalOptions GetGlobalOptions(AnalyzerConfigOptionsProvider source, CancellationToken token)
+ {
+ IVocabulary fallbackVocabulary = CodeGeneration.Draft202012.VocabularyAnalyser.DefaultVocabulary;
+ if (source.GlobalOptions.TryGetValue("build_property.CorvusJsonSchemaFallbackVocabulary", out string? fallbackVocabularyName))
+ {
+ fallbackVocabulary = fallbackVocabularyName switch
+ {
+ "Draft202012" => CodeGeneration.Draft202012.VocabularyAnalyser.DefaultVocabulary,
+ "Draft201909" => CodeGeneration.Draft201909.VocabularyAnalyser.DefaultVocabulary,
+ "Draft7" => CodeGeneration.Draft7.VocabularyAnalyser.DefaultVocabulary,
+ "Draft6" => CodeGeneration.Draft6.VocabularyAnalyser.DefaultVocabulary,
+ "Draft4" => CodeGeneration.Draft4.VocabularyAnalyser.DefaultVocabulary,
+ "OpenApi30" => CodeGeneration.OpenApi30.VocabularyAnalyser.DefaultVocabulary,
+ _ => CodeGeneration.Draft202012.VocabularyAnalyser.DefaultVocabulary,
+ };
+ }
+
+ bool optionalAsNullable = true;
+
+ if (source.GlobalOptions.TryGetValue("build_property.CorvusJsonSchemaOptionalAsNullable", out string? optionalAsNullableName))
+ {
+ optionalAsNullable = optionalAsNullableName == "NullOrUndefined";
+ }
+
+ bool useOptionalNameHeuristics = true;
+
+ if (source.GlobalOptions.TryGetValue("build_property.CorvusJsonSchemaUseOptionalNameHeuristics", out string? useOptionalNameHeuristicsName))
+ {
+ useOptionalNameHeuristics = useOptionalNameHeuristicsName == "true" || useOptionalNameHeuristicsName == "True";
+ }
+
+ bool alwaysAssertFormat = true;
+
+ if (source.GlobalOptions.TryGetValue("build_property.CorvusJsonSchemaAlwaysAssertFormat", out string? alwaysAssertFormatName))
+ {
+ alwaysAssertFormat = alwaysAssertFormatName == "true" || alwaysAssertFormatName == "True";
+ }
+
+ ImmutableArray? disabledNamingHeuristics = null;
+
+ if (source.GlobalOptions.TryGetValue("build_property.CorvusJsonSchemaDisabledNamingHeuristics", out string? disabledNamingHeuristicsSemicolonSeparated))
+ {
+ string[] disabledNames = disabledNamingHeuristicsSemicolonSeparated.Split([';'], StringSplitOptions.RemoveEmptyEntries);
+
+ disabledNamingHeuristics = disabledNames.Select(d => d.Trim()).ToImmutableArray();
+ }
+
+ return new(fallbackVocabulary, optionalAsNullable, useOptionalNameHeuristics, alwaysAssertFormat, disabledNamingHeuristics ?? DefaultDisabledNamingHeuristics);
+ }
+
+ private static CompoundDocumentResolver BuildDocumentResolver(ImmutableArray source, CancellationToken token)
+ {
+ PrepopulatedDocumentResolver newResolver = new();
+ foreach (AdditionalText additionalText in source)
+ {
+ if (token.IsCancellationRequested)
+ {
+ return new CompoundDocumentResolver();
+ }
+
+ string? json = additionalText.GetText(token)?.ToString();
+ if (json is string j)
+ {
+ var doc = JsonDocument.Parse(j);
+ if (SchemaReferenceNormalization.TryNormalizeSchemaReference(additionalText.Path, string.Empty, out string? normalizedReference))
+ {
+ newResolver.AddDocument(normalizedReference, doc);
+ }
+ }
+ }
+
+ return new CompoundDocumentResolver(newResolver, MetaSchemaResolver);
+ }
+
+ private static PrepopulatedDocumentResolver CreateMetaSchemaResolver()
+ {
+ PrepopulatedDocumentResolver metaSchemaResolver = new();
+ metaSchemaResolver.AddMetaschema();
+ return metaSchemaResolver;
+ }
+
+ private static VocabularyRegistry RegisterVocabularies(IDocumentResolver documentResolver)
+ {
+ VocabularyRegistry vocabularyRegistry = new();
+
+ // Add support for the vocabularies we are interested in.
+ CodeGeneration.Draft202012.VocabularyAnalyser.RegisterAnalyser(documentResolver, vocabularyRegistry);
+ CodeGeneration.Draft201909.VocabularyAnalyser.RegisterAnalyser(documentResolver, vocabularyRegistry);
+ CodeGeneration.Draft7.VocabularyAnalyser.RegisterAnalyser(vocabularyRegistry);
+ CodeGeneration.Draft6.VocabularyAnalyser.RegisterAnalyser(vocabularyRegistry);
+ CodeGeneration.Draft4.VocabularyAnalyser.RegisterAnalyser(vocabularyRegistry);
+ CodeGeneration.OpenApi30.VocabularyAnalyser.RegisterAnalyser(vocabularyRegistry);
+
+ // And register the custom vocabulary for Corvus extensions.
+ vocabularyRegistry.RegisterVocabularies(
+ CodeGeneration.CorvusVocabulary.SchemaVocabulary.DefaultInstance);
+
+ return vocabularyRegistry;
+ }
+
+ private static void EmitGeneratorAttribute(IncrementalGeneratorInitializationContext initializationContext)
+ {
+ initializationContext.RegisterPostInitializationOutput(static postInitializationContext =>
+ {
+ postInitializationContext.AddSource(
+ "JsonSchemaTypeGeneratorAttribute.g.cs",
+ SourceText.From(
+ """
+ using System;
+
+ namespace Corvus.Json;
+
+ [AttributeUsage(AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
+ internal sealed class JsonSchemaTypeGeneratorAttribute : Attribute
+ {
+ public JsonSchemaTypeGeneratorAttribute(string location, bool rebaseToRootPath = false)
+ {
+ this.Location = location;
+ this.RebaseToRootPath = rebaseToRootPath;
+ }
+
+ ///
+ /// Gets the location for the JSON schema.
+ ///
+ public string Location { get; }
+
+ ///
+ /// Gets a value indicating whether to rebase to the root path.
+ ///
+ public bool RebaseToRootPath { get; }
+ }
+ """,
+ Encoding.UTF8));
+ });
+ }
+
+ private static bool IsValidAttributeTarget(SyntaxNode node, CancellationToken token)
+ {
+ return
+ node is StructDeclarationSyntax structDeclarationSyntax &&
+ structDeclarationSyntax
+ .Modifiers
+ .Any(m => m.IsKind(SyntaxKind.PartialKeyword)) &&
+ structDeclarationSyntax.Parent is (FileScopedNamespaceDeclarationSyntax or NamespaceDeclarationSyntax);
+ }
+
+ private readonly struct GenerationSpecification(string typeName, string ns, string location, bool rebaseToRootPath)
+ {
+ public string TypeName { get; } = typeName;
+
+ public string Namespace { get; } = ns;
+
+ public string Location { get; } = location;
+
+ public bool RebaseToRootPath { get; } = rebaseToRootPath;
+ }
+
+ private readonly struct GenerationContext(CompoundDocumentResolver left, GlobalOptions right)
+ {
+ public CompoundDocumentResolver DocumentResolver { get; } = left;
+
+ public IVocabulary FallbackVocabulary { get; } = right.FallbackVocabulary;
+
+ public bool OptionalAsNullable { get; } = right.OptionalAsNullable;
+
+ public bool UseOptionalNameHeuristics { get; } = right.UseOptionalNameHeuristics;
+
+ public ImmutableArray DisabledNamingHeuristics { get; } = right.DisabledNamingHeuristics;
+
+ public bool AlwaysAssertFormat { get; } = right.AlwaysAssertFormat;
+ }
+
+ private readonly struct TypesToGenerate(ImmutableArray left, GenerationContext right)
+ {
+ public ImmutableArray GenerationSpecifications { get; } = left;
+
+ public CompoundDocumentResolver DocumentResolver { get; } = right.DocumentResolver;
+
+ public IVocabulary FallbackVocabulary { get; } = right.FallbackVocabulary;
+
+ public bool OptionalAsNullable { get; } = right.OptionalAsNullable;
+
+ public bool UseOptionalNameHeuristics { get; } = right.UseOptionalNameHeuristics;
+
+ public ImmutableArray DisabledNamingHeuristics { get; } = right.DisabledNamingHeuristics;
+
+ public bool AlwaysAssertFormat { get; } = right.AlwaysAssertFormat;
+ }
+
+ private readonly struct GlobalOptions(IVocabulary fallbackVocabulary, bool optionalAsNullable, bool useOptionalNameHeuristics, bool alwaysAssertFormat, ImmutableArray disabledNamingHeuristics)
+ {
+ public IVocabulary FallbackVocabulary { get; } = fallbackVocabulary;
+
+ public bool OptionalAsNullable { get; } = optionalAsNullable;
+
+ public bool UseOptionalNameHeuristics { get; } = useOptionalNameHeuristics;
+
+ public ImmutableArray DisabledNamingHeuristics { get; } = disabledNamingHeuristics;
+
+ public bool AlwaysAssertFormat { get; } = alwaysAssertFormat;
+ }
+}
\ No newline at end of file
diff --git a/Solutions/Corvus.Json.SourceGenerator/IndexRange/Index.cs b/Solutions/Corvus.Json.SourceGenerator/IndexRange/Index.cs
new file mode 100644
index 000000000..27660bd61
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/IndexRange/Index.cs
@@ -0,0 +1,157 @@
+//
+// Copyright (c) Endjin Limited. All rights reserved.
+//
+
+#pragma warning disable
+
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if NETSTANDARD2_1
+[assembly: TypeForwardedTo(typeof(System.Index))]
+#else
+using System.Runtime.CompilerServices;
+
+namespace System;
+
+/// Represent a type can be used to index a collection either from the start or the end.
+///
+/// Index is used by the C# compiler to support the new index syntax
+///
+/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ;
+/// int lastElement = someArray[^1]; // lastElement = 5
+///
+///
+public readonly struct Index : IEquatable
+{
+ private readonly int _value;
+
+ /// Construct an Index using a value and indicating if the index is from the start or from the end.
+ /// The index value. it has to be zero or positive number.
+ /// Indicating if the index is from the start or from the end.
+ ///
+ /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element.
+ ///
+#if !NET35
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+ public Index(int value, bool fromEnd = false)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ if (fromEnd)
+ _value = ~value;
+ else
+ _value = value;
+ }
+
+ // The following private constructors mainly created for perf reason to avoid the checks
+ private Index(int value)
+ {
+ _value = value;
+ }
+
+ /// Create an Index pointing at first element.
+ public static Index Start => new Index(0);
+
+ /// Create an Index pointing at beyond last element.
+ public static Index End => new Index(~0);
+
+ /// Create an Index from the start at the position indicated by the value.
+ /// The index value from the start.
+#if !NET35
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+ public static Index FromStart(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ return new Index(value);
+ }
+
+ /// Create an Index from the end at the position indicated by the value.
+ /// The index value from the end.
+#if !NET35
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+ public static Index FromEnd(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ return new Index(~value);
+ }
+
+ /// Returns the index value.
+ public int Value
+ {
+ get
+ {
+ if (_value < 0)
+ return ~_value;
+ else
+ return _value;
+ }
+ }
+
+ /// Indicates whether the index is from the start or the end.
+ public bool IsFromEnd => _value < 0;
+
+ /// Calculate the offset from the start using the giving collection length.
+ /// The length of the collection that the Index will be used with. length has to be a positive value
+ ///
+ /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values.
+ /// we don't validate either the returned offset is greater than the input length.
+ /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and
+ /// then used to index a collection will get out of range exception which will be same affect as the validation.
+ ///
+#if !NET35
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+ public int GetOffset(int length)
+ {
+ int offset = _value;
+ if (IsFromEnd)
+ {
+ // offset = length - (~value)
+ // offset = length + (~(~value) + 1)
+ // offset = length + value + 1
+
+ offset += length + 1;
+ }
+ return offset;
+ }
+
+ /// Indicates whether the current Index object is equal to another object of the same type.
+ /// An object to compare with this object
+ public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value;
+
+ /// Indicates whether the current Index object is equal to another Index object.
+ /// An object to compare with this object
+ public bool Equals(Index other) => _value == other._value;
+
+ /// Returns the hash code for this instance.
+ public override int GetHashCode() => _value;
+
+ /// Converts integer number to an Index.
+ public static implicit operator Index(int value) => FromStart(value);
+
+ /// Converts the value of the current Index object to its equivalent string representation.
+ public override string ToString()
+ {
+ if (IsFromEnd)
+ return "^" + ((uint)Value).ToString();
+
+ return ((uint)Value).ToString();
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Solutions/Corvus.Json.SourceGenerator/IndexRange/Range.cs b/Solutions/Corvus.Json.SourceGenerator/IndexRange/Range.cs
new file mode 100644
index 000000000..3f9638ea2
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/IndexRange/Range.cs
@@ -0,0 +1,100 @@
+//
+// Copyright (c) Endjin Limited. All rights reserved.
+//
+
+#pragma warning disable
+
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if NETSTANDARD2_1
+[assembly: TypeForwardedTo(typeof(System.Range))]
+#else
+using System.Runtime.CompilerServices;
+
+namespace System;
+
+/// Represent a range has start and end indexes.
+///
+/// Range is used by the C# compiler to support the range syntax.
+///
+/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 };
+/// int[] subArray1 = someArray[0..2]; // { 1, 2 }
+/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 }
+///
+///
+public readonly struct Range : IEquatable
+{
+ /// Represent the inclusive start index of the Range.
+ public Index Start { get; }
+
+ /// Represent the exclusive end index of the Range.
+ public Index End { get; }
+
+ /// Construct a Range object using the start and end indexes.
+ /// Represent the inclusive start index of the range.
+ /// Represent the exclusive end index of the range.
+ public Range(Index start, Index end)
+ {
+ Start = start;
+ End = end;
+ }
+
+ /// Indicates whether the current Range object is equal to another object of the same type.
+ /// An object to compare with this object
+ public override bool Equals(object? value) =>
+ value is Range r &&
+ r.Start.Equals(Start) &&
+ r.End.Equals(End);
+
+ /// Indicates whether the current Range object is equal to another Range object.
+ /// An object to compare with this object
+ public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End);
+
+ /// Returns the hash code for this instance.
+ public override int GetHashCode()
+ {
+ return Start.GetHashCode() * 31 + End.GetHashCode();
+ }
+
+ /// Converts the value of the current Range object to its equivalent string representation.
+ public override string ToString()
+ {
+ return Start + ".." + End;
+ }
+
+ /// Create a Range object starting from start index to the end of the collection.
+ public static Range StartAt(Index start) => new Range(start, Index.End);
+
+ /// Create a Range object starting from first element in the collection to the end Index.
+ public static Range EndAt(Index end) => new Range(Index.Start, end);
+
+ /// Create a Range object starting from first element to the end.
+ public static Range All => new Range(Index.Start, Index.End);
+
+ /// Calculate the start offset and length of range object using a collection length.
+ /// The length of the collection that the range will be used with. length has to be a positive value.
+ ///
+ /// For performance reason, we don't validate the input length parameter against negative values.
+ /// It is expected Range will be used with collections which always have non negative length/count.
+ /// We validate the range is inside the length scope though.
+ ///
+#if !NET35
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#endif
+ [CLSCompliant(false)]
+ public (int Offset, int Length) GetOffsetAndLength(int length)
+ {
+ int start = Start.GetOffset(length);
+ int end = End.GetOffset(length);
+
+ if ((uint)end > (uint)length || (uint)start > (uint)end)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+
+ return (start, end - start);
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Solutions/Corvus.Json.SourceGenerator/Metaschema.cs b/Solutions/Corvus.Json.SourceGenerator/Metaschema.cs
new file mode 100644
index 000000000..69cf1938e
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/Metaschema.cs
@@ -0,0 +1,114 @@
+//
+// Copyright (c) Endjin Limited. All rights reserved.
+//
+
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Text.Json;
+
+namespace Corvus.Json.SourceGenerator;
+
+///
+/// Apply the metaschema to a document resolver.
+///
+internal static class Metaschema
+{
+ ///
+ /// Adds metaschema to a document resolver.
+ ///
+ /// The document resolver to which to add the metaschema.
+ /// A reference to the document resolver once the operation has completed.
+ internal static IDocumentResolver AddMetaschema(this IDocumentResolver documentResolver)
+ {
+ var assembly = Assembly.GetAssembly(typeof(Metaschema));
+
+ Debug.Assert(assembly is not null, "The assembly containing this type must exist");
+
+ documentResolver.AddDocument(
+ "http://json_schema.org/draft_04/schema",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft4.schema.json")));
+
+ documentResolver.AddDocument(
+ "http://json_schema.org/draft_06/schema",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft6.schema.json")));
+
+ documentResolver.AddDocument(
+ "http://json_schema.org/draft_07/schema",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft7.schema.json")));
+
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2019_09/schema",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2019_09.schema.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2019_09/meta/applicator",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2019_09.meta.applicator.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2019_09/meta/content",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2019_09.meta.content.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2019_09/meta/core",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2019_09.meta.core.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2019_09/meta/format",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2019_09.meta.format.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2019_09/meta/hyper_schema",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2019_09.meta.hyper-schema.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2019_09/meta/meta_data",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2019_09.meta.meta-data.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2019_09/meta/validation",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2019_09.meta.validation.json")));
+
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2020_12/schema",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2020_12.schema.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2020_12/meta/applicator",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2020_12.meta.applicator.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2020_12/meta/content",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2020_12.meta.content.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2020_12/meta/core",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2020_12.meta.core.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2020_12/meta/format_annotation",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2020_12.meta.format-annotation.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2020_12/meta/format_assertion",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2020_12.meta.format-assertion.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2020_12/meta/hyper_schema",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2020_12.meta.hyper-schema.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2020_12/meta/meta_data",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2020_12.meta.meta-data.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2020_12/meta/unevaluated",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2020_12.meta.unevaluated.json")));
+ documentResolver.AddDocument(
+ "https://json_schema.org/draft/2020_12/meta/validation",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.draft2020_12.meta.validation.json")));
+
+ documentResolver.AddDocument(
+ "https://corvus_oss.org/json_schema/2020_12/schema",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.corvus.schema.json")));
+ documentResolver.AddDocument(
+ "https://corvus_oss.org/json_schema/2020_12/meta/corvus_extensions",
+ JsonDocument.Parse(ReadResource(assembly, "metaschema.corvus.meta.corvus-extensions.json")));
+
+ return documentResolver;
+ }
+
+ private static string ReadResource(Assembly assembly, string path)
+ {
+ string name = path;
+ using Stream? resourceStream = assembly.GetManifestResourceStream(name);
+ Debug.Assert(resourceStream is not null, $"The manifest resource stream {name} does not exist.");
+ using var reader = new StreamReader(resourceStream);
+ return reader.ReadToEnd();
+ }
+}
\ No newline at end of file
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/corvus/meta/corvus-extensions.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/corvus/meta/corvus-extensions.json
new file mode 100644
index 000000000..12eef7d72
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/corvus/meta/corvus-extensions.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://corvus-oss.org/json-schema/2020-12/meta/corvus-extensions",
+ "$dynamicAnchor": "meta",
+
+ "title": "Corvus vocabulary meta-schema for corvus extension keywords",
+ "type": [ "object", "boolean" ],
+ "properties": {
+ "$corvusTypeName": { "type": "string", "description": "The explicit type name for a schema during code generation." }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/corvus/schema.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/corvus/schema.json
new file mode 100644
index 000000000..52a6c2cf7
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/corvus/schema.json
@@ -0,0 +1,60 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://corvus-oss.org/json-schema/2020-12/schema",
+ "$vocabulary": {
+ "https://json-schema.org/draft/2020-12/vocab/core": true,
+ "https://json-schema.org/draft/2020-12/vocab/applicator": true,
+ "https://json-schema.org/draft/2020-12/vocab/unevaluated": true,
+ "https://json-schema.org/draft/2020-12/vocab/validation": true,
+ "https://json-schema.org/draft/2020-12/vocab/meta-data": true,
+ "https://json-schema.org/draft/2020-12/vocab/format-annotation": true,
+ "https://json-schema.org/draft/2020-12/vocab/content": true,
+ "https://corvus-oss.org/json-schema/2020-12/vocab/corvus-extensions": false
+ },
+ "$dynamicAnchor": "meta",
+
+ "title": "Core and Validation specifications meta-schema",
+ "allOf": [
+ { "$ref": "https://json-schema.org/draft/2020-12/meta/core" },
+ { "$ref": "https://json-schema.org/draft/2020-12/meta/applicator" },
+ { "$ref": "https://json-schema.org/draft/2020-12/meta/unevaluated" },
+ { "$ref": "https://json-schema.org/draft/2020-12/meta/validation" },
+ { "$ref": "https://json-schema.org/draft/2020-12/meta/meta-data" },
+ { "$ref": "https://json-schema.org/draft/2020-12/meta/format-annotation" },
+ { "$ref": "https://json-schema.org/draft/2020-12/meta/content" },
+ { "$ref": "https://corvus-oss.org/json-schema/2020-12/meta/corvus-extensions" }
+ ],
+ "type": [ "object", "boolean" ],
+ "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.",
+ "properties": {
+ "definitions": {
+ "$comment": "\"definitions\" has been replaced by \"$defs\".",
+ "type": "object",
+ "additionalProperties": { "$dynamicRef": "#meta" },
+ "deprecated": true,
+ "default": {}
+ },
+ "dependencies": {
+ "$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.",
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$dynamicRef": "#meta" },
+ { "$ref": "meta/validation#/$defs/stringArray" }
+ ]
+ },
+ "deprecated": true,
+ "default": {}
+ },
+ "$recursiveAnchor": {
+ "$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".",
+ "$ref": "meta/core#/$defs/anchorString",
+ "deprecated": true
+ },
+ "$recursiveRef": {
+ "$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".",
+ "$ref": "meta/core#/$defs/uriReferenceString",
+ "deprecated": true
+ }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/applicator.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/applicator.json
new file mode 100644
index 000000000..80a04ca13
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/applicator.json
@@ -0,0 +1,53 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/applicator",
+ "$recursiveAnchor": true,
+
+ "title": "Applicator vocabulary meta-schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "additionalItems": { "$recursiveRef": "#" },
+ "unevaluatedItems": { "$recursiveRef": "#" },
+ "items": {
+ "anyOf": [
+ { "$recursiveRef": "#" },
+ { "$ref": "#/$defs/schemaArray" }
+ ]
+ },
+ "contains": { "$recursiveRef": "#" },
+ "additionalProperties": { "$recursiveRef": "#" },
+ "unevaluatedProperties": { "$recursiveRef": "#" },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$recursiveRef": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$recursiveRef": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependentSchemas": {
+ "type": "object",
+ "additionalProperties": {
+ "$recursiveRef": "#"
+ }
+ },
+ "propertyNames": { "$recursiveRef": "#" },
+ "if": { "$recursiveRef": "#" },
+ "then": { "$recursiveRef": "#" },
+ "else": { "$recursiveRef": "#" },
+ "allOf": { "$ref": "#/$defs/schemaArray" },
+ "anyOf": { "$ref": "#/$defs/schemaArray" },
+ "oneOf": { "$ref": "#/$defs/schemaArray" },
+ "not": { "$recursiveRef": "#" }
+ },
+ "$defs": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$recursiveRef": "#" }
+ }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/content.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/content.json
new file mode 100644
index 000000000..7a9af9754
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/content.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/content",
+ "$recursiveAnchor": true,
+
+ "title": "Content vocabulary meta-schema",
+
+ "type": ["object", "boolean"],
+ "properties": {
+ "contentMediaType": { "type": "string" },
+ "contentEncoding": { "type": "string" },
+ "contentSchema": { "$recursiveRef": "#" }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/core.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/core.json
new file mode 100644
index 000000000..2cf1fedd3
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/core.json
@@ -0,0 +1,54 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/core",
+ "$recursiveAnchor": true,
+
+ "title": "Core vocabulary meta-schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference",
+ "$comment": "Non-empty fragments not allowed.",
+ "pattern": "^[^#]*#?$"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$anchor": {
+ "type": "string",
+ "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$recursiveRef": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$recursiveAnchor": {
+ "type": "boolean",
+ "default": false
+ },
+ "$vocabulary": {
+ "type": "object",
+ "propertyNames": {
+ "type": "string",
+ "format": "uri"
+ },
+ "additionalProperties": {
+ "type": "boolean"
+ }
+ },
+ "$comment": {
+ "type": "string"
+ },
+ "$defs": {
+ "type": "object",
+ "additionalProperties": { "$recursiveRef": "#" },
+ "default": {}
+ }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/format.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/format.json
new file mode 100644
index 000000000..87177f351
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/format.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/format",
+ "$recursiveAnchor": true,
+
+ "title": "Format vocabulary meta-schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "format": { "type": "string" }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/hyper-schema.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/hyper-schema.json
new file mode 100644
index 000000000..cd81bd875
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/hyper-schema.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/hyper-schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/hyper-schema",
+ "$recursiveAnchor": true,
+
+ "title": "JSON Hyper-Schema Vocabulary Schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "base": {
+ "type": "string",
+ "format": "uri-template"
+ },
+ "links": {
+ "type": "array",
+ "items": {
+ "$ref": "https://json-schema.org/draft/2019-09/links"
+ }
+ }
+ },
+ "links": [
+ {
+ "rel": "self",
+ "href": "{+%24id}"
+ }
+ ]
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/meta-data.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/meta-data.json
new file mode 100644
index 000000000..28d3e8ab1
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/meta-data.json
@@ -0,0 +1,34 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/meta-data",
+ "$recursiveAnchor": true,
+
+ "title": "Meta-data vocabulary meta-schema",
+
+ "type": ["object", "boolean"],
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": true,
+ "deprecated": {
+ "type": "boolean",
+ "default": false
+ },
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "writeOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "examples": {
+ "type": "array",
+ "items": true
+ }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/validation.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/validation.json
new file mode 100644
index 000000000..5356df926
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/validation.json
@@ -0,0 +1,95 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/meta/validation",
+ "$recursiveAnchor": true,
+
+ "title": "Validation vocabulary meta-schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/$defs/nonNegativeInteger" },
+ "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "maxItems": { "$ref": "#/$defs/nonNegativeInteger" },
+ "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "maxContains": { "$ref": "#/$defs/nonNegativeInteger" },
+ "minContains": {
+ "$ref": "#/$defs/nonNegativeInteger",
+ "default": 1
+ },
+ "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/$defs/stringArray" },
+ "dependentRequired": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/stringArray"
+ }
+ },
+ "const": true,
+ "enum": {
+ "type": "array",
+ "items": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/$defs/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/$defs/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ }
+ },
+ "$defs": {
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "$ref": "#/$defs/nonNegativeInteger",
+ "default": 0
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/schema.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/schema.json
new file mode 100644
index 000000000..2248a0c80
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/schema.json
@@ -0,0 +1,42 @@
+{
+ "$schema": "https://json-schema.org/draft/2019-09/schema",
+ "$id": "https://json-schema.org/draft/2019-09/schema",
+ "$vocabulary": {
+ "https://json-schema.org/draft/2019-09/vocab/core": true,
+ "https://json-schema.org/draft/2019-09/vocab/applicator": true,
+ "https://json-schema.org/draft/2019-09/vocab/validation": true,
+ "https://json-schema.org/draft/2019-09/vocab/meta-data": true,
+ "https://json-schema.org/draft/2019-09/vocab/format": false,
+ "https://json-schema.org/draft/2019-09/vocab/content": true
+ },
+ "$recursiveAnchor": true,
+
+ "title": "Core and Validation specifications meta-schema",
+ "allOf": [
+ {"$ref": "meta/core"},
+ {"$ref": "meta/applicator"},
+ {"$ref": "meta/validation"},
+ {"$ref": "meta/meta-data"},
+ {"$ref": "meta/format"},
+ {"$ref": "meta/content"}
+ ],
+ "type": ["object", "boolean"],
+ "properties": {
+ "definitions": {
+ "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.",
+ "type": "object",
+ "additionalProperties": { "$recursiveRef": "#" },
+ "default": {}
+ },
+ "dependencies": {
+ "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"",
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$recursiveRef": "#" },
+ { "$ref": "meta/validation#/$defs/stringArray" }
+ ]
+ }
+ }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/applicator.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/applicator.json
new file mode 100644
index 000000000..f4775974a
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/applicator.json
@@ -0,0 +1,45 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://json-schema.org/draft/2020-12/meta/applicator",
+ "$dynamicAnchor": "meta",
+
+ "title": "Applicator vocabulary meta-schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "prefixItems": { "$ref": "#/$defs/schemaArray" },
+ "items": { "$dynamicRef": "#meta" },
+ "contains": { "$dynamicRef": "#meta" },
+ "additionalProperties": { "$dynamicRef": "#meta" },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$dynamicRef": "#meta" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$dynamicRef": "#meta" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependentSchemas": {
+ "type": "object",
+ "additionalProperties": { "$dynamicRef": "#meta" },
+ "default": {}
+ },
+ "propertyNames": { "$dynamicRef": "#meta" },
+ "if": { "$dynamicRef": "#meta" },
+ "then": { "$dynamicRef": "#meta" },
+ "else": { "$dynamicRef": "#meta" },
+ "allOf": { "$ref": "#/$defs/schemaArray" },
+ "anyOf": { "$ref": "#/$defs/schemaArray" },
+ "oneOf": { "$ref": "#/$defs/schemaArray" },
+ "not": { "$dynamicRef": "#meta" }
+ },
+ "$defs": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$dynamicRef": "#meta" }
+ }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/content.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/content.json
new file mode 100644
index 000000000..76e3760d2
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/content.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://json-schema.org/draft/2020-12/meta/content",
+ "$dynamicAnchor": "meta",
+
+ "title": "Content vocabulary meta-schema",
+
+ "type": ["object", "boolean"],
+ "properties": {
+ "contentEncoding": { "type": "string" },
+ "contentMediaType": { "type": "string" },
+ "contentSchema": { "$dynamicRef": "#meta" }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/core.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/core.json
new file mode 100644
index 000000000..691862289
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/core.json
@@ -0,0 +1,48 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://json-schema.org/draft/2020-12/meta/core",
+ "$dynamicAnchor": "meta",
+
+ "title": "Core vocabulary meta-schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "$ref": "#/$defs/uriReferenceString",
+ "$comment": "Non-empty fragments not allowed.",
+ "pattern": "^[^#]*#?$"
+ },
+ "$schema": { "$ref": "#/$defs/uriString" },
+ "$ref": { "$ref": "#/$defs/uriReferenceString" },
+ "$anchor": { "$ref": "#/$defs/anchorString" },
+ "$dynamicRef": { "$ref": "#/$defs/uriReferenceString" },
+ "$dynamicAnchor": { "$ref": "#/$defs/anchorString" },
+ "$vocabulary": {
+ "type": "object",
+ "propertyNames": { "$ref": "#/$defs/uriString" },
+ "additionalProperties": {
+ "type": "boolean"
+ }
+ },
+ "$comment": {
+ "type": "string"
+ },
+ "$defs": {
+ "type": "object",
+ "additionalProperties": { "$dynamicRef": "#meta" }
+ }
+ },
+ "$defs": {
+ "anchorString": {
+ "type": "string",
+ "pattern": "^[A-Za-z_][-A-Za-z0-9._]*$"
+ },
+ "uriString": {
+ "type": "string",
+ "format": "uri"
+ },
+ "uriReferenceString": {
+ "type": "string",
+ "format": "uri-reference"
+ }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/format-annotation.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/format-annotation.json
new file mode 100644
index 000000000..3479e6695
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/format-annotation.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://json-schema.org/draft/2020-12/meta/format-annotation",
+ "$dynamicAnchor": "meta",
+
+ "title": "Format vocabulary meta-schema for annotation results",
+ "type": ["object", "boolean"],
+ "properties": {
+ "format": { "type": "string" }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/format-assertion.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/format-assertion.json
new file mode 100644
index 000000000..1a4f106cf
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/format-assertion.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://json-schema.org/draft/2020-12/meta/format-assertion",
+ "$dynamicAnchor": "meta",
+
+ "title": "Format vocabulary meta-schema for assertion results",
+ "type": ["object", "boolean"],
+ "properties": {
+ "format": { "type": "string" }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/hyper-schema.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/hyper-schema.json
new file mode 100644
index 000000000..f0beb5a4f
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/hyper-schema.json
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/hyper-schema",
+ "$id": "https://json-schema.org/draft/2020-12/meta/hyper-schema",
+ "$dynamicAnchor": "meta",
+
+ "title": "JSON Hyper-Schema Vocabulary Schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "base": {
+ "type": "string",
+ "format": "uri-template"
+ },
+ "links": {
+ "type": "array",
+ "items": {
+ "$ref": "https://json-schema.org/draft/2020-12/links"
+ }
+ }
+ },
+ "links": [
+ {
+ "rel": "self",
+ "href": "{+%24id}"
+ }
+ ]
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/meta-data.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/meta-data.json
new file mode 100644
index 000000000..4049ab21b
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/meta-data.json
@@ -0,0 +1,34 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://json-schema.org/draft/2020-12/meta/meta-data",
+ "$dynamicAnchor": "meta",
+
+ "title": "Meta-data vocabulary meta-schema",
+
+ "type": ["object", "boolean"],
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": true,
+ "deprecated": {
+ "type": "boolean",
+ "default": false
+ },
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "writeOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "examples": {
+ "type": "array",
+ "items": true
+ }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/unevaluated.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/unevaluated.json
new file mode 100644
index 000000000..93779e54e
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/unevaluated.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://json-schema.org/draft/2020-12/meta/unevaluated",
+ "$dynamicAnchor": "meta",
+
+ "title": "Unevaluated applicator vocabulary meta-schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "unevaluatedItems": { "$dynamicRef": "#meta" },
+ "unevaluatedProperties": { "$dynamicRef": "#meta" }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/validation.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/validation.json
new file mode 100644
index 000000000..ebb75db77
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/validation.json
@@ -0,0 +1,95 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://json-schema.org/draft/2020-12/meta/validation",
+ "$dynamicAnchor": "meta",
+
+ "title": "Validation vocabulary meta-schema",
+ "type": ["object", "boolean"],
+ "properties": {
+ "type": {
+ "anyOf": [
+ { "$ref": "#/$defs/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/$defs/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "const": true,
+ "enum": {
+ "type": "array",
+ "items": true
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/$defs/nonNegativeInteger" },
+ "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "maxItems": { "$ref": "#/$defs/nonNegativeInteger" },
+ "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "maxContains": { "$ref": "#/$defs/nonNegativeInteger" },
+ "minContains": {
+ "$ref": "#/$defs/nonNegativeInteger",
+ "default": 1
+ },
+ "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/$defs/stringArray" },
+ "dependentRequired": {
+ "type": "object",
+ "additionalProperties": {
+ "$ref": "#/$defs/stringArray"
+ }
+ }
+ },
+ "$defs": {
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "$ref": "#/$defs/nonNegativeInteger",
+ "default": 0
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/schema.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/schema.json
new file mode 100644
index 000000000..d5e2d31c3
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/schema.json
@@ -0,0 +1,58 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://json-schema.org/draft/2020-12/schema",
+ "$vocabulary": {
+ "https://json-schema.org/draft/2020-12/vocab/core": true,
+ "https://json-schema.org/draft/2020-12/vocab/applicator": true,
+ "https://json-schema.org/draft/2020-12/vocab/unevaluated": true,
+ "https://json-schema.org/draft/2020-12/vocab/validation": true,
+ "https://json-schema.org/draft/2020-12/vocab/meta-data": true,
+ "https://json-schema.org/draft/2020-12/vocab/format-annotation": true,
+ "https://json-schema.org/draft/2020-12/vocab/content": true
+ },
+ "$dynamicAnchor": "meta",
+
+ "title": "Core and Validation specifications meta-schema",
+ "allOf": [
+ {"$ref": "meta/core"},
+ {"$ref": "meta/applicator"},
+ {"$ref": "meta/unevaluated"},
+ {"$ref": "meta/validation"},
+ {"$ref": "meta/meta-data"},
+ {"$ref": "meta/format-annotation"},
+ {"$ref": "meta/content"}
+ ],
+ "type": ["object", "boolean"],
+ "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.",
+ "properties": {
+ "definitions": {
+ "$comment": "\"definitions\" has been replaced by \"$defs\".",
+ "type": "object",
+ "additionalProperties": { "$dynamicRef": "#meta" },
+ "deprecated": true,
+ "default": {}
+ },
+ "dependencies": {
+ "$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.",
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$dynamicRef": "#meta" },
+ { "$ref": "meta/validation#/$defs/stringArray" }
+ ]
+ },
+ "deprecated": true,
+ "default": {}
+ },
+ "$recursiveAnchor": {
+ "$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".",
+ "$ref": "meta/core#/$defs/anchorString",
+ "deprecated": true
+ },
+ "$recursiveRef": {
+ "$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".",
+ "$ref": "meta/core#/$defs/uriReferenceString",
+ "deprecated": true
+ }
+ }
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft4/schema.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft4/schema.json
new file mode 100644
index 000000000..bcbb84743
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft4/schema.json
@@ -0,0 +1,149 @@
+{
+ "id": "http://json-schema.org/draft-04/schema#",
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "description": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "positiveInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "positiveIntegerDefault0": {
+ "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
+ },
+ "simpleTypes": {
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ },
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string"
+ },
+ "$schema": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": {},
+ "multipleOf": {
+ "type": "number",
+ "minimum": 0,
+ "exclusiveMinimum": true
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "boolean",
+ "default": false
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "boolean",
+ "default": false
+ },
+ "maxLength": { "$ref": "#/definitions/positiveInteger" },
+ "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": {
+ "anyOf": [
+ { "type": "boolean" },
+ { "$ref": "#" }
+ ],
+ "default": {}
+ },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": {}
+ },
+ "maxItems": { "$ref": "#/definitions/positiveInteger" },
+ "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "maxProperties": { "$ref": "#/definitions/positiveInteger" },
+ "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": {
+ "anyOf": [
+ { "type": "boolean" },
+ { "$ref": "#" }
+ ],
+ "default": {}
+ },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "enum": {
+ "type": "array",
+ "minItems": 1,
+ "uniqueItems": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "dependencies": {
+ "exclusiveMaximum": [ "maximum" ],
+ "exclusiveMinimum": [ "minimum" ]
+ },
+ "default": {}
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft6/schema.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft6/schema.json
new file mode 100644
index 000000000..bd3e763bc
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft6/schema.json
@@ -0,0 +1,155 @@
+{
+ "$schema": "http://json-schema.org/draft-06/schema#",
+ "$id": "http://json-schema.org/draft-06/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ { "$ref": "#/definitions/nonNegativeInteger" },
+ { "default": 0 }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": {},
+ "examples": {
+ "type": "array",
+ "items": {}
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": { "$ref": "#" },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": {}
+ },
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": { "$ref": "#" },
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": { "$ref": "#" },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "propertyNames": { "$ref": "#" },
+ "const": {},
+ "enum": {
+ "type": "array",
+ "minItems": 1,
+ "uniqueItems": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "default": {}
+}
diff --git a/Solutions/Corvus.Json.SourceGenerator/metaschema/draft7/schema.json b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft7/schema.json
new file mode 100644
index 000000000..fb92c7f75
--- /dev/null
+++ b/Solutions/Corvus.Json.SourceGenerator/metaschema/draft7/schema.json
@@ -0,0 +1,172 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "http://json-schema.org/draft-07/schema#",
+ "title": "Core schema meta-schema",
+ "definitions": {
+ "schemaArray": {
+ "type": "array",
+ "minItems": 1,
+ "items": { "$ref": "#" }
+ },
+ "nonNegativeInteger": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "nonNegativeIntegerDefault0": {
+ "allOf": [
+ { "$ref": "#/definitions/nonNegativeInteger" },
+ { "default": 0 }
+ ]
+ },
+ "simpleTypes": {
+ "enum": [
+ "array",
+ "boolean",
+ "integer",
+ "null",
+ "number",
+ "object",
+ "string"
+ ]
+ },
+ "stringArray": {
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true,
+ "default": []
+ }
+ },
+ "type": ["object", "boolean"],
+ "properties": {
+ "$id": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$schema": {
+ "type": "string",
+ "format": "uri"
+ },
+ "$ref": {
+ "type": "string",
+ "format": "uri-reference"
+ },
+ "$comment": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "description": {
+ "type": "string"
+ },
+ "default": true,
+ "readOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "writeOnly": {
+ "type": "boolean",
+ "default": false
+ },
+ "examples": {
+ "type": "array",
+ "items": true
+ },
+ "multipleOf": {
+ "type": "number",
+ "exclusiveMinimum": 0
+ },
+ "maximum": {
+ "type": "number"
+ },
+ "exclusiveMaximum": {
+ "type": "number"
+ },
+ "minimum": {
+ "type": "number"
+ },
+ "exclusiveMinimum": {
+ "type": "number"
+ },
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "pattern": {
+ "type": "string",
+ "format": "regex"
+ },
+ "additionalItems": { "$ref": "#" },
+ "items": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/schemaArray" }
+ ],
+ "default": true
+ },
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "uniqueItems": {
+ "type": "boolean",
+ "default": false
+ },
+ "contains": { "$ref": "#" },
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+ "required": { "$ref": "#/definitions/stringArray" },
+ "additionalProperties": { "$ref": "#" },
+ "definitions": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "properties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "default": {}
+ },
+ "patternProperties": {
+ "type": "object",
+ "additionalProperties": { "$ref": "#" },
+ "propertyNames": { "format": "regex" },
+ "default": {}
+ },
+ "dependencies": {
+ "type": "object",
+ "additionalProperties": {
+ "anyOf": [
+ { "$ref": "#" },
+ { "$ref": "#/definitions/stringArray" }
+ ]
+ }
+ },
+ "propertyNames": { "$ref": "#" },
+ "const": true,
+ "enum": {
+ "type": "array",
+ "items": true,
+ "minItems": 1,
+ "uniqueItems": true
+ },
+ "type": {
+ "anyOf": [
+ { "$ref": "#/definitions/simpleTypes" },
+ {
+ "type": "array",
+ "items": { "$ref": "#/definitions/simpleTypes" },
+ "minItems": 1,
+ "uniqueItems": true
+ }
+ ]
+ },
+ "format": { "type": "string" },
+ "contentMediaType": { "type": "string" },
+ "contentEncoding": { "type": "string" },
+ "if": { "$ref": "#" },
+ "then": { "$ref": "#" },
+ "else": { "$ref": "#" },
+ "allOf": { "$ref": "#/definitions/schemaArray" },
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
+ "not": { "$ref": "#" }
+ },
+ "default": true
+}
diff --git a/Solutions/Corvus.JsonSchema.sln b/Solutions/Corvus.JsonSchema.sln
index c880f1b9d..9948d51c7 100644
--- a/Solutions/Corvus.JsonSchema.sln
+++ b/Solutions/Corvus.JsonSchema.sln
@@ -102,6 +102,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Corvus.Json.JsonReference",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Corvus.Json.CodeGeneration.HttpClientDocumentResolver", "Corvus.Json.CodeGeneration.HttpClientDocumentResolver\Corvus.Json.CodeGeneration.HttpClientDocumentResolver.csproj", "{75D43E9A-AA35-407F-85A2-E681962AA71E}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGenerator", "SourceGenerator", "{63B6708D-861F-4BAA-B35C-F52D68E6D84A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Corvus.Json.SourceGenerator", "Corvus.Json.SourceGenerator\Corvus.Json.SourceGenerator.csproj", "{30AB9BB0-925D-4B05-8E31-EDEC238869D5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sandbox.SourceGenerator", "Sandbox.SourceGenerator\Sandbox.SourceGenerator.csproj", "{95BCD5E7-B37F-4E36-AC00-21708F4E87BA}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -340,6 +346,22 @@ Global
{75D43E9A-AA35-407F-85A2-E681962AA71E}.Release|Any CPU.Build.0 = Release|Any CPU
{75D43E9A-AA35-407F-85A2-E681962AA71E}.Release|x64.ActiveCfg = Release|Any CPU
{75D43E9A-AA35-407F-85A2-E681962AA71E}.Release|x64.Build.0 = Release|Any CPU
+ {30AB9BB0-925D-4B05-8E31-EDEC238869D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {30AB9BB0-925D-4B05-8E31-EDEC238869D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {30AB9BB0-925D-4B05-8E31-EDEC238869D5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {30AB9BB0-925D-4B05-8E31-EDEC238869D5}.Debug|x64.Build.0 = Debug|Any CPU
+ {30AB9BB0-925D-4B05-8E31-EDEC238869D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {30AB9BB0-925D-4B05-8E31-EDEC238869D5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {30AB9BB0-925D-4B05-8E31-EDEC238869D5}.Release|x64.ActiveCfg = Release|Any CPU
+ {30AB9BB0-925D-4B05-8E31-EDEC238869D5}.Release|x64.Build.0 = Release|Any CPU
+ {95BCD5E7-B37F-4E36-AC00-21708F4E87BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {95BCD5E7-B37F-4E36-AC00-21708F4E87BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {95BCD5E7-B37F-4E36-AC00-21708F4E87BA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {95BCD5E7-B37F-4E36-AC00-21708F4E87BA}.Debug|x64.Build.0 = Debug|Any CPU
+ {95BCD5E7-B37F-4E36-AC00-21708F4E87BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {95BCD5E7-B37F-4E36-AC00-21708F4E87BA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {95BCD5E7-B37F-4E36-AC00-21708F4E87BA}.Release|x64.ActiveCfg = Release|Any CPU
+ {95BCD5E7-B37F-4E36-AC00-21708F4E87BA}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -375,6 +397,8 @@ Global
{6C13CDA3-46B4-4624-8170-8DD74C18B215} = {2CE21CBD-4B31-4408-A0E1-01E673F6FADF}
{2C9BC284-F481-4C94-A40E-F818055DB352} = {ADC45E09-87EA-482A-B306-C361184C13D4}
{75D43E9A-AA35-407F-85A2-E681962AA71E} = {95D2B90C-DBBA-4AF7-BAE8-4E933E050E75}
+ {30AB9BB0-925D-4B05-8E31-EDEC238869D5} = {63B6708D-861F-4BAA-B35C-F52D68E6D84A}
+ {95BCD5E7-B37F-4E36-AC00-21708F4E87BA} = {63B6708D-861F-4BAA-B35C-F52D68E6D84A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D744631F-1DD6-4290-BF2D-67925BA2341D}
diff --git a/Solutions/Sandbox.SourceGenerator/Model/FlimFlam.cs b/Solutions/Sandbox.SourceGenerator/Model/FlimFlam.cs
new file mode 100644
index 000000000..4666aa261
--- /dev/null
+++ b/Solutions/Sandbox.SourceGenerator/Model/FlimFlam.cs
@@ -0,0 +1,8 @@
+
+using Corvus.Json;
+
+namespace SourceGenTest2.Model;
+[JsonSchemaTypeGenerator("../test.json")]
+public readonly partial struct FlimFlam
+{
+}
diff --git a/Solutions/Sandbox.SourceGenerator/Sandbox.SourceGenerator.csproj b/Solutions/Sandbox.SourceGenerator/Sandbox.SourceGenerator.csproj
new file mode 100644
index 000000000..0643c40e6
--- /dev/null
+++ b/Solutions/Sandbox.SourceGenerator/Sandbox.SourceGenerator.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Solutions/Sandbox.SourceGenerator/WorkingWithGeneratedCode.cs b/Solutions/Sandbox.SourceGenerator/WorkingWithGeneratedCode.cs
new file mode 100644
index 000000000..666cf3c73
--- /dev/null
+++ b/Solutions/Sandbox.SourceGenerator/WorkingWithGeneratedCode.cs
@@ -0,0 +1,15 @@
+using Corvus.Json;
+using SourceGenTest2.Model;
+
+namespace Sandbox.SourceGenerator;
+public static class WorkingWithGeneratedCode
+{
+ public static void Process()
+ {
+ FlimFlam flimFlam = JsonAny.ParseValue("[1,\"hello\",5]"u8);
+ Console.WriteLine(flimFlam);
+ JsonArray array = flimFlam.As();
+ Console.WriteLine(array);
+ Console.WriteLine(flimFlam.IsValid());
+ }
+}
diff --git a/Solutions/Sandbox.SourceGenerator/test.json b/Solutions/Sandbox.SourceGenerator/test.json
new file mode 100644
index 000000000..0b9a74e21
--- /dev/null
+++ b/Solutions/Sandbox.SourceGenerator/test.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://corvus-oss.org/json-schema/2020-12/schema",
+ "type": "array",
+ "prefixItems": [
+ {
+ "$corvusTypeName": "PositiveInt32",
+ "type": "integer",
+ "format": "int32",
+ "minimum": 0
+ },
+ { "type": "string" },
+ { "type": "number", "minimum": 3.5 }
+ ],
+ "unevaluatedItems": false
+}
diff --git a/Solutions/Sandbox/Program.cs b/Solutions/Sandbox/Program.cs
index 6c02fe8c4..c6dce7fcd 100644
--- a/Solutions/Sandbox/Program.cs
+++ b/Solutions/Sandbox/Program.cs
@@ -1,13 +1,5 @@
-using Corvus.Json;
-using Corvus.Json.CodeGeneration;
-using Corvus.Json.CodeGeneration.CSharp;
-using Corvus.Json.CodeGeneration.CSharp.QuickStart;
+using Sandbox.SourceGenerator;
-var options = new CSharpLanguageProvider.Options("Test");
-
-var generator = CSharpGenerator.Create(options: options);
-
-IReadOnlyCollection generatedFiles =
- await generator.GenerateFilesAsync(new JsonReference("./CombinedDocumentSchema.json"));
+WorkingWithGeneratedCode.Process();
Console.ReadLine();
\ No newline at end of file
diff --git a/Solutions/Sandbox/Sandbox.csproj b/Solutions/Sandbox/Sandbox.csproj
index 9f4244ba0..7f47a93b3 100644
--- a/Solutions/Sandbox/Sandbox.csproj
+++ b/Solutions/Sandbox/Sandbox.csproj
@@ -14,6 +14,7 @@
+