Skip to content

Commit 6f63054

Browse files
authored
Enable CI builds (#18)
* Add submodule for JSON Schema test suite * Modify default test settings to use schema files from submodule * Remove local settings files from repo * Add pipelines and scripted build * Disable automatic building of benchmark code on each test execution * Modify path handling to work on Unix (\ vs / path separator) * Adjust package names and disable packaging for projects that shouldn't build NuGet packages * Remove Corvus.JsonHelpers, moving the handful of bits we're using into Corvus.Json.ExtendedTypes
1 parent 1204cee commit 6f63054

File tree

180 files changed

+7473
-10197
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

180 files changed

+7473
-10197
lines changed

.gitignore

+5-1
Original file line numberDiff line numberDiff line change
@@ -342,4 +342,8 @@ coverage.cobertura.xml
342342

343343
# NCrunch files
344344
*.ncrunchsolution
345-
*.ncrunchproject
345+
*.ncrunchproject
346+
347+
# Scripted build artifacts
348+
/_codeCoverage
349+
/_packages

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "JSON-Schema-Test-Suite"]
2+
path = JSON-Schema-Test-Suite
3+
url = https://github.com/json-schema-org/JSON-Schema-Test-Suite.git

JSON-Schema-Test-Suite

Submodule JSON-Schema-Test-Suite added at 387d690

README.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<Import Project="$(EndjinProjectPropsPath)" Condition="$(EndjinProjectPropsPath) != ''" />
3+
4+
<PropertyGroup>
5+
<TargetFramework>net6.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<PropertyGroup>
11+
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
12+
<Description>Defines .NET types representing data types found in JSON, including numerous types for specialized string forms such as JsonUuid and JsonDateTime.</Description>
13+
</PropertyGroup>
14+
15+
16+
<ItemGroup>
17+
<PackageReference Include="Corvus.Extensions" Version="1.1.4" />
18+
<PackageReference Include="Endjin.RecommendedPractices" Version="1.2.0">
19+
<PrivateAssets>all</PrivateAssets>
20+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
21+
</PackageReference>
22+
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
23+
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="6.0.5" />
24+
<PackageReference Include="NodaTime" Version="3.1.0" />
25+
<PackageReference Include="System.Buffers" Version="4.5.1" />
26+
<PackageReference Include="System.Collections.Immutable" Version="6.0.0" />
27+
<PackageReference Include="System.Text.Json" Version="6.0.4" />
28+
</ItemGroup>
29+
30+
<ItemGroup>
31+
<PackageReference Update="StyleCop.Analyzers" Version="1.2.0-beta.333" />
32+
</ItemGroup>
33+
34+
</Project>

Solutions/Corvus.JsonHelpers/Corvus.Json/JsonConstants.cs Solutions/Corvus.Json.ExtendedTypes/Corvus.Json/Internal/JsonConstants.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Corvus.Json
1313
{
14-
public static class JsonConstants
14+
internal static class JsonConstants
1515
{
1616
public const byte OpenBrace = (byte)'{';
1717
public const byte CloseBrace = (byte)'}';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// <copyright file="JsonHelpers.cs" company="Endjin Limited">
2+
// Copyright (c) Endjin Limited. All rights reserved.
3+
// </copyright>
4+
// Derived from code:
5+
// Licensed to the .NET Foundation under one or more agreements.
6+
// The .NET Foundation licenses this file to you under the MIT license.
7+
8+
#pragma warning disable
9+
10+
using System;
11+
using System.Buffers;
12+
using System.Collections.Generic;
13+
using System.Diagnostics;
14+
using System.Runtime.CompilerServices;
15+
using System.Text;
16+
using System.Text.Json;
17+
using System.Text.Json.Serialization;
18+
19+
namespace Corvus.Json
20+
{
21+
internal static partial class JsonHelpers
22+
{
23+
// Copy of Array.MaxArrayLength. For byte arrays the limit is slightly larger
24+
private const int MaxArrayLength = 0X7FEFFFFF;
25+
26+
/// <summary>
27+
/// Returns <see langword="true"/> if <paramref name="value"/> is between
28+
/// <paramref name="lowerBound"/> and <paramref name="upperBound"/>, inclusive.
29+
/// </summary>
30+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
31+
public static bool IsInRangeInclusive(uint value, uint lowerBound, uint upperBound)
32+
=> (value - lowerBound) <= (upperBound - lowerBound);
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// <copyright file="JsonReaderHelper.cs" company="Endjin Limited">
2+
// Copyright (c) Endjin Limited. All rights reserved.
3+
// </copyright>
4+
// Derived from code:
5+
// Licensed to the .NET Foundation under one or more agreements.
6+
// The .NET Foundation licenses this file to you under the MIT license.
7+
8+
#pragma warning disable
9+
10+
using System;
11+
using System.Buffers;
12+
using System.Buffers.Text;
13+
using System.Diagnostics;
14+
using System.Diagnostics.CodeAnalysis;
15+
using System.Text;
16+
17+
namespace Corvus.Json
18+
{
19+
internal static partial class JsonReaderHelper
20+
{
21+
public static ReadOnlySpan<byte> GetUnescapedSpan(ReadOnlySpan<byte> utf8Source, int idx)
22+
{
23+
// The escaped name is always >= than the unescaped, so it is safe to use escaped name for the buffer length.
24+
int length = utf8Source.Length;
25+
byte[]? pooledName = null;
26+
27+
Span<byte> utf8Unescaped = length <= JsonConstants.StackallocThreshold ?
28+
stackalloc byte[length] :
29+
(pooledName = ArrayPool<byte>.Shared.Rent(length));
30+
31+
Unescape(utf8Source, utf8Unescaped, idx, out int written);
32+
Debug.Assert(written > 0);
33+
34+
ReadOnlySpan<byte> propertyName = utf8Unescaped.Slice(0, written).ToArray();
35+
Debug.Assert(!propertyName.IsEmpty);
36+
37+
if (pooledName != null)
38+
{
39+
new Span<byte>(pooledName, 0, written).Clear();
40+
ArrayPool<byte>.Shared.Return(pooledName);
41+
}
42+
43+
return propertyName;
44+
}
45+
46+
internal static void Unescape(ReadOnlySpan<byte> source, Span<byte> destination, int idx, out int written)
47+
{
48+
Debug.Assert(idx >= 0 && idx < source.Length);
49+
Debug.Assert(source[idx] == JsonConstants.BackSlash);
50+
Debug.Assert(destination.Length >= source.Length);
51+
52+
source.Slice(0, idx).CopyTo(destination);
53+
written = idx;
54+
55+
for (; idx < source.Length; idx++)
56+
{
57+
byte currentByte = source[idx];
58+
if (currentByte == JsonConstants.BackSlash)
59+
{
60+
idx++;
61+
currentByte = source[idx];
62+
63+
if (currentByte == JsonConstants.Quote)
64+
{
65+
destination[written++] = JsonConstants.Quote;
66+
}
67+
else if (currentByte == 'n')
68+
{
69+
destination[written++] = JsonConstants.LineFeed;
70+
}
71+
else if (currentByte == 'r')
72+
{
73+
destination[written++] = JsonConstants.CarriageReturn;
74+
}
75+
else if (currentByte == JsonConstants.BackSlash)
76+
{
77+
destination[written++] = JsonConstants.BackSlash;
78+
}
79+
else if (currentByte == JsonConstants.Slash)
80+
{
81+
destination[written++] = JsonConstants.Slash;
82+
}
83+
else if (currentByte == 't')
84+
{
85+
destination[written++] = JsonConstants.Tab;
86+
}
87+
else if (currentByte == 'b')
88+
{
89+
destination[written++] = JsonConstants.BackSpace;
90+
}
91+
else if (currentByte == 'f')
92+
{
93+
destination[written++] = JsonConstants.FormFeed;
94+
}
95+
else if (currentByte == 'u')
96+
{
97+
// 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
98+
// Otherwise, the Utf8JsonReader would have alreayd thrown an exception.
99+
Debug.Assert(source.Length >= idx + 5);
100+
101+
bool result = Utf8Parser.TryParse(source.Slice(idx + 1, 4), out int scalar, out int bytesConsumed, 'x');
102+
Debug.Assert(result);
103+
Debug.Assert(bytesConsumed == 4);
104+
idx += bytesConsumed; // The loop iteration will increment idx past the last hex digit
105+
106+
if (JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
107+
{
108+
// The first hex value cannot be a low surrogate.
109+
if (scalar >= JsonConstants.LowSurrogateStartValue)
110+
{
111+
throw new InvalidOperationException($"Read Invalid UTF16: {scalar}");
112+
}
113+
114+
Debug.Assert(JsonHelpers.IsInRangeInclusive((uint)scalar, JsonConstants.HighSurrogateStartValue, JsonConstants.HighSurrogateEndValue));
115+
116+
idx += 3; // Skip the last hex digit and the next \u
117+
118+
// We must have a low surrogate following a high surrogate.
119+
if (source.Length < idx + 4 || source[idx - 2] != '\\' || source[idx - 1] != 'u')
120+
{
121+
throw new InvalidOperationException("Read Invalid UTF16");
122+
}
123+
124+
// 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
125+
// Otherwise, the Utf8JsonReader would have alreayd thrown an exception.
126+
result = Utf8Parser.TryParse(source.Slice(idx, 4), out int lowSurrogate, out bytesConsumed, 'x');
127+
Debug.Assert(result);
128+
Debug.Assert(bytesConsumed == 4);
129+
130+
// If the first hex value is a high surrogate, the next one must be a low surrogate.
131+
if (!JsonHelpers.IsInRangeInclusive((uint)lowSurrogate, JsonConstants.LowSurrogateStartValue, JsonConstants.LowSurrogateEndValue))
132+
{
133+
throw new InvalidOperationException($"Read Invalid UTF16: {lowSurrogate}");
134+
}
135+
136+
idx += bytesConsumed - 1; // The loop iteration will increment idx past the last hex digit
137+
138+
// To find the unicode scalar:
139+
// (0x400 * (High surrogate - 0xD800)) + Low surrogate - 0xDC00 + 0x10000
140+
scalar = (JsonConstants.BitShiftBy10 * (scalar - JsonConstants.HighSurrogateStartValue))
141+
+ (lowSurrogate - JsonConstants.LowSurrogateStartValue)
142+
+ JsonConstants.UnicodePlane01StartValue;
143+
}
144+
145+
var rune = new Rune(scalar);
146+
int bytesWritten = rune.EncodeToUtf8(destination.Slice(written));
147+
Debug.Assert(bytesWritten <= 4);
148+
written += bytesWritten;
149+
}
150+
}
151+
else
152+
{
153+
destination[written++] = currentByte;
154+
}
155+
}
156+
}
157+
}
158+
}

Solutions/Corvus.Json/Corvus.Json/JsonUri.cs Solutions/Corvus.Json.ExtendedTypes/Corvus.Json/JsonUri.cs

+16-1
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,22 @@ private static string FormatUri(Uri value)
498498

499499
private static bool TryParseUri(string text, [NotNullWhen(true)] out Uri? value)
500500
{
501-
return Uri.TryCreate(text, UriKind.Absolute, out value);
501+
// Uri.TryCreate considers full-qualified file paths to be acceptable as absolute Uris.
502+
// This means that on Linux "/abc" is considered an acceptable absolute Uri! (This is
503+
// conceptually equivalent to "C:\abc" being an absolute Uri on Windows, but it's more
504+
// of a problem because a lot of relative Uris of the kind you come across on the web
505+
// look exactly like Unix file paths.)
506+
// https://github.com/dotnet/runtime/issues/22718
507+
// However, this only needs to be a problem if you insist that the Uri is absolute.
508+
// If you accept either absolute or relative Uris, it will intepret "/abc" as a
509+
// relative Uri on either Windows or Linux. It only interprets it as an absolute Uri
510+
// if you pass UriKind.Absolute when parsing.
511+
// This is why we take the peculiar-looking step of passing UriKind.RelativeOrAbsolute
512+
// and then rejecting relative Uris. This causes this method to reject "/abc" on all
513+
// platforms. Back when we passed UriKind.Absolute, this code incorrectly accepted
514+
// "abc".
515+
return Uri.TryCreate(text, UriKind.RelativeOrAbsolute, out value) &&
516+
value.IsAbsoluteUri;
502517
}
503518
}
504519
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<Import Project="$(EndjinProjectPropsPath)" Condition="$(EndjinProjectPropsPath) != ''" />
4+
5+
<PropertyGroup>
6+
<TargetFramework>net6.0</TargetFramework>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<Nullable>enable</Nullable>
9+
</PropertyGroup>
10+
11+
<PropertyGroup>
12+
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
13+
<Description>Object models representing JSON Schema documents (201909 and 202012 drafts)</Description>
14+
</PropertyGroup>
15+
16+
17+
<ItemGroup>
18+
<PackageReference Include="Endjin.RecommendedPractices" Version="1.2.0">
19+
<PrivateAssets>all</PrivateAssets>
20+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
21+
</PackageReference>
22+
</ItemGroup>
23+
24+
25+
<ItemGroup>
26+
<ProjectReference Include="..\Corvus.Json.ExtendedTypes\Corvus.Json.ExtendedTypes.csproj" />
27+
</ItemGroup>
28+
29+
</Project>

0 commit comments

Comments
 (0)