Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable CI builds #18

Merged
merged 12 commits into from
May 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,8 @@ coverage.cobertura.xml

# NCrunch files
*.ncrunchsolution
*.ncrunchproject
*.ncrunchproject

# Scripted build artifacts
/_codeCoverage
/_packages
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "JSON-Schema-Test-Suite"]
path = JSON-Schema-Test-Suite
url = https://github.com/json-schema-org/JSON-Schema-Test-Suite.git
1 change: 1 addition & 0 deletions JSON-Schema-Test-Suite
Submodule JSON-Schema-Test-Suite added at 387d69
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,33 @@
Support for Json Schema validation and entity generation

For an introduction to the concepts here, take a look at [this blog post](https://endjin.com/blog/2021/05/csharp-serialization-with-system-text-json-schema).

## Use of JSON-Schema-Test-Suite

This project uses test suites from https://github.com/json-schema-org/JSON-Schema-Test-Suite to
validate operation. The ./JSON-Schema-Test-Suite folder is a submodule pointing to that test suite
repo. When cloning this repository it is important to clone submodules, because test projects in
this repository depend on that submodule being present. If you've already cloned the project, and
haven't yet got the submodules, run this commands:

```
git submodule update --init --recursive
```

Note that `git pull` does nota utomatically update submodules, so if `git pull` reports that any
submodules have changed, you can use the preceding command again, used to update the existing
submodule reference.

When updating to newer versions of the test suite, we can update the submodule reference thus:

```
cd JSON-Schema-Test-Suite
git fetch
git merge origin/master
cd ..
git commit - "Updated to latest JSON Schema Test Suite"
```

(Or you can use `git submodule update --remote` instead of `cd`ing into the submodule folder and
updating from there.)

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(EndjinProjectPropsPath)" Condition="$(EndjinProjectPropsPath) != ''" />

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<PropertyGroup>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<Description>Defines .NET types representing data types found in JSON, including numerous types for specialized string forms such as JsonUuid and JsonDateTime.</Description>
</PropertyGroup>


<ItemGroup>
<PackageReference Include="Corvus.Extensions" Version="1.1.4" />
<PackageReference Include="Endjin.RecommendedPractices" Version="1.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.5" />
<PackageReference Include="NodaTime" Version="3.1.0" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.Collections.Immutable" Version="6.0.0" />
<PackageReference Include="System.Text.Json" Version="6.0.4" />
</ItemGroup>

<ItemGroup>
<PackageReference Update="StyleCop.Analyzers" Version="1.2.0-beta.333" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Corvus.Json
{
public static class JsonConstants
internal static class JsonConstants
{
public const byte OpenBrace = (byte)'{';
public const byte CloseBrace = (byte)'}';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// <copyright file="JsonHelpers.cs" company="Endjin Limited">
// Copyright (c) Endjin Limited. All rights reserved.
// </copyright>
// 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.

#pragma warning disable

using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Corvus.Json
{
internal static partial class JsonHelpers
{
// Copy of Array.MaxArrayLength. For byte arrays the limit is slightly larger
private const int MaxArrayLength = 0X7FEFFFFF;

/// <summary>
/// Returns <see langword="true"/> if <paramref name="value"/> is between
/// <paramref name="lowerBound"/> and <paramref name="upperBound"/>, inclusive.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsInRangeInclusive(uint value, uint lowerBound, uint upperBound)
=> (value - lowerBound) <= (upperBound - lowerBound);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// <copyright file="JsonReaderHelper.cs" company="Endjin Limited">
// Copyright (c) Endjin Limited. All rights reserved.
// </copyright>
// 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.

#pragma warning disable

using System;
using System.Buffers;
using System.Buffers.Text;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;

namespace Corvus.Json
{
internal static partial class JsonReaderHelper
{
public static ReadOnlySpan<byte> GetUnescapedSpan(ReadOnlySpan<byte> utf8Source, int idx)
{
// The escaped name is always >= than the unescaped, so it is safe to use escaped name for the buffer length.
int length = utf8Source.Length;
byte[]? pooledName = null;

Span<byte> utf8Unescaped = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
(pooledName = ArrayPool<byte>.Shared.Rent(length));

Unescape(utf8Source, utf8Unescaped, idx, out int written);
Debug.Assert(written > 0);

ReadOnlySpan<byte> propertyName = utf8Unescaped.Slice(0, written).ToArray();
Debug.Assert(!propertyName.IsEmpty);

if (pooledName != null)
{
new Span<byte>(pooledName, 0, written).Clear();
ArrayPool<byte>.Shared.Return(pooledName);
}

return propertyName;
}

internal static void Unescape(ReadOnlySpan<byte> source, Span<byte> destination, int idx, out int written)
{
Debug.Assert(idx >= 0 && idx < source.Length);
Debug.Assert(source[idx] == JsonConstants.BackSlash);
Debug.Assert(destination.Length >= source.Length);

source.Slice(0, idx).CopyTo(destination);
written = idx;

for (; idx < source.Length; idx++)
{
byte currentByte = source[idx];
if (currentByte == JsonConstants.BackSlash)
{
idx++;
currentByte = source[idx];

if (currentByte == JsonConstants.Quote)
{
destination[written++] = JsonConstants.Quote;
}
else if (currentByte == 'n')
{
destination[written++] = JsonConstants.LineFeed;
}
else if (currentByte == 'r')
{
destination[written++] = JsonConstants.CarriageReturn;
}
else if (currentByte == JsonConstants.BackSlash)
{
destination[written++] = JsonConstants.BackSlash;
}
else if (currentByte == JsonConstants.Slash)
{
destination[written++] = JsonConstants.Slash;
}
else if (currentByte == 't')
{
destination[written++] = JsonConstants.Tab;
}
else if (currentByte == 'b')
{
destination[written++] = JsonConstants.BackSpace;
}
else if (currentByte == 'f')
{
destination[written++] = JsonConstants.FormFeed;
}
else if (currentByte == 'u')
{
// The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it
// Otherwise, the Utf8JsonReader would have alreayd thrown an exception.
Debug.Assert(source.Length >= idx + 5);

bool result = Utf8Parser.TryParse(source.Slice(idx + 1, 4), out int scalar, out int bytesConsumed, 'x');
Debug.Assert(result);
Debug.Assert(bytesConsumed == 4);
idx += bytesConsumed; // The loop iteration will increment idx past the last hex digit

if (JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
{
// The first hex value cannot be a low surrogate.
if (scalar >= JsonConstants.LowSurrogateStartValue)
{
throw new InvalidOperationException($"Read Invalid UTF16: {scalar}");
}

Debug.Assert(JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.HighSurrogateEndValue));

idx += 3; // Skip the last hex digit and the next \u

// We must have a low surrogate following a high surrogate.
if (source.Length < idx + 4 || source[idx - 2] != '\\' || source[idx - 1] != 'u')
{
throw new InvalidOperationException("Read Invalid UTF16");
}

// The source is known to be valid JSON, and hence if we see a \u, it is guaranteed to have 4 hex digits following it
// Otherwise, the Utf8JsonReader would have alreayd thrown an exception.
result = Utf8Parser.TryParse(source.Slice(idx, 4), out int lowSurrogate, out bytesConsumed, 'x');
Debug.Assert(result);
Debug.Assert(bytesConsumed == 4);

// If the first hex value is a high surrogate, the next one must be a low surrogate.
if (!JsonHelpers.IsInRangeInclusive((uint)lowSurrogate, JsonConstants.LowSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
{
throw new InvalidOperationException($"Read Invalid UTF16: {lowSurrogate}");
}

idx += bytesConsumed - 1; // The loop iteration will increment idx past the last hex digit

// To find the unicode scalar:
// (0x400 * (High surrogate - 0xD800)) + Low surrogate - 0xDC00 + 0x10000
scalar = (JsonConstants.BitShiftBy10 * (scalar - JsonConstants.HighSurrogateStartValue))
+ (lowSurrogate - JsonConstants.LowSurrogateStartValue)
+ JsonConstants.UnicodePlane01StartValue;
}

var rune = new Rune(scalar);
int bytesWritten = rune.EncodeToUtf8(destination.Slice(written));
Debug.Assert(bytesWritten <= 4);
written += bytesWritten;
}
}
else
{
destination[written++] = currentByte;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,22 @@ private static string FormatUri(Uri value)

private static bool TryParseUri(string text, [NotNullWhen(true)] out Uri? value)
{
return Uri.TryCreate(text, UriKind.Absolute, out value);
// Uri.TryCreate considers full-qualified file paths to be acceptable as absolute Uris.
// This means that on Linux "/abc" is considered an acceptable absolute Uri! (This is
// conceptually equivalent to "C:\abc" being an absolute Uri on Windows, but it's more
// of a problem because a lot of relative Uris of the kind you come across on the web
// look exactly like Unix file paths.)
// https://github.com/dotnet/runtime/issues/22718
// However, this only needs to be a problem if you insist that the Uri is absolute.
// If you accept either absolute or relative Uris, it will intepret "/abc" as a
// relative Uri on either Windows or Linux. It only interprets it as an absolute Uri
// if you pass UriKind.Absolute when parsing.
// This is why we take the peculiar-looking step of passing UriKind.RelativeOrAbsolute
// and then rejecting relative Uris. This causes this method to reject "/abc" on all
// platforms. Back when we passed UriKind.Absolute, this code incorrectly accepted
// "abc".
return Uri.TryCreate(text, UriKind.RelativeOrAbsolute, out value) &&
value.IsAbsoluteUri;
}
}
}
29 changes: 29 additions & 0 deletions Solutions/Corvus.Json.JsonSchema/Corvus.Json.JsonSchema.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="$(EndjinProjectPropsPath)" Condition="$(EndjinProjectPropsPath) != ''" />

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<PropertyGroup>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<Description>Object models representing JSON Schema documents (201909 and 202012 drafts)</Description>
</PropertyGroup>


<ItemGroup>
<PackageReference Include="Endjin.RecommendedPractices" Version="1.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>


<ItemGroup>
<ProjectReference Include="..\Corvus.Json.ExtendedTypes\Corvus.Json.ExtendedTypes.csproj" />
</ItemGroup>

</Project>
Loading