From f79731c17b54f4ede11b805f407b33def449a86b Mon Sep 17 00:00:00 2001 From: Matthew Adams Date: Thu, 19 Sep 2024 17:33:53 +0100 Subject: [PATCH 1/4] Initial working commit for local package reference. --- .../CSharp/PublicCodeGeneratorExtensions.cs | 6 +- ...alueStringBuilder.AppendSpanFormattable.cs | 31 ++ .../ValueStringBuilder.Replace.cs | 165 +++++++ .../ValueStringBuilder.cs | 413 ++++++++++++++++++ .../Corvus.Json.SourceGenerator.csproj | 145 ++++++ .../Corvus.Json.SourceGenerator.props | 12 + .../IncrementalSourceGenerator.cs | 374 ++++++++++++++++ .../IndexRange/Index.cs | 157 +++++++ .../IndexRange/Range.cs | 100 +++++ .../Corvus.Json.SourceGenerator/Metaschema.cs | 114 +++++ .../corvus/meta/corvus-extensions.json | 11 + .../metaschema/corvus/schema.json | 60 +++ .../draft2019-09/meta/applicator.json | 53 +++ .../metaschema/draft2019-09/meta/content.json | 14 + .../metaschema/draft2019-09/meta/core.json | 54 +++ .../metaschema/draft2019-09/meta/format.json | 11 + .../draft2019-09/meta/hyper-schema.json | 26 ++ .../draft2019-09/meta/meta-data.json | 34 ++ .../draft2019-09/meta/validation.json | 95 ++++ .../metaschema/draft2019-09/schema.json | 42 ++ .../draft2020-12/meta/applicator.json | 45 ++ .../metaschema/draft2020-12/meta/content.json | 14 + .../metaschema/draft2020-12/meta/core.json | 48 ++ .../draft2020-12/meta/format-annotation.json | 11 + .../draft2020-12/meta/format-assertion.json | 11 + .../draft2020-12/meta/hyper-schema.json | 26 ++ .../draft2020-12/meta/meta-data.json | 34 ++ .../draft2020-12/meta/unevaluated.json | 12 + .../draft2020-12/meta/validation.json | 95 ++++ .../metaschema/draft2020-12/schema.json | 58 +++ .../metaschema/draft4/schema.json | 149 +++++++ .../metaschema/draft6/schema.json | 155 +++++++ .../metaschema/draft7/schema.json | 172 ++++++++ Solutions/Corvus.JsonSchema.sln | 24 + .../Sandbox.SourceGenerator/Model/FlimFlam.cs | 8 + .../Sandbox.SourceGenerator.csproj | 18 + .../WorkingWithGeneratedCode.cs | 14 + Solutions/Sandbox.SourceGenerator/test.json | 19 + 38 files changed, 2827 insertions(+), 3 deletions(-) create mode 100644 Solutions/Corvus.Json.SourceGenerator/Corvus.HighPerformance/ValueStringBuilder.AppendSpanFormattable.cs create mode 100644 Solutions/Corvus.Json.SourceGenerator/Corvus.HighPerformance/ValueStringBuilder.Replace.cs create mode 100644 Solutions/Corvus.Json.SourceGenerator/Corvus.HighPerformance/ValueStringBuilder.cs create mode 100644 Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj create mode 100644 Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.props create mode 100644 Solutions/Corvus.Json.SourceGenerator/IncrementalSourceGenerator.cs create mode 100644 Solutions/Corvus.Json.SourceGenerator/IndexRange/Index.cs create mode 100644 Solutions/Corvus.Json.SourceGenerator/IndexRange/Range.cs create mode 100644 Solutions/Corvus.Json.SourceGenerator/Metaschema.cs create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/corvus/meta/corvus-extensions.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/corvus/schema.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/applicator.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/content.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/core.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/format.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/hyper-schema.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/meta-data.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/meta/validation.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2019-09/schema.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/applicator.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/content.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/core.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/format-annotation.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/format-assertion.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/hyper-schema.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/meta-data.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/unevaluated.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/meta/validation.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft2020-12/schema.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft4/schema.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft6/schema.json create mode 100644 Solutions/Corvus.Json.SourceGenerator/metaschema/draft7/schema.json create mode 100644 Solutions/Sandbox.SourceGenerator/Model/FlimFlam.cs create mode 100644 Solutions/Sandbox.SourceGenerator/Sandbox.SourceGenerator.csproj create mode 100644 Solutions/Sandbox.SourceGenerator/WorkingWithGeneratedCode.cs create mode 100644 Solutions/Sandbox.SourceGenerator/test.json 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..193fb5395 --- /dev/null +++ b/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj @@ -0,0 +1,145 @@ + + + + + netstandard2.0 + 12.0 + enable + cs + true + true + true + true + true + + + + true + $(NoWarn);nullable;NU5128;RS2008;SA0001 + 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..e287a3fef --- /dev/null +++ b/Solutions/Sandbox.SourceGenerator/Sandbox.SourceGenerator.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/Solutions/Sandbox.SourceGenerator/WorkingWithGeneratedCode.cs b/Solutions/Sandbox.SourceGenerator/WorkingWithGeneratedCode.cs new file mode 100644 index 000000000..76e4d0839 --- /dev/null +++ b/Solutions/Sandbox.SourceGenerator/WorkingWithGeneratedCode.cs @@ -0,0 +1,14 @@ +using Corvus.Json; +using SourceGenTest2.Model; + +namespace Sandbox.SourceGenerator; +public static class WorkingWithGeneratedCode +{ + public static void Process() + { + FlimFlam flimFlam = JsonAny.ParseValue("[1,2,3]"u8); + Console.WriteLine(flimFlam); + JsonArray array = flimFlam.As(); + Console.WriteLine(array); + } +} diff --git a/Solutions/Sandbox.SourceGenerator/test.json b/Solutions/Sandbox.SourceGenerator/test.json new file mode 100644 index 000000000..959a2af42 --- /dev/null +++ b/Solutions/Sandbox.SourceGenerator/test.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://corvus-oss.org/json-schema/2020-12/schema", + "type": "array", + "prefixItems": [ + { + "$corvusTypeName": "PositiveInt32", + "type": "integer", + "format": "int32", + "minimum": 0 + }, + { "type": "string" }, + { + "type": "string", + "format": "date-time" + }, + { "type": "number", "minimum": 3.5 } + ], + "unevaluatedItems": false +} From a7b801cb79ccb3ad26b703beba0b8f27ea7a958e Mon Sep 17 00:00:00 2001 From: Matthew Adams Date: Fri, 20 Sep 2024 08:53:09 +0100 Subject: [PATCH 2/4] The source generator is now packable as well as locally referencable. --- .../Corvus.Json.SourceGenerator.csproj | 23 +++++++++++++++---- .../Sandbox.SourceGenerator.csproj | 1 + .../WorkingWithGeneratedCode.cs | 3 ++- Solutions/Sandbox.SourceGenerator/test.json | 4 ---- Solutions/Sandbox/Program.cs | 12 ++-------- Solutions/Sandbox/Sandbox.csproj | 1 + 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj b/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj index 193fb5395..5f427f807 100644 --- a/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj +++ b/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj @@ -5,12 +5,8 @@ netstandard2.0 12.0 enable - cs true - true true - true - true @@ -18,6 +14,8 @@ $(NoWarn);nullable;NU5128;RS2008;SA0001 false false + true + false @@ -142,4 +140,21 @@ A source generator that will emit dotnet types based on JSON Schema. + + + + + + + + + + + + + + + + + diff --git a/Solutions/Sandbox.SourceGenerator/Sandbox.SourceGenerator.csproj b/Solutions/Sandbox.SourceGenerator/Sandbox.SourceGenerator.csproj index e287a3fef..0643c40e6 100644 --- a/Solutions/Sandbox.SourceGenerator/Sandbox.SourceGenerator.csproj +++ b/Solutions/Sandbox.SourceGenerator/Sandbox.SourceGenerator.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + true diff --git a/Solutions/Sandbox.SourceGenerator/WorkingWithGeneratedCode.cs b/Solutions/Sandbox.SourceGenerator/WorkingWithGeneratedCode.cs index 76e4d0839..666cf3c73 100644 --- a/Solutions/Sandbox.SourceGenerator/WorkingWithGeneratedCode.cs +++ b/Solutions/Sandbox.SourceGenerator/WorkingWithGeneratedCode.cs @@ -6,9 +6,10 @@ public static class WorkingWithGeneratedCode { public static void Process() { - FlimFlam flimFlam = JsonAny.ParseValue("[1,2,3]"u8); + 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 index 959a2af42..0b9a74e21 100644 --- a/Solutions/Sandbox.SourceGenerator/test.json +++ b/Solutions/Sandbox.SourceGenerator/test.json @@ -9,10 +9,6 @@ "minimum": 0 }, { "type": "string" }, - { - "type": "string", - "format": "date-time" - }, { "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 @@ + From 9c334458750918fc6bc6c710f972fed334c1cf4b Mon Sep 17 00:00:00 2001 From: Matthew Adams Date: Fri, 20 Sep 2024 09:16:07 +0100 Subject: [PATCH 3/4] Removed "generate package on build" so it can be handled by our build tooling. --- .../Corvus.Json.SourceGenerator.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj b/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj index 5f427f807..edc5936e5 100644 --- a/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj +++ b/Solutions/Corvus.Json.SourceGenerator/Corvus.Json.SourceGenerator.csproj @@ -14,7 +14,6 @@ $(NoWarn);nullable;NU5128;RS2008;SA0001 false false - true false From abbe907378d7caaf1c703098a50ea677377f4341 Mon Sep 17 00:00:00 2001 From: Matthew Adams Date: Fri, 20 Sep 2024 09:35:20 +0100 Subject: [PATCH 4/4] Added SourceGenerator info to the README. --- README.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) 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.